经过这几天查资料,翻代码。这里总结一下lame解析MP3的过程以及遇到问题。

开发环境是Linux下c++开发

使用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(......)方法。

点进去看一下就知道其实他们都是对hip_decode1_headerB()方法的封装,只不过前者需要提供mp3data_struct结构体指针。方便将mp3的数据格式读取出来,做一些前期工作。后者只是直接就开始解析了。

 

源代码在公司内网中,取不出来,所以以下代码都是手敲复制出来的,可能会拼写错。别指望照搬过去就能直接运行,看懂先。

#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;
}

 

Logo

更多推荐