1.MP3编码格式
1.1.MP3概述
MPEG-1 or MPEG-2 Audio Layer III是一种音频压缩技术,其全称是动态影像专家压缩标准音频层面3(Moving Picture Experts Group Audio Layer III),简称为MP3,是目前最流行的音频编码格式。
MP3文件是由帧(frame)构成的,帧是MP3文件最小的组成单位。MPEG音频文件是MPEG1标准中的声音部分,也叫MPEG音频层,它根据压缩质量和编码复杂程度划分为三层,即 Layer-1、Layer2、Layer3,且分别对应MP1、MP2、MP3这三种声音文件,并根据不同的用途,使用不同层次的编码。MPEG音频编码的层次越高,编码器越复杂,压缩率也越高,MP1和MP2的压缩率分别为4:1和6:1-8:1,而MP3的压缩率则高达10:1-12:1,也就是说,一分钟CD音质的音乐,未经压缩需要10MB的存储空间,而经过MP3压缩编码后只有1MB左右。不过MP3对音频信号采用的是有损压缩方式,为了降低声音失真度,MP3采取了“感官编码技术”,即编码时先对音频文件进行频谱分析,然后用过滤器滤掉噪音电平,接着通过量化的方式将剩下的每一位打散排列,最后形成具有较高压缩比的MP3文件,并使压缩后的文件在回放时能够达到比较接近原音源的声音效果。根据MPEG规范的说法,MPEG-4中的AAC(Advanced audio coding)将是MP3格式的下一代。
1.2.MPEG音频压缩基础
在众多音频压缩方法中,这些方法在保持声音质量的同时尽量压缩数字音频使之占用更小的存储空间。MPEG压缩是该领域中效果最好的一个。这种压缩是有损压缩,这意味着,当运用这一方法压缩时肯定会丢失一部分音频信息。但是,由于压缩方法的控制很难发现这种损失。使用几个非常复杂和苛刻的数学算法,使得只有原始音频中几乎听不到的部分损失掉。这就给重要的信息剩下了更多的空间。通过这种方法可以将音频压缩12倍(可以选择压缩率),效果显著。正是应为他的质量,MPEG音频变得流行起来。
MPEG-1,MPEG-2和MPEG-4都是人们熟悉的MPEG标准,MP3只涉及到前两中,另外还有一个非官方标准MPEG-2.5用于扩展MPEG-2/LSF到更低的采样率。
MPEG-1音频(ISO/IEC 11172-3)描述了具有如下属性的三层音频编码:
1或2个声道;
采样频率为32kHz,44.1kHz或48kHz;
位率从32kbps到448kbps;
每一层都有自己的优点。
MPEG-2音频(ISO/IEC 13818-3)有两个MPEG-1的扩展,通常叫做MPEG-2/LSF和MPEG-2/Multichannel。
MPEG-2/LSF有如下特点:
1或2个声道;
采样频率为MPEG-1的一半;
波特率从8kbps256kbps;
MPEG-2/Mutichannel有如下特点:
多达5个声道和1个LFE-通道(低频增强 不是重低音);
同MPEG-1一样的采样频率;
5.1的最高波特率可能达到1Mbps;
1.3.MPEG Layer3编/解码的基本原理
音乐CD具有44.1KHz 16Bits立体声的音频质量,一张CD可以存储74分钟的歌曲(大约15首左右)。如何将这些歌曲无损或基本无损地进行压缩,以使在同样的媒体上存储更多的歌曲,一直困扰着软件业。当MPEG协会提出MPEG Audio Layer1~Layer3后,机会产生了。通过使用MPEG1 Layer3编码技术,制作者得以用大约12∶1的压缩率记录16KHz带宽的有损音乐信号。不过,同CD原声区别不大。人的听力系统具有非常优越的性能,其动态范围超过96dB。你既可以听到扣子掉在地上这样小的声音,也可以听到波音747的强大的轰鸣声。但当我们站在飞机场听着波音747的轰鸣时,你还能分辨出扣子掉在地上的声音吗?不可能。人的听力系统适应声音的动态变化,人们对这种适应及屏蔽特性音质研究后得出对声音压缩非常有用的理论。人们很早以前就知道利用这种特性来为磁带录音降低噪音了(当没有音乐时嘶嘶声很容易听到,而当音乐信号电平很高时嘶嘶声不容易听到)。当声音较强时产生屏蔽效应。在阈值曲线下的噪音或小信号声音无法被人耳听到。在较强信号出现时,允许通过更多的信号。在此时增加被量化过的小信号数据(使用无用的位来携带更多的信息)可以达到一定程度的压缩的目的。通常情况下,MP3压缩器将原始声音通过FFT(快速傅立叶变换)变化到频域,然后通过一定的算法算出何种频率声音可以携带更多的信息。而在还原时解码器所需要做的仅仅是将其从频域再变换回来。
1.4.整个MP3文件结构
MP3文件大体分为三部分:TAG_V2(ID3V2),音频数据,TAG_V1(ID3V1)
表格 1-1
1.4.1.ID3V2
ID3V2到现在一共有4个版本,但流行的播放软件一般只支持第3版,即ID3v2.3。由于ID3V1记录在MP3文件的末尾,ID3V2就只好记录在MP3文件的首部了(如果有一天发布ID3V3,真不知道该记录在哪里)。也正是由于这个原因,对ID3V2的操作比ID3V1要慢。而且ID3V2结构比ID3V1的结构要复杂得多,但比前者全面且可以伸缩和扩展。
下面就介绍一下ID3V2.3:
每个ID3V2.3的标签都由一个标签头和若干个标签帧或一个扩展标签头组成。关于曲目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必要的,但每个标签至少要有一个标签帧。标签头和标签帧一起顺序存放在MP3文件的首部。
(一)标签头
在文件的首部顺序记录10个字节的ID3V2.3的头部。数据结构如下:
char Header[3]; //必须为"ID3"否则认为标签不存在
char Ver; //版本号 ID3V2.3就记录3
char Revision; //副版本号 此版本记录为0
char Flag; //存放标志的字节,这个版本只定义了三位,稍后详细解说
char Size[4]; //标签大小,包括标签头的10 个字节和所有的标签帧的大小
注:对这里我有疑惑,因为在实际寻找首帧的过程中,我发现有的mp3文件的标签大小是不包含标签头的,但有的又是包含的,可能是某些mp3编码器写标签的BUG,所以为了兼容只好认为其是包含的,如果按大小找不到,再向后搜索,直到找到首帧为止。
图 1-1
蓝色部分即为ID3V2.3的头部:前4个字节就是ID30x03(第3版)
第5个字节:副版本号,为0
注:文中关于mp3文件数据截图均为“紫藤花.mp3”文件中的数据截图。
(1)标志字节
标志字节一般为0,定义如下:
abc00000
a – 表示是否使用Unsynchronisation(一般不设置)
b – 表示是否有扩展头部,一般没有(至少Winamp 没有记录),所以一般也不设置
c – 表示是否为测试标签(99.99%的标签都不是测试用的啦,所以一般也不设置)
上图蓝色部分第6个字节:存放标志的字节,只定义了三位,这里值为0。
(2)标签大小
一共四个字节,但每个字节只用7位,最高位不使用恒为0。所以格式如下:
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
计算大小时要将0去掉,得到一个28位的二进制数,就是标签大小,计算公式如下:
int total_size;
total_size = (Size[0]&0x7F)*0x200000 + (Size[1]&0x7F)*0x4000 + (Size[2]&0x7F)*0x80 + (Size[3]&0x7F)
上图蓝色部分第7到10字节:表示ID3标签的大小,这里为
total_size =(0x00&0x7F)*0x200000 + (0x00&0x7F)*0x4000 + (0x10&0x7F)*0x80 + (0x72 &0x7F) = 0x872 = 2162
(二)标签帧
每个标签帧都由一个10个字节的帧头和至少一个字节的不固定长度的内容组成。它们也是顺序存放在文件中,和标签头及其他的标签帧也没有特殊的字符分隔。得到一个完整的帧的内容只有从帧头中得到内容大小后才能读出,读取时要注意大小,不要将其他帧的内容或帧头读入。
帧头的定义如下:
char ID[4]; /用四个字符标识一个帧,说明其内容,稍后有常用的标识对照表/
char Size[4]; /帧内容的大小,不包括帧头,不得小于1/
char Flags[2]; /存放标志,只定义了6位,稍后详细解说/
图 1-2
蓝色部分是一个歌曲标题的标签帧
(1)帧标识
用四个字符标识一个帧,说明一个帧的内容含义,常用的对照如下:
TIT2 = 标题 //表示内容为这首歌的标题,下同
TPE1=作者
TALB=专集
TRCK=音轨 //格式:N/M其中N 为专集中的第N首,M为专集中共M首,N和M为ASCII码表示的数字
TYER=年代 //是用ASCII 码表示的数字
TCON=类型 //直接用字符串表示
COMM=备注 //格式:“eng\0备注内容”,其中eng表示备注所使用的自然语言
注:前4个字节为帧标识,这里是54 49 54 32(TIT2)是标题的标签帧。
文章最后扫码进qun,可以免费领取音视频学习资料。资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)
表1-2 标签帧的标识以及其意义对照表
表1-2 标签帧的标识以及其意义对照表
(2)大小
这个可没有标签头的算法那么麻烦,每个字节的8 位全用,格式如下
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
算法如下:
int FSize;
FSize = Size[0]*0x100000000 + Size[1]*0x10000+ Size[2]*0x100 + Size[3];
第5到8字节为标签帧的大小,这里为
FSize = 0x00*0x100000000+ 0x00*0x10000 + 0x00*0x100 + 0x24 = 0x24 = 36
注意:这里的帧大小,并不包含帧头的10个字节,只表示帧内容的大小。
(3)标志
只定义了6位,另外的10位为0,但大部分的情况下16位都为0就可以了。格式如下:abc00000ijk00000
a – 标签保护标志,设置时认为此帧作废
b – 文件保护标志,设置时认为此帧作废
c – 只读标志,设置时认为此帧不能修改
i – 压缩标志,设置时一个字节存放两个BCD码表示数字
j – 加密标志
k – 组标志,设置时说明此帧和其他的某帧是一组
值得一提的是winamp在保存和读取帧内容的时候会在内容前面加个’’\0’’,并把这个字节计算在帧内容的大小中。
第9到10字节为标签帧的标记,如上所述,这里为00。
帧内容是歌曲标题,标题的36个字节的内容为:紫藤花☆ゞ忍音.地带[renyin.12u.cn]。
1.4.2.音频数据帧
每个帧都有一个帧头Header,长度是4Byte(32bit),帧头后面可能有两个字节的CRC 校验值,这两个字节的校验值是否存在决定于Header信息的第16bit,为0则帧头后面无校验,为1则有校验,校验值长度为2个字节,紧跟在Header后面的就是帧的实体数据,也就是压缩的声音数据,当解码器读到此处时就进行解码了。格式如下:
(1)帧头格式
帧头长4字节,对于固定位率的MP3文件,所有帧的帧头(即CBR帧头)格式一样,其数据结构如下(注:此结构要自己定义):
typedef struct 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; //强调模式
}FHEADER, *LPHEADER;
表1-3 MP3帧头字节使用说明
注意:关于读取帧头也可以使用下面的方法
定义一个结构体
typedef struct frameHeader
{
unsigned int sync1:8; //同步信息1
unsigned int error_protection:1; //CRC校验
unsigned int layer:2; //层
unsigned int version:2; //版本
unsigned int sync2:3; //同步信息2
unsigned int extension:1; //版权
unsigned int padding:1; //填充空白字
unsigned int sample_rate_index:2; //采样率索引
unsigned int bit_rate_index:4; //位率索引
unsigned int emphasis:2; //强调方式
unsigned int original:1; //原始媒体
unsigned int copyright:1; //版权标志
unsigned int mode_extension:2; //扩展模式,仅用于联合立体声
unsigned int channel_mode:2; //声道模式
}FHEADER, *pFHEADER;
请注意该结构体将同步信息分成了两个部分,而且其他的位的顺序也和上表列出的有所差别,这个主要是因为c语言在存取数据时总是从低位开始,而这个帧头是需要从高位来读取的。
读取方式如下:
FHEADER header;
fread( &header, sizeof( FHEADER ), 1, streams ); //这里假设文件已打开,读取位置已经指向帧头所在的位置
这样一次就可以读入帧头的所有信息了。
第1帧数据:帧头为FF FB 90 04
11111111 11111011 10010000 00000010
前11位:同步信息
第12到13位:版本,其值为11 ->MPEG 1
第14到15位:层,其值为01->Layer 3
第16位:CRC检验标识,其值为1 ->不校验
第17到20位:位率,其值为1001,从前面可知本帧为V1,L3类型, ->128K
第21到22位:采样率,其值为00,从前面知本帧为 MPEG 1-> 44.1K
第23位:帧长调节,其值为0 ->无需调整
第24位:未使用
第25到26位:声道模式,其值为00->立体声Stereo
第27到28位:扩充模式,当声道模式为01(联合立体声)时才使用,此处未使用
第29位:版权,其值为0->无版权
第30位:原版标志,其值为1->是原版文件
第31到32位:强调方式,其值为00->未定义
(2)计算帧长度
我们首先区分两个术语:帧大小和帧长度。帧大小即每帧采样数表示一帧中采样的个数,这是恒定值。其值如下表所示
帧长度是压缩时每一帧的长度,包括帧头。它将填充的空位也计算在内。LayerI的一个空位长4字节,LayerII和LayerIII的空位是1字节。当读取MPEG文件时必须计算该值以便找到相邻的帧。注意:因为有填充和比特率变换,帧长度可能变化。
从头中读取比特率,采样频率和填充的值后可以进行计算,
LyaerI使用公式:
帧长度(字节) = (( 每帧采样数/ 8 * 比特率 ) / 采样频率 ) + 填充 * 4
LyerII和LyaerIII使用公式:
帧长度(字节)= (( 每帧采样数/ 8 * 比特率 ) / 采样频率 ) + 填充
例如图1-3中,比特率为128K,采样率为44.1K,填充0,则其帧长度为:
(1152 / 8 * 128K)/44.1K = 417 (字节)
(3)每帧的持续时间
每帧的持续时间可以通过计算获得,下面给出计算公式
每帧持续时间(毫秒) = 每帧采样数 / 采样频率 * 1000ms
如图1-3中,其每帧时间为:
1152 / 44.1K * 1000 = 26.12 (约等于26ms)
如果是MPEG2 Layer III 采样率为16KHz的话那一帧要持续36毫秒,这个相差还是蛮大的,所以还是应该通过计算来获的。
(4)CRC校验
如果帧头的校验位为0,则帧头后就有一个16位的CRC值,这个值是big-endian的值,把这个值和该帧通过计算得出的CRC值进行比较就可以得知该帧是否有效。
(5)帧数据
在帧头后边是Side Info(姑且称之为通道信息)。对标准的立体声MP3文件来说其长度为32字节。通道信息后面是Scale factor(增益因子)信息。当解码器在读到上述信息后,就可以进行解码了。对应图1-3中地址0x880到0x89F(含),此处数据全为0。当MP3文件被打开后,播放器首先试图对帧进行同步,然后分别读取通道信息及增益因子等数据,再进行霍夫曼解码,至此我们已经获得解压后的数据。但这些数据仍然不能进行播放,它们还处于频域,要想听到歌曲还要将它由频域通过特定的手段转换到时域。接下来的处理分别为立体化处理;抗锯齿处理;IMDCT变换;IDCT变换及窗口化滑动处理。
对于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的标记。
CBR总播放时长计算公式:
播放时长 = (文件大小 – ID3标签大小) * 8 / 比特率
VBR总播放时长计算公式:
播放时长 = 有效数据帧总帧数 * 每帧采样数 / 采样率
(6)VBR头
这里列出VBR的第一帧存储文件信息的头的格式。有两种格式,一种是常见的XINGHeader(头部包含字符‘Xing’),另一种是VBRIHeader(头部包含字符‘VBRI’)鉴于VBRIHeader不常见,下面只说XINGHeader。VBR的第一帧不包含声音数据,其长度是156个字节,用来存放标准的声音帧头(4字节)、VBR文件标识、帧数、文件字节数等信息,具体结构说明见表1-4。
表1-4 VBR文件第一帧结构
在VBR格式的第一帧中,XING Header包括帧头一共最多只需要156个字节就够了,当然也可以在XING Header后面存储编码器的信息,比如lame在其后就是存储其版本,这需要给第一帧留足够的空间才行。
至于mp3的信息用从XING Header读出的信息就可以计算,比如:
总持续时间 = 总帧数 * 每帧采样数 / 采样率 (结果为秒)
平均位率 = 文件长度 / 总持续时间 *