通过这几天查资料,翻代码。这里总结一下lame解析MP3的过程以及遇到问题。c++
开发环境是Linux下c++开发shell
使用lame解析MP3,主要包含如下几个头文件。缓存
#include <lame.h> #include <timestatus.h> #include <set_get.h> #include <get_audio.h> #include <lame_reader_def.h>
目前网上查到有关lame 解析MP3的资料很少。大部分是使用lame压缩MP3格式。大多人用libmad解码MP3。函数
惟一查到的几个有关lame解析MP3的demo格式雷同,兼容性不好。在测试过程当中遇到过不兼容的状况。测试
这种方式我也把方法列出来。主要使用mpglib_interfac.c文件中的hip_decode1_headers(……),以及hip_decode(......)方法。spa
点进去看一下就知道其实他们都是对hip_decode1_headerB()方法的封装,只不过前者须要提供mp3data_struct结构体指针。方便将mp3的数据格式读取出来,作一些前期工做。后者只是直接就开始解析了。命令行
源代码在公司内网中,取不出来,因此如下代码都是手敲复制出来的,可能会拼写错。别期望照搬过去就能直接运行,看懂先。3d
#include <lame.h> #include <string.h>
//定义数据 FILE* m_filename; hip_t m_hip; lame_t m_lame_gfp; mp3data_struct m_mp3data; //存储MP3的各种数据,采样率,通道数,帧数等 short int m_pcm_1[9000]; short int m_pcm_r[9000]; unsigned char m_mp3_buffer[9000]; //mp3文件中读取解析以前的数据缓存 int sampleRate; //采样率 int channelCount; //通道数 //初始化阶段 m_lame_gfp = lame_init(); lame_set_decode_only(m_lame_gfp,1); m_hip = hip_decode_init(); m_filename = fopen("voice.MP3","rb"); /** 这一段的逻辑就是,循环从文件中读取16个字节出来,而后交给hip_decode1_header1函数解析。 m_hip中会保存上一次解析的位置,因此直接不断的丢一些数据给hip_decode1_header1函数就行。 当成功解析出一个帧头的数据时,实际上就获得了整个MP3的采样率和通道数了。大部分MP3每一帧的信息是同样的。固然也不是所有的。 因此说对大对数MP3是兼容的。这里成功解析出来以后,m_mp3data.header_parsed值会变成1,所以可退出这个过程。 至于每次多输入一些数据也是能够的,例如将16设置成210,418均可以。 */ int reslen = -1; do{ reslen = fread(m_mp3_buff,,sizeof(char),16,m_filename); hip_decode1_header1(m_hip,m_mp3_buffer,reslen,m_pcm_l,m_pcm_r,&m_mp3data); sampleRate = m_mp3data.samplerate; channelCount = m_mp3data.stereo; }while(!m_mp3data.header_parsed && relen>0); fseek(m_filename,0,SEEK_SET); //将文件指针拨回起点。 //循环解析MP3音频数据阶段 int iread; unsigned char data[8192]; //存放解析以后的pcm static FILE *output_file = fopen(output.pcm,"W+"); /** 这里逻辑其实和上面的解析帧头是同样的。循环向hip_decode函数中投喂MP3数据,通过解析以后的pcm数据放在m_pcm_l以及m_pcm_r中。 hip_decode()返回值iread是采样数,不是字符长度,这点要注意。通常一个采样点是16位长,2个字节大小,与short int的长度一致。 */ while(reslen = fread(m_mp3buff,sizeof(char),418,m_filename)){ if(reslen > 0){ iread = hip_decode(m_hip,m_mp3_buffer,reslen,m_pcm_l,m_pcm_r); if(iread>0){ unsigned j = 0; for(int i = 0;i<sample;i++){ //双通道的话就将采样点交叉合并在一块儿。 memcpy(data + j,m_pcm_l + i , 2); j+=2; if(m_mp3data.stereo == 2){ memcpy(data + j,m_pcm_r + i , 2); j+=2; } } } } // 这里就能够将data中的数据写入文件中,或者输入至播放设备中。 fwrite(data,sizeof(short)*m_mp3dtat.stereo,sample,output_file); } if(m_hip){ hip_decode_exit(m_hip); } if(m_filename){ fclose(m_filename); } if(output_file){ fclose(output_file); }
以上代码是我制做解析MP3的程序的初版本,能暴力解析大部分MP3.直到有一天一个大佬问我这个解析程序作的怎么样,他那边要参考一下个人解析过程,而后甩给我一个12m的MP3,说,试试解析这个MP3。这MP3能在各个播放器上正常播放。
很顺利的解析出来的pcm只有400k大小。显然是失败了,通常44100的Mp3解析出来的pcm是原来文件的2-4倍大小。解析过程一直报这个问题:(n行)。
htp: bitstream problem, resyncing skipping xxxx byte...
网上找了一圈也没找到缘由,没办法只有啃源代码,再加一点点的揣测。
大概的意思就是找到下一帧帧头,以前跳过了多少个字符数据。若是说MP3跳过一两次,百来个字符,那都算正常。
可是大佬给个人MP3解析的时候几乎跳过了99%的字符。显然是解析失败了。
这个地方卡了我一成天。剖析源代码。分析MP3格式。
在网上查了不少资料,获得一个结论。
在shell中直接经过命令lame -decode 将MP3转WAV时是能正常将上述的有问题的MP3转化的。由于,命令行的入口函数调用的方法就不是hip_decode_init() -> hip_decode_header() -> hip_decode() -> hip_decode_exit()这个过程。
而是采用另外一套流程。这里我就直接将相关的代码列出来:

#include <lame.h> #include <timestatus.h> #include <set_get.h> #include <get_audio.h> #include <lame_reader_def.h>
//定义变量 lame_t m_lame_gfp; DecoderProgress m_dp; int sampleRate; //采样率 int channelCount; //通道数 ///初始化阶段 int lame_err = 0; m_lame_gfp = lame_init(); if(m_lame_gfp == NULL){ //LOGE("初始化失败。"); return -1; } lame_set_write_id3tag_automatic(m_lame_gfp,0); lame_set_out_samplerate(m_lame_gfp,441000); //设置输出的pcm 的采样率 lame_set_decode_only(m_lame_gfp,1); lame_err = init_infile(m_lame_gfp,"voice.mp3"); //初始化读取文件 if(lame_err < 0){ //LOGE("初始化失败。"); return -1; } lame_err = lame_init_params(m_lame_gfp); if(lame_err < 0){ //LOGE("fail"); return -1; } /** set_get.c中包含了各种获取MP3音频信息的函数,直接调用便可。下面就是获取采样率和通道数的函数。 能够查看源代码中还有获取其余信息的函数。 */ sampleRate = lame_get_out_samplerate(m_lame_gfp); channelCount = lame_get_num_channels(m_lame_gfp); //解析过程 static FILE *output_file = fopen(output.pcm,"W+"); short int pcm_buffer[2][1152]; int iread = 0; unsigned char data[8192]; //存放解析以后的pcm /** 这里通常是以帧为单位,一次循环输出一帧数据。 */ do{ iread = get_audio16(m_lame_gfp,pcm_buff); if(m_dp != NULL){ decoder_progress(m_dp,&global_decode.mp3input,iread); } if(lame_get_num_channels(m_lame_gfp) !=2 ){ memcpy(data,pcm_buffer[0],iread); }else{ unsigned int j = 0; for(int i = 0;i < iread ; i++ , j += 4){ memcpy(data + j , pcm_buffer[0] + i , 2); memcpy(data + j + 2, pcm_buffer[1] + i , 2); }; } // 这里就能够将data中的数据写入文件中,或者输入至播放设备中。 fwrite(data,sizeof(short)*m_mp3dtat.stereo,sample,output_file); }while(iread > 0); close_infile(); //关闭文件 if(output_file){ fclose(output_file); } if(m_dp){ decoder_progress_finish(m_dp); m_dp = NULL; } if(m_lame_gfp){ lame_close(m_lame_gfp); m_lame_gfp = NULL; }