MP3是大家所熟悉的一种音乐播放格式,它其实是mpeg标准中的mpeg1的layer3编码,这个是和压缩技术相关的,对于mpeg的了解,大家可以去网上找一些格式解析的文档,在这里我们只要知道MP3这种格式是什么就够了,在以下的内容中,会有详细解释。
MP3中有一个关键词就是:帧,MP3是由若干个帧组成。
Mp3的文件结构
MP3文件大体分为三部分:TAG_V2(ID3V2),Frame, TAG_V1(ID3V1),其中ID3V1在整个MP3文件的末尾128个字节,包含了作者,作曲,专辑等信息,而ID3V2是在文件的开头部分,是对ID3V1的扩展包含MP3的一些信息如作者,专辑,发行日等等,它的大小不固定,可以从他的标签头记录的是个字节中得到标识和大小。
ID3V2 ID3V2到现在一共有4个版本,但流行的播放软件一般只支持第3版,既ID3v2.3。由于ID3V1记录在MP3文件的末尾,ID3V2就只好记录在MP3文件的首部了(如果有一天发布ID3V3,真不知道该记录在哪里)。也正是由于这个原因,对ID3V2的操作比ID3V1要慢。而且ID3V2结构比ID3V1的结构要复杂得多,但比前者全面且可以伸缩和扩展。 下面就介绍一下ID3V2.3。 每个ID3V2.3的标签都一个标签头和若干个标签帧或一个扩展标签头组成。关于曲目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必要的,但每个标签至少要有一个标签帧。标签头和标签帧一起顺序存放在MP3文件的首部。
一、标签头
char Header[3]; /*必须为"ID3"否则认为标签不存在*/
char Ver; /*版本号ID3V2.3就记录3*/
char Revision; /*副版本号此版本记录为0*/
char Flag; /*存放标志的字节,这个版本只定义了三位,稍后详细解说*/
char Size[4]; /*标签大小,包括标签头的10个字节和所有的标签帧的大小*/(我实际测试不包括标签头的10个字节。是笔误吗?百度一搜都是的。竟然还有的注释:有的文档认为不包括。我去,还带这的?难道标准不一样?那么,标准又是什么?标准仅仅是"有的文档"?)
1、标志字节 标志字节一般为0,定义如下: abc00000 a -- 表示是否使用Unsynchronisation(这个单词不知道是什么意思,字典里也没有找到,一般不设置) b -- 表示是否有扩展头部,一般没有(至少Winamp没有记录),所以一般也不设置 c -- 表示是否为测试标签(99.99%的标签都不是测试用的啦,所以一般也不设置)
2、计算标签大小:
一共四个字节,但每个字节只用7位,最高位不使用恒为0。所以格式如下
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
计算大小时要将0去掉,得到一个28位的二进制数,就是标签大小(不懂为什么要这样做),计算公式如下:
int total_size = (Size[0]&0x7F)*0x200000
+(Size[1]&0x7F)*0x4000 //这个地方原来是0x400,可能是由于原作者笔误,然后百度一下到处都是0x400
+(Size[2]&0x7F)*0x80
+(Size[3]&0x7F)
按道理来说,跳过标签大小,就是第一帧的帧头位置,但是有时候却不是,所以我们仍然要搜索判断是否是帧头,下面我们来看mp3的帧结构。
这句是因为扩展标签帧吗?有时候不是,那是什么情况下不是?
二、标签帧
每个标签帧都有一个10个字节的帧头和至少一个字节的不固定长度的内容组成。它们也是顺序存放在文件中,和标签头和其他的标签帧也没有特殊的字符分隔。得到一个完整的帧的内容只有从帧头中的到内容大小后才能读出,读取时要注意大小,不要将其他帧的内容或帧头读入。帧头的定义如下:
char FrameID[4]; /*用四个字符标识一个帧,说明其内容,稍后有常用的标识对照表*/
char Size[4]; /*帧内容的大小,不包括帧头,不得小于1*/
char Flags[2]; /*存放标志,只定义了6位,稍后详细解说*/
1.帧标识 用四个字符标识一个帧,说明一个帧的内容含义,常用的对照如下:
TIT2=标题 表示内容为这首歌的标题,下同
TPE1=作者 TALB=专集
TRCK=音轨 格式:N/M 其中N为专集中的第N首,M为专集中共M首,N和M为ASCII码表示的数字
TYER=年代 是用ASCII码表示的数字
TCON=类型 直接用字符串表示
COMM=备注 格式:"eng\0备注内容",其中eng表示备注所使用的自然语言
2.大小 这个可没有标签头的算法那么麻烦,每个字节的8位全用,格式如下
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
算法如下:
int FSize; FSize = Size[0]*0x100000000 +Size[1]*0x10000 +Size[2]*0x100 +Size[3];
3.标志 只定义了6位,另外的10位为0,但大部分的情况下16位都为0就可以了。格式如下:
abc00000 ijk00000
a -- 标签保护标志,设置时认为此帧作废
b -- 文件保护标志,设置时认为此帧作废
c -- 只读标志,设置时认为此帧不能修改(但我没有找到一个软件理会这个标志)
i -- 压缩标志,设置时一个字节存放两个BCD码表示数字
j -- 加密标志(没有见过哪个MP3文件的标签用了加密)
k -- 组标志,设置时说明此帧和其他的某帧是一组
值得一提的是winamp在保存和读取帧内容的时候会在内容前面加个'\0',并把这个字节计算在帧内容的大小中。
Mp3的帧详解
每一帧其实包括 帧头,附加信息,主数据,其实我们只要找到帧头,帧头中所包含的数据就能让我们掌控这一帧的信息,帧头固定4个字节(32bit),格式如下
AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
下面是就是每个位置代表的含义:
标识 | 长度 | 含义 | 示例 |
A | 11 | 用于同步帧,找到此帧头(所有位均置 1) | 11111111111 |
B | 2 | 音频版本 ID 00 - 版本是 MPEG 2.5 (MPED-2 的非官方扩展版本) 01 – 保留 10 – 版本是 MPEG 2 (ISO/IEC 13818-3) 11 – 版本是 MPEG 1 (ISO/IEC 11172-3) 通过ID查表得其他信息 | 11 |
C | 2 | Layer 的索引 00 – 保留 01 - Layer III 10 - Layer II 11 - Layer I | 01 |
D | 1 | 保护位 1 – 无 CRC 0 – 用 16位的 CRC保护下面的帧头 | 1 |
E | 4 | 比特率索引(查表) | 1001 |
F | 2 | 采样率索引 (查表) | 10 |
G | 1 | 填充位,如果为1,计算帧长时,要多加1 | 1 |
H | 1 | 私有位 (仅用于标示性的) | 1 |
I | 2 | 声道的模式 00 – 立体声 01 – 混合立体声 10 – 双声道 (两个单声道) 11 – 一个声道 (单声道) | 01 |
J | 2 | 联合立体声(joint stereo) 采用联合立体声编码方式的两个声道具有关联性。例如MS_stereo将两个声道相加、相差后处理,相减后去掉了左右声道相同的成份,后续的压缩可得到更高的压缩率。 | 10 |
K | 1 | 版权保护,0=no 1=yes | 1 |
L | 1 | 原始版本,0=no 1=yes | 0 |
M | 1 | 预加重 00 - none 01 - 50/15 ms 10 - reserved 11 - CCIT J.17 | 01 |
1)比特率
其中E和F位置的值,是通过mpeg ID和layer索引查标准的值,我写成了一个数组,直接查表得到比特率
int bitrate[5][15] = {
/* MPEG-1 */
{ 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, /* Layer I */
256000, 288000, 320000, 352000, 384000, 416000, 448000 },
{ 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, /* Layer II */
128000, 160000, 192000, 224000, 256000, 320000, 384000 },
{ 0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, /* Layer III */
112000, 128000, 160000, 192000, 224000, 256000, 320000 },
/* MPEG-2 ,MPEG-2.5 */
{ 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, /* Layer I */
128000, 144000, 160000, 176000, 192000, 224000, 256000 },
{ 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, /* Layer */
64000, 80000, 96000, 112000, 128000, 144000, 160000 } /* II & III */
};
2)采样频率:
int samplingrate[4][3] = { //value s are in Hz
{11025 , 12000 , 8000}, //MPEG Version 2.5
{0,0,0}, //reserved
{22050, 24000, 16000}, //MPEG Version 2 (ISO/IEC 13818-3)
{44100, 48000, 32000} //MPEG Version 1 (ISO/IEC 11172-3)
};
3)每帧持续时间
在这里在介绍一个比较重要的概念那就是,每帧持续时间,MP3有一个特殊性,那就是每帧持续时间是固定的:
每帧持续时间(毫秒) = 每帧采样数 / 采样频率 * 1000
我们从第一帧的帧头可以得音频版本 ID和layer版本,从而可以确定每帧采样个数
mpeg 1 mpeg 2 mpeg 2.5
layer I 384 384 384
layer II 1152 1152 1152
layer III 1152 576 576
现在我们应该很清楚的知道,为什么我们只需要知道MP3是mpeg1的layerIII层的原因了;(但是按照我现在的理解只要是layer III的就是MP3,呵呵,可以讨论)
(写这个有点慢,因为不会用,不知道咋添加表格,不习惯)
4)计算总时长
下面我们来看计算时间的公式:
播放时间 = 总帧bytes ÷ 比特率 × 8000
比特率:查表可得到;
总帧bytes:简单的看就是,总文件的大小-ID3信息,总文件大小就是读取整个文件的大小,ID3就是之前说过的ID3V2和ID3V1,ID3V1在末尾处128个字节,ID3V2在文件开始为止,大小可以从标签头得到,之前讲过,这样我们就能准确的得到总帧bytes,我们就能计算出来mp3的总时长了.
但是这只是对固定比特率(CBR)的MP3总时长的计算方式,对于变比特率(VBR)的MP3,由于每帧的比特率可能不同,用以上的公式就无法准确算出来mp3总时长,有些文档说可以计算平均比特率,但是这个估算值偏差有时候很大,下一次我来详细讲如果求变化比特率Mp3的总时长,牢牢记住上面的表格,一切的信息都能从那里来。
VBR格式的mp3计算时间
之前我们所讨论的都是对于一个固定比特率(CBR)的MP3进行求总时长,由于固定比特率的特殊性,很容易第一帧的帧头就能知道整个MP3的信息,但是对于变比特率(VBR)的MP3,比特率和帧长都不是固定的,所以我们就不能用之前的公式来计算总时长。
其实在第一帧的帧头后面还存着VBR的一些信息,里面存着文件长度,总帧数等信息,从而使我们很快算出变比特率的总时长,方法如下:
1)XING头标志
由于VBR是XING公司推出的算法,所以在MP3的第一帧里会有"XING"或者"Info"的关键字,具体格式
位置 | 长度(字节) | 含义 |
0 | 4 | 包含ASCII的字符XING或者Info,没有NULL结尾 |
4 | 4 | 标识位0x0001 - 存在总帧数(Frames)字段 0x0002 - 存在文件大小(Bytes)字段 0x0004 –存在 TOC字段 0x0008 – 存在音频质量指示字段 |
8 | 4 | 若标识位存在总帧数,那么这四个字节就是总帧数 |
12 | 4 | 若标识位存在文件大小,那么这四个字节就是文件大小 |
还有别的信息,这里不需要,我就不在这里说了。
2) VBRI头标志
还有一种头也同样标识和XING类似,只是有"VBRI"的关键字,具体格式
位置 | 长度(字节) | 含义 |
0 | 4 | 包含ASCII的字符VBRI,没有NULL结尾 |
10 | 4 | 文件大小 |
14 | 4 | 总帧数 |
我只写了对总时长有需要的位置,其他的暂时没用到,就不提了,不过可以讨论。
3)确定标志头位置
知道的有这样的标志头可以帮助我们很快找到需要的信息,那么如何定位呢,公式如下:
标志头位置 = MPEG 头位置 + MPEG帧头大小(4字节)+ 边信息大小
对于边信息,我们可以根据头中所给J位置上2个字节得到
mode = (header>>6)&0x03
其中:'00' Stereo; '01' Joint Stereo (Stereo); '10' Dual channel (Two mono channels);
'11' Single channel (Mono).
MPEG 1 MPEG2/2.5
Stereo ,Joint Stereo Dual channel 32 17
Mono 17 9
现在已经很清楚了,我们就能定位出来头标志,然后读出我们需要的总帧数,之前一届我已经介绍了每帧持续时间,因此
VBR的MP3总时长(毫秒) = 总帧数*每帧持续时间
到此不知道我讲的清楚不清楚,这也是我通过自己做,和看别人的文档总结的一些,希望能帮助到需要的人,有什么问题可以和我讨论。
这个准确度还是比较高的,前提是从帧头中读到的信息是正确有效的,呵呵,我测得大部分MP3还是比较正确的,如果真的要准确,那就预读一遍MP3文件,把每一帧的找到,累加得到帧数,我做MP3帧索引表的时候就这么搜的,其实对于小文件还是比较快的。