C语言读取mp3文件的信息

来源:互联网 发布:nba数据统计网站 编辑:程序博客网 时间:2024/04/28 00:31
MP3文件的ID3V1信息与ID3V2信息结构的分析 

——吴俊涛2005/05/05 

E-mail:bo_tao@126.com  QQ:29248671 

主 页:http://wjt276.home4u.china.com(有源代码) 

 本人是一位编程爱好者,想通过VB。NET编写一个可以读取MP3文件的信息的不程序,可以不知道文件结构,呀我在网上找了好长时间(好几个月)都没有找到VB的。后来看到一个VC的,还附有结构分析,太好了,动手了。 

一:“ID3v1”信息的分析 

MP3的基本歌曲信息存在了MP3文件的最后128个字节里,其结构是: 
Public Structure ID3v1Info 

Dim ID3v1TAG As String 'TAG三个字母,ID3V1的标识 

Dim Title As String '存储标题信息,30个字节 

Dim Artist As String '存储歌手信息,30个字节 

Dim Album As String '存储专辑信息,30个字节 

Dim Year As String '存储年代信息,4个字节 

Dim Comments As String '存储备注信息,28个字节(有时为30字节) 

Dim Genre As String '存储音乐风格信息,保留位,1个字节  

Dim Reserved As String '保留位,1个字节(有时没有意思 

Dim Track As String '音轨(曲号)保留位,1个字节(有时没有) 

End Structure 

ID3V1信息存储结构如下(如图1): 

图 1 一个MP3文件的ID3v1信息 


1-3 TAG 

4-33 歌曲名(Take Me To Your Heart ) 

34-63 歌手名(Michael Learns to Rock) 

64-93 专辑名(Take Me to Your Heart) 

94-97 年(2004) 

98-125 备注 (http://www.uptu.com) 

126 保留位,这时为0,则说明有音轨,下一位就是音轨 

127 保留位,为音轨(第几首歌)(OC) 

128 保留位 (风格)(66) 

  

而在Winamp的ID3v1歌曲信息里(如图1),我们看到的是他都包括: 
Title(歌曲名) 
Artist(歌手名) 
Album(专辑名) 
Year(年) 
Comment(备注) 
Genre(歌曲风格)注,见下面有详细的列表 
Track#(歌曲在专辑里的顺序,就是我们经常说的“第几首”) 

  Title,Artist,Album,Year,Comment我们都是可以在那个128个字节里得到,Genre和Track哪里去了呢?有的朋友都重视了那128字节信息的前125个信息了,而这两个信息是却放在了最后的126-128字节里。其实,127那处就是Track信息,而 128处就是Genre信息。他们的存储方式都不是字符,我们提取他们的时候需要注意,他们都是数字。比如,就如我们看到的这首歌的126处是0x0D,那么很显然,他就是13。也就是第13号歌曲风格,Pop流行( 下面列表)。 

这时,你也该猜到了,127和128都是有意义的,自然126处也是有其意义!ID3v1信息的Comment(注释)一共占用28个字节。这个说法并不是完全的正确。准确的说应该是正确了一部分。有的时候注释也可以超过这个数字的。ID3v1要求注释最多可以到30个字节。那么有的读者会问“MP3的 ID3v1就是得有130个字节的信息了嘛?”不是,当然不是。ID3v1是固定的128个字节,这个你不用担心。其实ID3v1是这样安排的:如果 MP3的注释是大于28个字节的,那么就要借用126-127两个字节。所以ID3v1的注释部分可能是28个字节也可能是30个字节。那么,怎么区分到底是28个字节还是30个字节呢?很简单,126处就是管这个的,我们只要看看126处是不是0x00,如果是0x00那么注释就有28个字节。如果不等于0x00,那么就是说注释是30个字节。同时别忘了,由于第127字节存储了Track信息,那么如果注释是30个字节的时候,这首歌的ID3v1里的那个127处的信息自然就不是Track信息了。Track自然就是没有地方存了,所以127处变的没有Track意义了,它只是Comment的一部分了。在你决定制作读取ID3v1的程序的时候,请特别注意一下。 

我们最终知道了126处是ID3v1信息的注释部分到底是28个字节还是30个字节的标志位。127处是音轨信息(Track),而Reserved3则是歌曲风格(Genre)。现在我们重新再写一次结构 

原理2:MP3文件有没有ID3v1信息的错误理解 
  那就是到底什么能叫做“MP3文件没有ID3v1信息”。检测的方法是先提取指定的MP3文件的最后128字节信息,然后确定这128个字节的前3个字节是“TAG”。很多朋友都会同意这个方法没有问题的。可是,实际上问题并不是那么简单。 

  Winamp或者其他的MP3播放相关的软件都有MP3信息的写入和读取的功能,然而这些写入ID3v1的软件都会不自觉的当你一打开这个MP3文件就会给它加上这128个字节的信息。也就是说当我们用这种软件打开MP3文件的时候,这些软件就会自动的在这个MP3文件尾端添加了一个128字节的 ID3v1结构,而且还是以“TAG”开头!(如图3)。那么很显然,光靠检测那“TAG”三个字节的信息,还是不能完全确定MP3到底有没有ID3v1 信息的。我们还要确定这“TAG”后的125字节是不是正确的信息。一般情况下,这类软件产生的ID3v1结构都是由一堆00,或者一堆空格组成的,所以我们要判断一下是不是ID3v1的信息是一堆00或者一堆空格。如果是,那么MP3文件虽然有这“TAG”三个字母,却仍然不是一个合法的ID3v1信息。MP3文件仍然应该认为没有ID3v1信息。我觉得这个东西有必要特别提醒大家注意。 

二: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个字节。 

源代码已发布,你可以到本站点的主页去下载,如有什么问题,请一定联系我呀,不要客气轼呀 

您如有什么问题,可以发E-MAIL给我,我们一起来讨论,主页里有源代码和实例,有兴趣的朋友不访去看看 http://wjt276.home4u.china.com 

作者:吴俊涛   时间:2005/05/05

原创粉丝点击