C语言解析MP3数据结构
本章博客编写的初始原因:MP3一般有CBR和VBR两种格式,其中,正常的VBR会把总帧数记录在MP3数据帧的第一帧中,从而利用总帧数计算MP3的总时间。但是,偶尔会遇到一些mp3文件,它是VBR格式,却没有在第一个数据帧记录总帧数。这一类mp3文件是获取不了正确的总时间的,比如Windows Media Player,Kugou等播放器都是无法获取正确总时间的;为了获取总帧数,我决定去遍历整个数据,统计总帧数;
这篇博客的代码,只需稍作修改,就可以计算总帧数和平均bitrate;
在看这边文章之后,希望你已经对MP3的数据结构有一定了解,可以先搜索《MP3详细解析》,看一下其他人的博客,熟悉MP3结构之后再来看这边文章;
以下是MP3数据帧Header的结构,在其他博客中都会看到这个表格,我的代码就是研究这个表格之后,整理编写;
1.mp3数据帧的头
2.根据Layer和Mpeg获得数据帧长度
先大概说明一下以下代码的功能:先解析mp3数据的前3帧,如果前3帧的bitrate相同,说明这确实是一个CBR格式MP3,退出此函数;如果前3帧的bitrate不同,则解码前100帧数据,统计总帧数,计算平均bitrate;
代码中会使用到的定义:
static const long table_BitRate_V1L1[15] = {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448};
static const long table_BitRate_V1L2[15] = {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384};
static const long table_BitRate_V1L3[15] = {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320};
static const long table_BitRate_V2L1[15] = {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256};
static const long table_BitRate_V2L2L3[15] = {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160};
static const long *table_BitRate_V1[4] = {0, table_BitRate_V1L3, table_BitRate_V1L2, table_BitRate_V1L1};
static const long *table_BitRate_V2[4] = {0, table_BitRate_V2L2L3, table_BitRate_V2L2L3, table_BitRate_V2L1};
static const long **table_BitRate[4] = {table_BitRate_V2, 0, table_BitRate_V2, table_BitRate_V1};
static const long table_SampleRate_V1[3] = {44100,48000,32000};
static const long table_SampleRate_V2[3] = {22050,24000,16000};
static const long table_SampleRate_V25[3] = {11025,12000,8000};
static const long *table_SampleRate[4] = {table_SampleRate_V25, 0, table_SampleRate_V2, table_SampleRate_V1};
static const INT32U table_MpegVersion[4] = {25,0,2,1};
#define EQUAL_MASK 0xFFFE0CC0
#define LAYER_III 1
#define LAYER_II 2
#define LAYER_I 3
#define MPEG_V25 0
#define MPEG_V2 2
#define MPEG_V1 3
typedef struct
{
INT8U emphasis : 2;
INT8U original : 1;
INT8U copyright : 1;
INT8U ext : 2;
INT8U channel : 2;
INT8U priv : 1;
INT8U padding : 1;
INT8U samplerate : 2;
INT8U bitrate : 4;
INT8U protection : 1;
INT8U layer : 2;
INT8U mpeg : 2;
INT16U sync : 11;
}MP3_SYNC_WORD;
typedef union//struct
{
MP3_SYNC_WORD sync_word;
INT32U word;
INT8U ch[4];
} MP3_SYNC_UNION;
代码主体:
说明:函数有两个输入参数,一个是MP3的文件句柄;一个是数据帧在文件中的偏移位置,即ID3V2之后的位置;
#define CHECK_BUF_SIZE 2048 //ring buf size
#define VBR_STATISTICS_NUM 100 //if the bitrate of front 3 frame is not same,statistics next (100-3) frame and get average bitrate
#define STATISTICS_BUF_SIZE 2048*10 //the bigger size of read file each time
//function name : audio_mp3_VBR_check_again
//input parameter: fin the mp3 file handle
// ID3V2_length the len of mp3 ID3V2
//return value : 0 CBR
// >0 the average bitrate of front 100 frame
// -1 error
static INT32U audio_mp3_VBR_check_again(INT16S fin,INT32U ID3V2_length)
{
INT32U total_frame=0;
INT32U bitrate_average = 0;
INT32U buf=0;
INT32U buf_size=0;
INT32U position=0;
INT8U ret=0;
INT8U check_num = 3;
INT32U VBR_flag = 0;
INT32S real_read = 0;
INT32U offset;
INT8U i;
MP3_SYNC_UNION mp3_file_head[2];
INT8U *p_audio_file_head;
INT32U BitRate;
INT32U BitRate2[check_num];
INT32U SampleRate;
INT32U Padding;
INT32U FrameLengthInBytes=0;
buf_size = CHECK_BUF_SIZE;
buf = (INT32U)gp_malloc_align(buf_size,4);
if(!buf)
{
gp_free((void*)buf);
return -1;
}
p_audio_file_head = (INT8U*)buf;
mp3_file_head[0].word = 0;
position = 0;//effect : Position to each frame header position in file and buf
//Parse the header of 3 frames,whether the bitrate of each frame is same
while(check_num--)
{
p_audio_file_head = (INT8U*)buf;
lseek(fin, ID3V2_length+position, SEEK_SET);
buf_size = CHECK_BUF_SIZE;
real_read = fs_read(fin,buf,buf_size);
mp3_file_head[0].ch[0] = *(p_audio_file_head);
//此处先统计前3帧数据的bitrate
while (real_read > 4)
{
//while循环就是根据以上表格编写的,主要是解析前每帧的前4个Bytes
if( mp3_file_head[0].sync_word.sync == 0x07FF &&
mp3_file_head[0].sync_word.mpeg != 1 &&
mp3_file_head[0].sync_word.layer != 0 &&
mp3_file_head[0].sync_word.layer != 3 &&
mp3_file_head[0].sync_word.bitrate != 0 &&
mp3_file_head[0].sync_word.bitrate != 15 &&
mp3_file_head[0].sync_word.samplerate != 3)
{
BitRate = table_BitRate[mp3_file_head[0].sync_word.mpeg][mp3_file_head[0].sync_word.layer][mp3_file_head[0].sync_word.bitrate] * 1000;
SampleRate = table_SampleRate[mp3_file_head[0].sync_word.mpeg][mp3_file_head[0].sync_word.samplerate];
Padding = mp3_file_head[0].sync_word.padding;
if(mp3_file_head[0].sync_word.mpeg == MPEG_V1)
FrameLengthInBytes = 144 * BitRate / SampleRate + Padding; // 144 = 1152/8
else
FrameLengthInBytes = 72 * BitRate / SampleRate + Padding;
//根据计算得到的帧长度FrameLengthInBytes,计算本帧长度,之后偏移到下一帧帧头
position = position + FrameLengthInBytes - 4;
BitRate2[check_num] = BitRate;
mp3_file_head[0].word = 0;
bitrate_average += BitRate;
total_frame++;
break;
}
p_audio_file_head ++;
position ++;
real_read -- ;
mp3_file_head[0].word <<= 8;
mp3_file_head[0].ch[0] = *(p_audio_file_head);
}
}
//Whether the bitrate of each frame is same
if(BitRate2[0]==BitRate2[1] && BitRate2[1]==BitRate2[2])
VBR_flag = 0;
else
VBR_flag = 1;
//If it's in VBR format, parse each frame to get the total number of frames
if(VBR_flag)
{
gp_free((void*)buf);
buf_size = STATISTICS_BUF_SIZE;
buf = (INT32U)gp_malloc_align(buf_size,4);
p_audio_file_head = (INT8U*)buf;
lseek(fin, ID3V2_length+position, SEEK_SET);
real_read = fs_read(fin,buf,buf_size);
mp3_file_head[0].ch[0] = *(p_audio_file_head);
ret = 1;
//开始统计前100帧的bitrate
//此处我写也两个while(),外循环是我备用做其他功能的,统计数据看内循环while
while(ret)
{
while (real_read > 4)
{
if( mp3_file_head[0].sync_word.sync == 0x07FF &&
mp3_file_head[0].sync_word.mpeg != 1 &&
mp3_file_head[0].sync_word.layer != 0 &&
mp3_file_head[0].sync_word.layer != 3 &&
mp3_file_head[0].sync_word.bitrate != 0 &&
mp3_file_head[0].sync_word.bitrate != 15 &&
mp3_file_head[0].sync_word.samplerate != 3)
{
BitRate = table_BitRate[mp3_file_head[0].sync_word.mpeg][mp3_file_head[0].sync_word.layer][mp3_file_head[0].sync_word.bitrate] * 1000;
SampleRate = table_SampleRate[mp3_file_head[0].sync_word.mpeg][mp3_file_head[0].sync_word.samplerate];
Padding = mp3_file_head[0].sync_word.padding;
if(mp3_file_head[0].sync_word.mpeg == MPEG_V1)
FrameLengthInBytes = 144 * BitRate / SampleRate + Padding; // 144 = 1152/8
else
FrameLengthInBytes = 72 * BitRate / SampleRate + Padding;
position = position + FrameLengthInBytes - 4;
real_read = real_read - FrameLengthInBytes + 4;
p_audio_file_head = p_audio_file_head + FrameLengthInBytes - 4;
bitrate_average += BitRate;
mp3_file_head[0].word = 0;
total_frame++;
if(total_frame>=100) //将此处if注释掉的话,就可以遍历所有数据帧
break;
}
if(real_read < 5) //Read the file, and Protect the last four bytes of buf
{
offset = 0;
buf_size = STATISTICS_BUF_SIZE;
p_audio_file_head = (INT8U*)buf;
if(real_read > 0) //Protect the last four bytes of buf
{
offset = real_read;
buf_size = STATISTICS_BUF_SIZE - real_read;
for(i=0;i<offset;i++) //deal the last four bytes
{
*(p_audio_file_head+i) = *(p_audio_file_head+STATISTICS_BUF_SIZE-offset+i);
}
}
lseek(fin, ID3V2_length+position, SEEK_SET);
real_read = fs_read(fin,buf+offset,buf_size);
if(real_read < 1024)
break;
}
p_audio_file_head ++;
real_read --;
position ++;
mp3_file_head[0].word <<= 8;
mp3_file_head[0].ch[0] = *(p_audio_file_head);
}
ret = 0;
}
}
else //CBR
{
total_frame = 0;
bitrate_average = 0;
}
lseek(fin, ID3V2_length+RING_BUF_SIZE/2, SEEK_SET);
gp_free((void*)buf);
if(total_frame==0)
return -1;
return bitrate_average/total_frame;
}
不知道你是否有耐心把上面的代码研究看懂,看不懂我也没办法了哈!!!
其实就是不断的解析数据帧的前4个bytes,得到bitrate和帧长之后,就往后偏移帧长,之后解析下一帧帧头,一直到整个文件读完,或者读够100帧;
以上代码,做适当修改,就可以统计MP3文件的总帧数,也可统计整个文件的bitrate之和,就可以按CBR或VBR的方式计算总时间了;
修改如下:
1.将第二次统计中的 if(total_frame>=100) 判断注释掉,即可统计所有数据帧,得到总帧数;
2.在第一次判断前三帧Bitrate是否相同时,如果想多判断几帧,修改check_num的值就可以了;
看完代码之后会不会豁然开朗呢?网上的MP3解析各式各样,没有代码看解析,都是懵懵懂懂,结合解析看代码,是不是发现MP3的数据结构就清楚?
编写代码不易,请给个赞!!!