写点什么

Mp3 文件结构全解析 (二)

用户头像
轻口味
关注
发布于: 2 小时前
Mp3文件结构全解析(二)

上一篇 Mp3 文件结构全解析(一)中分析了 MP3 的 Tag 解析,这篇接着分析 MP3 的音频内容解析

音频数据解析

每个 FRAME 都有一个帧头 FRAMEHEADER,长度是 4BYTE(32bit),帧头后面可能有两个字节的 CRC 校验,这两个字节的是否存在决定于 FRAMEHEADER 信息的第 16bit, 为 0 则帧头后面无校验,为 1 则有校验,校验值长度为 2 个字节,紧跟在 FRAMEHEADER 后面,接着就是帧的实体数据了,格式如下:



帧头 FRAMEHEADER 格式

帧头长 4 字节,结构如下:


typedef FrameHeader
{
unsigned int sync:11; //同步信息
unsigned int version:2; //版本
unsigned int layer: 2; //层
unsigned int error protection:1; // CRC校验
unsigned int bitrate_index:4; //位率
unsigned int sampling_frequency:2; //采样频率
unsigned int padding:1; //帧长调节
unsigned int private:1; //保留字
unsigned int mode:2; //声道模式
unsigned int mode extension:2; //扩充模式
unsigned int copyright:1; // 版权
unsigned int original:1; //原版标志
unsigned int emphasis:2; //强调模式
}HEADER, *LPHEADER;
复制代码


详细说明:


帧长度与帧大小

帧大小即每帧的采样数,表示一帧数据中采样的个数,该值是恒定的,如下表所示:


帧长度是压缩时每一帧的长度,包括帧头的 4 个字节。它将填充的空位也计算在内。Layer 1 的一个空位长 4 字节,Layer 2 和 Layer 3 的空位是 1 字节。当读取 MPEG 文件时必须计算该值以便找到相邻的帧。注意:因为有填充和比特率变换,帧长度可能变化


计算公式如下:


  • Layer 1:Len(字节) = ((每帧采样数/8 比特率)/采样频率)+填充 4

  • Layer2/3:Len(字节) = ((每帧采样数/8*比特率)/采样频率)+填充


例:MPEG1 Layer3 比特率 128000,采样率 44100,填充 0,帧长度为:((1152/8*128K)/44.1K+0=417 字节

帧持续时间

计算公式:


每帧持续时间(毫秒) = 每帧采样数 / 采样频率 * 1000


例:1152/441000*1000=26ms

帧数据

在帧头后边是 Side Info(姑且称之为通道信息)。对标准的立体声 MP3 文件来说其长度为 32 字节。当解码器在读到上述信息后,就可以进行解码了。


对于 mp3 来说现在有两种编码方式,一种是 CBR,也就是固定位率,固定位率的帧的大小在整个文件中都是是固定的(公式如上所述),只要知道文件总长度,和从第一帧帧头读出的信息,就都可以通过计算得出这个 mp3 文件的信息,比如总的帧数,总的播放时间等等,要定位到某一帧或某个时间点也很方便,这种编码方式不需要文件头,第一帧开始就是音频数据。另一种是 VBR,就是可变位率,VBR 是 XING 公司推出的算法,所以在 MP3 的 FRAME 里会有“Xing"这个关键字(也有用"Info"来标识的,现在很多流行的小软件也可以进行 VBR 压缩,它们是否遵守这个约定,那就不得而知了),它存放在 MP3 文件中的第一个有效帧的数据区里,它标识了这个 MP3 文件是 VBR 的。同时第一个帧里存放了 MP3 文件的帧的总个数,这就很容易获得了播放总时间,同时还有 100 个字节存放了播放总时间的 100 个时间分段的帧索引,假设 4 分钟的 MP3 歌曲,240S,分成 100 段,每两个相邻 INDEX 的时间差就是 2.4S,所以通过这个 INDEX,只要前后处理少数的 FRAME,就能快速找出我们需要快进的帧头。其实这第一帧就相当于文件头了。不过现在有些编码器在编码 CBR 文件时也像 VBR 那样将信息记入第一帧,比如著名的 lame,它使用"Info"来做 CBR 的标记。

VBR 头文件

VBR 文件头位于 MP3 文件中第一个有效帧的数据区,详细结构如下:

实现一个数据解析器

主要步骤:


  1. 读取一个字节,判断是否是 FF

  2. 读取一个字节与上 0xF0,判断是否等于 0xF0 或者 0xE0

  3. 如果是说明找到一帧音频,再依次读取两个字节中计算位率,采样率等信息

  4. 通过公式计算音频内容大小:(144 * (float)nFrameBitRate /(float)nFrameSamplingFrequency ) + nFramePadded

  5. 跳过音频内容大小个字节,继续读取下一帧.


具体代码实现:


position = lseek(fd, tagsize-10, 0);  printf("seek to get audio frame , position = %d\n", position);
int nFrames, nFileSampleRate; unsigned char ucHeaderByte1, ucHeaderByte2, ucHeaderByte3, ucHeaderByte4; float fBitRateSum=0;syncWordSearch: while( position < file_size) { if (read(fd, &ucHeaderByte1, sizeof(ucHeaderByte1)) < 0) { perror("Read File: "); exit(1); } position ++; //printf("111:%d\n", ucHeaderByte1); if( ucHeaderByte1 == 0xFF ) { if (read(fd, &ucHeaderByte2, sizeof(ucHeaderByte2)) < 0) { perror("Read File: "); exit(1); } position ++; unsigned char ucByte2LowerNibble = ucHeaderByte2 & 0xF0; if( ucByte2LowerNibble == 0xF0 || ucByte2LowerNibble == 0xE0 ) { ++nFrames; printf("Found frame %d at offset = %ld B\n",nFrames, position); //printf("Header Bits:\n"); //get the rest of the header: if (read(fd, &ucHeaderByte3, sizeof(ucHeaderByte3)) < 0) { perror("Read File: "); exit(1); } position ++; if (read(fd, &ucHeaderByte4, sizeof(ucHeaderByte4)) < 0) { perror("Read File: "); exit(1); } position ++; //print the header: //printBits(sizeof(ucHeaderByte1),&ucHeaderByte1); //printBits(sizeof(ucHeaderByte2),&ucHeaderByte2); //printBits(sizeof(ucHeaderByte3),&ucHeaderByte3); //printBits(sizeof(ucHeaderByte4),&ucHeaderByte4); //get header info: int nFrameSamplingFrequency = findFrameSamplingFrequency(ucHeaderByte3); int nFrameBitRate = findFrameBitRate(ucHeaderByte3); int nMpegVersionAndLayer = findMpegVersionAndLayer(ucHeaderByte2);
if( nFrameBitRate==0 || nFrameSamplingFrequency == 0 || nMpegVersionAndLayer==0 ) {//if this happens then we must have found the sync word but it was not actually part of the header --nFrames; printf("Error: not a header\n\n"); goto syncWordSearch; } fBitRateSum += nFrameBitRate; if(nFrames==1){ nFileSampleRate = nFrameSamplingFrequency; } int nFramePadded = findFramePadding(ucHeaderByte3); //calculate frame size: int nFrameLength = (144 * (float)nFrameBitRate / (float)nFrameSamplingFrequency ) + nFramePadded; printf("\tFrame Length: %d Bytes \n\n", nFrameLength);
//lnPreviousFramePosition=ftell(ifMp3)-4; //the position of the first byte of this frame
//move file position by forward by frame length to bring it to next frame: position = lseek(fd, position + nFrameLength-4, 0); } } } float fFileAveBitRate= fBitRateSum/nFrames; printmp3details(nFrames,nFileSampleRate,fFileAveBitRate);
复制代码


很多文章都是抄来抄去,犯了很多相同的错误,遇到问题,最权威的还是官方文档,需要很耐心的阅读学习,边阅读边实现,加深自己的理解和记忆.


示例代码地址:git@github.com:qingkouwei/mp3parser.git

发布于: 2 小时前阅读数: 2
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android音视频、AI相关领域从业者

评论

发布
暂无评论
Mp3文件结构全解析(二)