通過這幾天查資料,翻代碼。這裏總結一下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()這個過程。
而是採用另外一套流程。這裏我就直接將相關的代碼列出來:
![](http://static.javashuo.com/static/loading.gif)
#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; }