MP3格式写数据到MP3数据帧.docx
《MP3格式写数据到MP3数据帧.docx》由会员分享,可在线阅读,更多相关《MP3格式写数据到MP3数据帧.docx(14页珍藏版)》请在冰豆网上搜索。
MP3格式写数据到MP3数据帧
MP3格式音频文件结构解析
一、概述
Layer-3音频文件,MPEG(MovingPictureExpertsGroup)在汉语中译为活动图
像专家组,特指活动影音压缩标准,MPEGT频文件是MPEG标准中的声音部分,也叫MPEGt频层,它根据压缩质量和编码复杂程度划分为三层,即Layer-1、Layer2、
Layer3,且分别对应MP1MP2MP3这三种声音文件,并根据不同的用途,使用不同层次的编码。
MPEG!
频编码的层次越高,编码器越复杂,压缩率也越高,MP1和MP2的压缩率分别为4:
1和6:
1-8:
1,而MP3的压缩率则高达10:
1-12:
1,也就是说,一分钟CD音质的音乐,未经压缩需要10MB的存储空间,而经过MP3压缩编码后只有
1MB左右。
不过MP3对音频信号采用的是有损压缩方式,为了降低声音失真度,MP3
采取了“感官编码技术”,即编码时先对音频文件进行频谱分析,然后用过滤器滤掉噪音电平,接着通过量化的方式将剩下的每一位打散排列,最后形成具有较高压缩比的MP3文件,并使压缩后的文件在回放时能够达到比较接近原音源的声音效果。
二、整个MP3文件结构:
MP3文件大体分为三部分:
TAG_V2(ID3V2),音频数据,TAG_V1(ID3V1)
a).ID3V2在文件开始的位置,包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量。
b).一系列的音频数据的帧,在文件的中间位置,个数由文件大小和帧长决定;
每个帧的长度可能不固定,也可能固定,由位率bitrate决定
每个帧又分为帧头和数据实体两部分
帧头记录了mp3的位率,采样率,版本等信息,每个帧之间相互独立。
c).ID3V1在文件结尾的位置,包含了作者,作曲,专辑等信息,长度为128Byte。
ID3V2
包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量。
Frame
Frame
一系列的帧,个数由文件大小和帧长决定
每个FRAME勺长度可能不固定,也可能固定,由位率bitrate决定每个FRAMED分为帧头和数据实体两部分
帧头记录了mp3的位率,米样率,版本等信息,每个帧之间相互独立。
ID3V1
包含了作者,作曲,专辑等信息,长度为128BYTE
表格2.1
1、ID3V2
ID3V2到现在一共有4个版本,但流行的播放软件一般只支持第3版,既ID3V2.3。
由于ID3V1记录在MP3文件的末尾,ID3V2就只好记录在MP3文件的首部了(如果有一天发布ID3V3,真不知道该记录在哪里)。
也正是由于这个原因,对ID3V2的操作比ID3V1要慢。
而且ID3V2结构比ID3V1的结构要复杂得多,但比前者全面且可以伸缩和扩展。
下面就介绍一下ID3V2.3:
每个ID3V2.3的标签都一个标签头和若干个标签帧或一个扩展标签头组成。
关于曲
目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必
要的,但每个标签至少要有一个标签帧。
标签头和标签帧一起顺序存放在MP3文件
的首部。
1、标签头
在文件的首部顺序记录10个字节的ID3V2.3的头部。
数据结构如下:
charHeader[3];/*
必须为"ID3"否则认为标签不存在*/
charVer;/*
版本号ID3V2.3就记录3*/
charRevision;/*
副版本号此版本记录为0*/
charFlag;/*
存放标志的字节,这个版本只定义了三位,稍后详细解说*/
charSize[4];/*
标签大小,包括标签头的10个字节和所有的标签帧的大小*/
第5个字节:
副版本号,为0
1)标志字节
标志字节一般为0,定义如下:
abc00000
a--表示是否使用Unsynchronisation(这个单词不知道是什么意思,字典里也没有找到,一般不设置)
b--表示是否有扩展头部,一般没有(至少Winamp没有记录),所以一般也不设置
c--表示是否为测试标签(99.99%的标签都不是测试用的啦,所以一般也不设置)
第6个字节:
存放标志的字节,只定义了三位,这里值为0
2)标签大小
一共四个字节,但每个字节只用7位,最高位不使用恒为0。
所以格式如下
0XXXXXXX0XXXXXXX0XXXXXXX0XXXXXXX
计算大小时要将0去掉,得到一个28位的二进制数,就是标签大小(不懂为什么要这样做),计算公式如下:
total_size=(Size[0]&0x7F)*0x200000+(Size[1]&0x7F)*0x4000+
(Size[2]&0x7F)*0x80+(Size[3]&0x7F)
注意:
这里的帧大小,并不包含帧头的10个字节,只表示帧内容的大小•这里0X4000,很多写的是0x400是错的•
2、标签帧
每个标签帧都有一个10个字节的帧头和至少一个字节的不固定长度的内容组成。
它
们也是顺序存放在文件中,和标签头和其他的标签帧也没有特殊的字符分隔。
得到一个完整的帧的内容只有从帧头中的到内容大小后才能读出,读取时要注意大小,不要将其他帧的内容或帧头读入。
帧头的定义如下:
*/
charID[4];/*用四个字符标识一个帧,说明其内容,稍后有常用的标识对照表
charSize[4];/*帧内容的大小,不包括帧头,不得小于1*/
charFlags[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位全用,格式如下
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
算法如下:
intFSize=Size[0]*0x100000000+Size[1]*0x10000+Size[2]*0x100+Size[3];
注意:
这里的帧大小,并不包含帧头的10个字节,只表示帧内容的大小
3)标志
只定义了6位,另外的10位为0,但大部分的情况下16位都为0就可以了。
格
式如下:
abc00000ijk00000
a--标签保护标志,设置时认为此帧作废
b--文件保护标志,设置时认为此帧作废
c--只读标志,设置时认为此帧不能修改(但我没有找到一个软件理会这个标志)
i--压缩标志,设置时一个字节存放两个BCD码表示数字
j--加密标志(没有见过哪个MP3文件的标签用了加密)
k--组标志,设置时说明此帧和其他的某帧是一组
值得一提的是winamp在保存和读取帧内容的时候会在内容前面加个''\0'',并把
这个字节计算在帧内容的大小中。
2、音频数据帧
每个帧都有一个帧头Header,长度是4Byte(32bit),帧头后面可能有两个字节的
CRC校验值,这两个字节的是否存在决定于Header信息的第16bit,为0则帧头后面无校验,为1则有校验,校验值长度为2个字节,紧跟在Header后面,接着就是帧的实体数据了,格式如下:
1、帧头格式
帧头长4字节,对于固定位率的MP3文件,所有帧的帧头格式一样其数据结构如下
unsignedintoriginal:
1;//原始媒体
unsignedintcopyright";//版权标志
//声道模式
unsignedintchannel_mode:
2;
}FHEADER,*LPHEADER;
1)计算帧长度
我们首先区分两个术语:
帧大小和帧长度。
帧大小即每帧采样数表示一帧中采样的
个数,这是恒定值。
其值如下表所示
帧长度是压缩时每一帧的长度,包括帧头。
它将填充的空位也计算在内。
Layerl的
一个空位长4字节,Layerll和LayerIII的空位是1字节。
当读取MPEGt件时必须计算该值以便找到相邻的帧。
注意:
因为有填充和比特率变换,帧长度可能变化。
从头中读取比特率,采样频率和填充的值后可以进行计算,
Lyaerl使用公式:
帧长度(字节)二((每帧采样数/8*比特率)/采样频率)+填充*4
LyerII禾口LyaerIII使用公式:
帧长度(字节)二((每帧采样数/8*比特率)/采样频率)+填充
例:
LayerIII比特率128000,采样频率44100,填充0二〉帧大小417字节;
如图2.3中,比特率为128K采样率为44.1K,填充0,则其帧长度为:
(1152/8*128K)/44.1K=417(字节)
2)每帧的持续时间
每帧的持续时间可以通过计算获得,下面给出计算公式
每帧持续时间(毫秒)=每帧采样数/采样频率*1000
如图2.3中,其每帧时间为:
1152/44.1K*1000=26.12(约等于26ms)
如果是MPEG2LayerIII采样率为16KHz的话那一帧要持续36毫秒,这个相差还是蛮大的,所以还是应该通过计算来获的。
3)CRC校验
如果帧头的校验位为0,则帧头后就有一个16位的CRC直,这个值是big-endian的值,把这个值和该帧通过计算得出的CRC直进行比较就可以得知该帧是否有效。
4)帧数据
在帧头后边是Sidelnfo(姑且称之为通道信息)。
对标准的立体声MP3文件来说其长度为32字节。
通道信息后面是Scalefactor(增益因子)信息。
当解码器在读到上述信息后,就可以进行解码了。
图2.3中地址为0x880到0x89F(含),此处数据
全为0。
对于mp3来说现在有两种编码方式,一种是CBR也就是固定位率,固定位率的帧的大小在整个文件中都是是固定的(公式如上所述),只要知道文件总长度,和从第一帧帧头读出的信息,就都可以通过计算得出这个mp3文件的信息,比如总的帧
数,总的播放时间等等,要定位到某一帧或某个时间点也很方便,这种编码方式不需要文件头,第一帧开始就是音频数据。
另一种是VBR就是可变位率,VBR是XING公司推出的算法,所以在MP3的FRAME!
会有“Xing"这个关键字(也有用"Info"来标识的,现在很多流行的小软件也可以进行VBF压缩,它们是否遵守这个约定,
那就不得而知了),它存放在MP3文件中的第一个有效帧的数据区里,它标识了这个MP3文件是VBR的。
同时第一个帧里存放了MP3文件的帧的总个数,这就很容易获得了播放总时间,同时还有100个字节存放了播放总时间的100个时间分段的帧索引,假设4分钟的MP3歌曲,240S,分成100段,每两个相邻INDEX的时间差就是2.4S,所以通过这个INDEX只要前后处理少数的FRAME就能快速找出我们需
文件时也像VBF那样将信息记入第一帧,比如著名的lame,它使用"Info"来做CBR
的标记。
5)VBR头
这里列出VBR的第一帧存储文件信息的头的格式。
有两种格式,一种是常见的
XINGHeader(头部包含字符’Xing'),另一种是VBRIHeader(头部包含字符
'VBRI')鉴于VBRIHeader不常见,下面只说XINGHeader:
XINGHeader的起始位置,相对于第一帧帧头的位置,单位是字节
36-39"Xing"文件为MPEG并且不是单声道(大多数VBR的mp3文件都是如此)
21-24"Xing"文件为MPEG并且是单声道
21-24"Xing"文件为MPEG并且不是单声道
13-16"Xing"文件为MPEG并且是单声道
在VBF格式的第一帧中,XINGHeader包括帧头一共最多只需要156个字节就够了,当然也可以在XINGHeader后面存储编码器的信息,比如lame在其后就是存储其版本,这需要给第一帧留足够的空间才行。
3、ID3v1
ID3V1标准并不周全,存放的信息少,无法存放歌词,无法录入专辑封面、图片等。
ID3V2是一个相当完备的标准,但给编写软件带来困难,虽然赞成此格式的人很多,在软件中绝大多数MP3仍在使用ID3V1标准。
ID3v1标签包含艺术家,标题,唱片集,发布年代和流派。
另外还有额外的注释空间。
位于音频文件的最后固定为128
字节。
可以读取该文件的最后这128字节获得标签。
ID3V1结构如下:
AAABBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDEEE
EFFFFFFFFFFFFFFFFFFFFFFFFFFFFGHI
表3.1ID3V1.0
文件尾说明
字节长度(字
子节节)
说明
存放“TAG字符,表示ID3V1.0标准,紧接其后的是歌曲
1-3(A)3
信息。
4-33(B)30
歌名
34-63(C)30
作者
64-93(D)30
专辑名
94-97(E)4
年份
98-125(F)28
附注
126(G)1
保留位
127(H)1
音轨号
128(I)1
MP3t乐类别,共147种。
ID3V1的各项信息都是顺序存放,没有任何标识将其分开,比如标题信息不足30个
字节,则使用''\0''填充,数据结构定义如下:
typedefstructtaglD3V1
{
charHeader[3];
/*标签头必须是"TAG"否则认为没有标签*/
charTitle[30];
/*标题*/
charArtist[30];
/*作者*/
charAlbum[30];
/*专集*/
charYear[4];
/*出品年代*/
charComment[28];
/*备注*/
charreserve;
/*保留*/
chartrack;
/*音轨*/
charGenre;
/*类型*/
}ID3V1,*pID3V1;
例子:
向MP3文件写入自己的数据帧
unituWriteDataToMp3;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,StdCtrls,Buttons,ExtCtrls;
type
TForm1=class(TForm)
Panel1:
TPanel;
Panel2:
TPanel;
BitBtn1:
TBitBtn;
BitBtn2:
TBitBtn;
procedureBitBtn1Click(Sender:
TObject);
procedureBitBtn2Click(Sender:
TObject);
private
{Privatedeclarations}
procedureWriteMIO_DataToMp3」D3(mp3File:
string);
public
{Publicdeclarations}
end;
var
Form1:
TForm1;
implementation
{$R*.dfm}
procedureTForm1.WriteMIO_DataToMp3」D3(mp3File:
string);type
ID3Header=record//ID3标签
ID3:
array[0..2]ofChar;//==ID3
Ver1:
Byte;//3
Ver2:
Byte;//0
Flag:
Byte;//
Size:
array[0..3]ofChar;//string[4];
//包括标签头的10个字节和所有的标签帧的大小
//(Size[0]&0x7F)*0x200000+(Size[1]&0x7F)*0x4000+(Size[2]&
//0x7F)*0x80+(Size[3]&0x7F)
end;
FrameHeader=record//帧头
ID:
array[0..3]ofChar;//4个字符标识一个帧,说明其内容
Size:
array[0..3]ofChar;//Size[0]*0x100000000+Size[1]*0x10000+
//Size[2]*0x100+Size[3]
Flag:
array[0..1]ofChar;//==00
end;
wx_Head=record//写数据
ID:
Word;
data_len:
Integer;
Data:
string[128];
end;
var
wx:
wx_Head;
i,j,n,data_len,id3Size,frameSize:
Integer;
ms,ms2:
TMemoryStream;
p:
PChar;
wb,c:
Byte;
ID3:
ID3Header;
Frame:
FrameHeader;
rwFile:
Integer;
wMp3File:
string;
begin
//读取MP3文件的ID3头
rwFile:
=FileOpen(mp3File,fmOpenRead);
FileRead(rwFile,ID3,SizeOf(ID3Header));
FileClose(rwFile);
if(ID3.ID3<>'ID3')and(ID3.Ver1<>3)then
begin
ShowMessage('文件【’+mp3File+'】不是标准的ID3V2版本的MP3文件!
');
Exit;
end;
wMp3File:
='C:
\MyMp3.MP3';
//计算原来MP3文件的ID3大小
Id3Size:
=(ord(id3.Size[0])and$7F)*$200000+
(ord(id3.Size[1])and$7F)*$4000+
(ord(id3.Size[2])and$7F)*$80+
(ord(id3.Size[3])and$7F);
//我们的帧头
Frame.ID:
='SYLT';
Frame.Size:
='0000:
Frame.Flag:
='00';
//我们的PWM+IO数据头
wx.lD:
=$55AA;
wx.Data:
='向MP3文件里写自己的数据帧’;
ms:
=TMemoryStream.Create;
ms.Write(ID3,SizeOf(ID3Header));
ms.Write(Frame,SizeOf(FrameHeader));
ms.Write(wx,SizeOf(wx_Head));
data_len:
=SizeOf(wx_Head);
wx.data_len:
=data_len;//数据大小,不含头
frameSize:
=SizeOf(wx_Head);//我们使用帧大小
//帧大小
Frame.Size[0]
Frame.Size[1]
Frame.Size[2]
Frame.Size[3]
id3Size:
=id3Size+frameSize+SizeOf(ID3Header)+SizeOf(FrameHeader);//ID3新的大小
=chr((frameSizeshr24)and$FF);=chr((frameSizeshr16)and$FF);
=chr((frameSizeshr8)and$FF);
=chr(frameSizeand$FF);
//ID3填写的大小
ID3.Size[0]:
=chr((id3Sizeshr21)and$7F);
ID3.Size[1]:
=chr((id3Sizeshr14)and$7F);
ID3.Size[2]:
=chr((id3Sizeshr7)and$7F);
ID3.Size[3]:
=chr(id3Sizeand$7F);
//数据大小改变了,重写
ms.Position:
=0;
ms.Write(ID3,SizeOf(ID3Header));
ms.Write(Frame,SizeOf(FrameHeader));
ms.Write(wx,SizeOf(wx_Head));//更新数据长度数据
ms.Position:
=ms.Size;//移动、然后后面追加MP3数据
ms2:
=TMemoryStream.Create;//Mp3数据
ms2.LoadFromFile(mp3File);
ms2.Seek(SizeOf(ID3Header),soFromBeginning);//ms2要去掉LD310个字
节的头
n:
=ms2.Size-SizeOf(ID3Header);
p:
=GetMemory(n);
ms2.ReadBuffer(p»n);
ms2.Free;
ms.Write(pA,n*SizeOf(Byte));//追加MP3数据
FreeMemory(p);
ms.SaveToFile(wMp3File);//写文件
ms.Free;
ShowMessage('用MP3【'+mp3File+'】文件写目标文件【'+wMp3File+'】
已完成!
’);
end;
procedureTForm1.BitBtn1Click(Sender:
TObject);
var
aFile:
string;
i:
Integer;
begin
aFile:
='';
withTOpenDialog.Create(self)do
begin
Options:
=[ofHideReadOnly,ofNoChangeDir,ofAllowMultiSelect,
ofEnableSizing];
Filter:
='(*.mp3)|*.mp3';
ifExecutethenaFile:
=FileName;
Free;
end;
ifaFile<>''then
begin
WriteMIO_DataToMp3_ID3(aFile);
end;
end;
pr