ID3v1 & ID3v1.1
MP3格式标准里并没有特别定义储存曲目相关讯息的结构,于是1996年有人提出了一种解决办法,即在mp3档案末尾新增一些额外的数据(128 byte)来储存曲名、演唱者、专辑名等讯息,这就是ID3(v1)。
ID3v1的字列(field)名称和每个字列的大小都是类BIOS的,如下所显示
Song title——30 characters
Artist——30 characters
Album——30 characters
Year——4 characters
Comment——30 characters
Genre—— byte
ID3v1.1和ID3v1唯一区别就是多了个Album track字列,实际上是从上面说的Comment字列末尾抽出两个字符用来存放Album track讯息。ID3v1(v1.1)大家一定很熟悉,因为MP3中早已经广泛的使用ID3,甚至很多人误以为这是mp3原有的的标准(事实上mp3格 式标准里并不包含ID3)。虽然广为使用,但ID3v1,ID3v1.1有明显缺陷。字列名和字列长度都是类BIOS死的,不可增强,缺乏灵活性。而且 30个字符实在十分有限,想必很多人碰到过title或者artist太长被截断的情况吧。所以有人提出新的ID3v2格式,试图解决这些问题,增强 ID3的功能。
●ID3v2
虽然从名称上ID3v2好像只是ID3v1的一个升级,实际在格式定义上ID3v2和 ID3v1完全不同,ID3v2应该算一个全新的tag系统。如果用过WINAMP的ID3 tag editor,就知道ID3v2和ID3v1最直观的区别是新增加了一些文本字列,譬如composer、conductor、media type、copyright message等等。但这不是重点,相较于ID3v1,ID3v2最大的改进应该是极大的增强了灵活性和可括展性,不仅每个字列的长度是可增强的(再也不 必为ID3v1 30个字符的限制而烦恼了),而且用户还可以很容易的新增自订字列。
ID3v2 tag支持unicode,可以包含歌词(包括同步讯息),曲目的volume、balance、equalizer、reverb设定讯息,甚至可以插 入图片,支持连接外部讯息和网页……还有很多,有兴趣的可以看这里([url]http://www.id3.org /frames.html[/url])。
看上去ID3v2功能强大,真是不错,不过凡事都有两面性,ID3v2繁多的功能也带来一个 负面问题,就是使得ID3v2太过复杂,实现起来比较困难,一个单纯实现所有ID3v2标准功能的程序就得100多K(FB2K主程序也就200多K)。 而且有些功能也并不十分合适,譬如把图片嵌入到音濒档案中就不是一个好主意,你需要另外编写程序从音濒档案中提取图片,而且也造成播放音濒档案时更容易出 现错误。
还有一点值得注意的,ID3v2不像ID3v1储存于在档案末尾,而是插入到音濒档案的最开头。原本的初衷是考虑网络上播放档案时可以先接收到相应tag讯息。实际套用时却发现这么处理是弊大于利。
1、兼容性变差。时常看到有人反映因为插入ID3v2 tag而造成音濒档案不能正常播放的。
2、写入tag速度慢。由于tag放在档案开头,所以第一次插入tag必须得重写整个档案,严迭影响速度,虽然ID3v2采取了的一些方法,避免每次修改tag都必须重写整个档案(大多数情况下不会,某些情况下还是有可能),但同时也增加了程序处理的复杂度。
3、耗费临时空间。由于插入tag得重写整个档案,如果你向一个大档案新增ID3v2 tag,必须得有充足的磁盘空间,否则就会写入失败,甚至会造成源文件的损坏。
(注:最新的ID3v2.4标准已经可以选项把tag存放在档案末尾。)
所以,ID3v2虽然初看起来不错,但并不受欢迎,在Hydrogenaudio Forums上用ID3v2做关键词搜寻一下就知道了,很多人毫不吝惜的把「SHIT」的光荣称号授予了ID3v2。事实上目前流行的音濒格式(无论有损 无损),只要有自己的tag标准定义的,没有一个选项ID3v2;标准版Foobar2000也不支持ID3v2,不过倒是有个第三方插件可以读写MP3 和AAC的ID3v2 tag。
尽管如此,ID3v2在硬件支持上还是颇具优势,目前绝大多数MP3随身听只支持ID3v1和ID3v2,如果你有这样的便携式硬设备,那ID3v2几乎是你的唯一选项。
目前tag格式没有一个统一标准,不同音濒格式使用的tag也不尽相同。现在比较流行的tag格式有ID3和APE(这个APE和平常说的无损音濒格式APE可不一样),它们各自也还有不同的版本,下面分别做简要介绍。
●APEv1 & APEv2
APE也是一种tag格式,如果我没搞错,应该是Monkey's Audio的APE最先采用,是不是因此而得名我就不太清楚了。APE用的是APEv1,后来在APEv1基础上改进又有了APEv2,主要区别就是两点:
1、APEv2标准里增加了一个APE Tags Header
2、APEv2采用UTF-8编码,使得可以实现unicode支持
APE tag具有与ID3v2一样的灵活性和可括展性,字列名可自订,字列长度可增强,同时格式定义又不像ID3v2那么繁琐。APE tag的格式很简单,实现起来也很方便,tag存放位置是可选的,既可以在档案头也可以在档案尾(推荐在档案尾),所以有不少人对APEv2比较倾瞇。譬 如,MPC(MusePack)把ID3v1和APEv2作为标准支持的tag,Foobar2000也把APEv2作为标准的tag系统。
7.4.3 ID3V2标签
1998 年,ID3V2作为新的标准诞生了,尽管其沿用了"ID3"的名称,但是ID3V2和ID3V1并没有太多联系。ID3V2定义在MP3文件的头部,这与 ID3V1不同。ID3V2是变长的,这一特性使ID3V2具有良好的扩展性,甚至个人也可以定义ID3V2中的帧,只要符合ID3V2的布局即可。
ID3V2的结构图如图7-8所示,其中深色标识的结构是可选的。在ID3V2中,比特顺序采用Big endian方式排列,也就是高字节存储在高位。ID3V2的结构相对于ID3V1复杂很多,这里重点介绍ID3V2头和ID3V2帧。
图7-8 ID3V2的结构图 |
1. ID3V2头
ID3V2头(Header)的长度是固定的,共10个字节,其布局如图7-9所示。
图7-9 ID3V2头的布局 |
- int tagSize = (header[9] & 0xff) + ((header[8] & 0xff) <<7)
- + ((header[7] & 0xff) <<14) + ((header[6] & 0xff) <<21);
2. 扩展头
扩展头(Extended Header)包含了更多的数据信息,这些数据是对ID3V2头的补充,但是并非解析MP3文件的关键数据。
3. ID3V2 帧
每 个ID3V2标签含有一个或者多个ID3V2帧,每个帧由ID3V2帧头和帧体构成。ID3V2帧头由4个字节的帧ID、4个字节的大小标志和2个字节的 标志位组成,共计10个字节。帧头的布局如图7-10所示。其中帧ID由4个字符组成,字符可以是0~9和A~Z,例如TIT2、TALB等。紧随其后是 4个字节的尺寸标识,4个字节的每个比特都可以使用,共计32位用来表示帧的大小。需要注意的是,这个大小表示的是帧体的大小,不包括帧头的10个字节, 因此整个尺寸应该是帧体的大小加上10个字节。标签帧并没有固定的顺序要求,TIT2可以出现在TALB前面,也可以出现在TALB的后面。
图7-10 ID3V2帧头的布局 |
ID3V2的帧体由字节数组构成,其内容一般是与帧ID对应的。例如,TIT2帧体内存储了歌曲的标题,TALB帧体内存储了歌曲的专辑信息。帧体的第1个字节标识了字符的编码方式,目前有4种编码方式可用。
0000 0000代表字符使用ISO-8859-1编码方式;
0000 0001代表字符使用UTF-16编码方式;
0000 0002代表字符使用 UTF-16BE编码方式;
0000 0003代表字符使用UTF-8编码方式。
在读取帧体内容时,应该按照上面的编码对应表首先确定编码方式,然后再生成相关的字符串。对于TIT2和TALB等帧ID来说,读取其内容比较简单。对于USLT(对应歌曲的歌词信息)等结构较复杂的帧,需要仔细研究其格式才能将内容从帧体中读取出来。
4. 填充
在 ID3V2帧后面可以存放填充(Padding)位,填充位的值只能是0。填充位使得ID3V2帧的大小比ID3V2计算得到的大小要小一些,也就是说, 留下了一些空白的空间,这些空间可以用来增加一些额外的帧信息。由于增加的信息写在一些空白的空间内,因此无须重写整个文件,这也就是填充存在的重要意 义。
5. ID3V2尾
ID3V2 尾(Footer)是可选的,有时候可能需要从MP3文件的尾部向前搜索ID3V2的位置,这时候ID3V2的存在就可以大大地加快搜索的速度。ID3V2 尾和ID3V2 头的内容是一致的,只是文件标识部分由"ID3"改成了"3DI"。
ID3V2 的结构相对要复杂一些,在设计ID3V2类时,主要考虑了ID3V2的大小和ID3V2帧。ID3V2的大小可以帮助我们快速定位到MP3帧的起始位 置,ID3V2帧内存储了MP3文件的元数据,包括歌曲名称、歌手和专辑等。ID3V2类定义了一个HashMap类型的成员变量,用来存储ID3V2帧 数据,以ID3V2帧ID为键,以帧的内容为值。
ID3v2信息的提取
MP3文件的 “ID3v1信息”。这个信息结构提取起来非常容易,写入到文件也不是什么难事。但是它的信息安排和可扩展性却非常之差(只能128个字节)。就如你所 知,MP3文件还有另外的一个信息结构,这个结构具有更好的可扩展性,而且存储的容量也不受限制(也就是总长度不固定)。这个信息就是 ID3v2信息(相对ID3v1而言)。由于ID3v1信息存储在了文件的最后128个字节里,那么ID3v2就不得不放弃选择存储在文件的末尾了,于是 它被存储在了文件的起始位置。
ID3v2信息的存储和读取远远要比ID3v1信息复杂的多。这是因为ID3v2信息不再固定,而且由于这段信息存储在了文件的首端,所以重新写入的时候也远比ID3v1麻烦的多。
我用尽可能清楚而且简练的话,给大家讲一下ID3v2信息的读取方法。ID3v2到现在一共有4个版本,不过比较流行的MP3播放软件一般只支持第3 版,即ID3v2.3。我们要读取的就是ID3v2.3信息。ID3v2信息包括两个部分,一个部分是标头信息,另一个部分是标体信息。其中标头信息占固 定的十个字节,
每个ID3V2的标签部一个标签头和若干个标签帧或一个扩展标签组成关于曲目的信息如标题、作者等都放在在不同的标签帧中,扩展标签头和标签帧关不是必要的,但每个标签至少要有一个标签头和标签帧一直顺序存放在MP3文件首部。
它的结构如下:
(一)、标签头
Private Structure ID3v2Header
Dim Header() As Byte'ID3v2标识位,应该是“ID3”三个字母为对
Dim Ver As Byte'版本号ID3V2就记录3
Dim Revision As Byte’副版本号此版本记录为0
Dim Flag As Byte‘存放标志的字节,这个版本只定义了三位,稍后详细解说
Dim Size() As Byte’标签大小,不包括标签头的10个字节(但是有的文章说包括)我是通过核实 才这样说的,看看源代码就知道了
End Structure
这十个字节的信息作用:
1、Header(2),一般为“ID3”,否则没有ID3V2信息
2、Flag 标志字节:标志字节一般为0,字义为abc00000
a-表示是否使用Unsynchronisation
b-表示是否有扩展头部,一般没有(WINAMP也没有)所以一般不设置
c-表示是否为测试标签(99.9%的标签都不是测试用的,所以一般不设置)
3、Sixe(3) 标签大小:一共四个字节,但每个字节只使用7位,最高位不使用恒为0,所以格式如下:
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
计算大小时要将0去掉,得到一个28位的二进制数,就是标签的大,计算公式如下
①、VC的:ID3size =(Size[0]&0x7F)*0x200000 +(Size[1]&0x7F)*0x400 +(Size[2]&0x7F)*0x80 +(Size[3]&0x7F);
②、VB的:ID3size =Size(0) * (2 ^ 21) + Size(1) * (2 ^ 14) + Size(2) * (2 ^ 7) + Size(3) * (2 ^ 0)
VB的我已在类中声明了一个函数ByteToLong,很方便,直调用就OK了。
通过解析这段标头信息我们可以知道一个MP3文件是不是有ID3v2信息,如果有我们就知道了ID3v2的数据体的总长度。
(二)、标签帧
接下来我们要解析ID3v2的 标签帧,别担心,虽然复杂,但也没你想象的那么的痛苦。ID3v2的数据体又分为很多相同的数据结构。
每个标签帧都有一个10个字节的帧头和至少一个字节的不固定长度的内容组成,它们也是顺序存放在文件中,和标签头和其他的标签帧也没有特殊的字符分 隔,得到一个完整的帧的内容只有从帧头中的到内容大小 后才能读出,读取时要注意大小,不要将其它的帧的内容或帧头读入。帧的定义如下:
Private Structure ID3v2Frame
Dim FrameID As String‘用4个字符标识一个帧,说明其内容,常用的标识对照表见附表
Dim Size() As Byte’4个字节 帧内容的大小,不包括帧头,不得小于1,计算时也用上面的公式计算
Dim Flags() As Byte’2个字节 存放标志,只定义了6位,稍后详细解说
End Structure
1、FrameID 帧标:用四个字符标识一个帧的内容含义,常用的对照如下:
TEXT: 歌词作者 TENC: 编码
WXXX: URL链接(URL) TCOP: 版权(Copyright)
TOPE: 原艺术家 TCOM: 作曲家
TDAT: 日期 TPE3: 指挥者
TPE2: 乐队 TPE1: 艺术家相当于ID3v1的Artist
TPE4: 翻译(记录员、修改员) TYER: 年代相当于ID3v1的Year
USLT: 歌词 TALB: 专辑相当于ID3v1的Album
TIT1: 内容组描述 TIT2: 标题相当于ID3v1的Title
TIT3: 副标题 TCON: 流派(风格)相当于ID3v1的Genre见下表
TBPM: 每分钟节拍数 COMM: 注释相当于ID3v1的Comment
TDLY: 播放列表返录 TRCK: 音轨(曲号)相当于ID3v1的Track
TFLT: 文件类型 TIME: 时间
TKEY: 最初关键字 TLAN: 语言
TLEN: 长度 TMED: 媒体类型
TOAL: 原唱片集 TOFN: 原文件名
TOLY: 原歌词作者 TORY: 最初发行年份
TOWM: 文件所有者(许可证者) TPOS: 作品集部分
TPUB: 发行人 TRDA: 录制日期
TRSN: Intenet电台名称 TRSO: Intenet电台所有者
TSIZ: 大小 TSRC: ISRC(国际的标准记录代码)
TSSE: 编码使用的软件(硬件设置) UFID: 唯一的文件标识符
AENC: 音频加密技术
其中要说明的是这个FrameID,在ID3v1里我们是根据每一个信息所占用的固定的字节数和位置来判断他是哪个信息的。而ID3v2为了提供 更好的可扩展性,把这些信息变得“动态”化了,因为长度并不是预先设定好的,而是在size[4]里存储的。这样长度就可以不再固定了。我觉得在我们自己 定义文件的时候ID3v2和ID3v1也是值得我们考虑的一个方面。如果结构很小而且存储的量也不大,我们可以采用ID3v1的信息存储方式。如果存储的 信息不固定,而且要求有很好的可扩展性,那么ID3v2当然成了首选。实际上,现在很多格式的文件的存储方式都是ID3v2的存储方式非常接近的。
2、Size() 帧内容大小:不再是总标头那样的每个字节只取后7位了,它是按照正常的8位存储的。得到帧内容的大小的格式如下 :
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
计算成整形,公式如下:
①、VC的: FSize = Size[0]*0x100000000 + Size[1]*0x10000 + Size[2]*0x100 + Size[3];
②、VB的:ID3size =Size(0) * (2 ^ 21) + Size(1) * (2 ^ 14) + Size(2) * (2 ^ 7) + Size(3) * (2 ^ 0)
VB的我已在类中声明了一个函数ByteToLong,很方便,直调用就OK了。
3、Flags() 标志:只定义了6位,另外10位为0 但大部分的情况下16位都为0就可以了,格式如下:
a-标签保护标志,设置时认为此帧作废
b-文件保护标志,设置时认为此帧作废
c-只读标志,设置时认为此帧不能修改(目前好像没有看到过)
i-压缩标志,设置时一个字节存放两个BCD码表示数字
j-加密标志(好像不太实用)
k-组标志,设置时说明此帧和其它的某帧是一组。
详细你可能到www.ID3.org去了解一下。
4、帧内容(数据体)
标头后面就是数据体了,我们提取数据体的前十个字节,我们知道了这个数据结构存储的FrameID是TIT2,查上面的表,说明这个数据结构存储的 是歌曲名信息。大小是00 00 00 17,换成十进制就是23。也就是歌曲名是这个子标头后的23个字节的信息。也就是:“Take Me To Your Heart ”。接下来的一个数据结构的FrameID是TPE1,说明是歌手名,而大小是00 00 00 17,说明这个数据体有23个字节,也就是:“Michael Learns to Rock”。依次类推。这里需要大家知道的是一个汉字占用两个字节。在写入时,要计算字节数,我已编写了一个函数ByteSize,大家可以直接使用了。
还有特别要提醒大家的是,ID3v2的注释信息(FrameID是COMM)的数据体的前四(但经我测试为前5个字节)个字节,并不是注释内容,而 是注释使用的自然语言,这个例子里我们看到是:”eng\0”,我们要跳过这四个字节的信息进行解析。此外ID3v2的歌曲类型 Genre(FrameID是 TCON)的存储也不太一样的。由于很多MP3播放器的写入方式并不是非常一致,而在Genre写入的也不一致。比如,这首歌的ID3v2的Genre是 Classic Rock,其实有的还会写入成:(1),或者1,还有(1)Classic Rock,所以格式五花八门,我们要在解析的时候注意一下。还有,值得一提的是winamp在保存和读取帧内容的时候会在内容前面加个'\0',并把这个 字节计算在帧内容的大小中。所以前面提到的歌手名“Michael Learns to Rock”本身应该22个字节,可是却占了23个字节。