MP3格式-写数据到MP3数据帧

更新时间:2024-03-07 12:59:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

MP3格式音频文件结构解析

一、概述

Layer-3 音频文件,MPEG(Moving Picture Experts Group) 在汉语中译为活动图像专家组,特指活动影音压缩标准,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 文件,并使压缩后的文件在回放时能够达到比较接近原音源的声音效果。

二、整个MP3 文件结构:

MP3 文件大体分为三部分:TAG_V2(ID3V2),音频数据,TAG_V1(ID3V1)

a). ID3V2 在文件开始的位置,包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1 的信息量。

b). 一系列的音频数据的帧,在文件的中间位置,个数由文件大小和帧长决定; 每个帧的长度可能不固定,也可能固定,由位率bitrate决定 每个帧又分为帧头和数据实体两部分

帧头记录了mp3 的位率,采样率,版本等信息,每个帧之间相互独立 。 c). ID3V1在文件结尾的位置,包含了作者,作曲,专辑等信息,长度为128Byte。

ID3V2 包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量。 Frame 一系列的帧,个数由文件大小和帧长决定 . 每个FRAME的长度可能不固定,也可能固定,由位率bitrate决定 . 每个FRAME又分为帧头和数据实体两部分 . 帧头记录了mp3的位率,采样率,版本等信息,每个帧之间相互独立。 Frame 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 的头部。数据结构如下: char Header[3]; /*必须为\否则认为标签不存在*/ char Ver; /*版本号ID3V2.3 就记录3*/ char Revision; /*副版本号此版本记录为0*/ char Flag; /*存放标志的字节,这个版本只定义了三位,稍后详细解说*/ char Size[4]; /*标签大小,包括标签头的10个字节和所有的标签帧的大小*/ 第5个字节:副版本号,为0 1)标志字节

标志字节一般为0,定义如下: abc00000

a -- 表示是否使用Unsynchronisation(这个单词不知道是什么意思,字典里也没有找到,一般不设置)

b -- 表示是否有扩展头部,一般没有(至少Winamp 没有记录),所以一般也不设置 c -- 表示是否为测试标签(99.99%的标签都不是测试用的啦,所以一般也不设置) 第6个字节:存放标志的字节,只定义了三位,这里值为0

2)标签大小

一共四个字节,但每个字节只用7位,最高位不使用恒为0。所以格式如下 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx

计算大小时要将0 去掉,得到一个28 位的二进制数,就是标签大小(不懂为什么要这样做),计算公式如下:

total_size = (Size[0]&0x7F)*0x200000+ (Size[1]&0x7F)*0x4000 + (Size[2]&0x7F)*0x80 +(Size[3]&0x7F)

注意:这里的帧大小,并不包含帧头的10个字节,只表示帧内容的大小.这里0X4000,很多写的是0x400是错的.

2、标签帧

每个标签帧都有一个10个字节的帧头和至少一个字节的不固定长度的内容组成。它们也是顺序存放在文件中,和标签头和其他的标签帧也没有特殊的字符分隔。得到一个完整的帧的内容只有从帧头中的到内容大小后才能读出,读取时要注意大小,不要将其他帧的内容或帧头读入。 帧头的定义如下:

char ID[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 表示备注所使用的自然语言

2)大小

这个可没有标签头的算法那么麻烦,每个字节的8 位全用,格式如下 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 算法如下:

int FSize = 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文件,所有帧的帧头格式一样其数据结构如下(注:此结构要自己定义): 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, *LPHEADER; 1)计算帧长度 我们首先区分两个术语:帧大小和帧长度。帧大小即每帧采样数表示一帧中采样的个数,这是恒定值。其值如下表所示 Layer 1 Layer 2 Layer 3 MPEG 1 MPEG 2(LSF) MPEG 2.5(LSF) 384 384 384 1152 1152 1152 1152 576 576 帧长度是压缩时每一帧的长度,包括帧头。它将填充的空位也计算在内。LayerI的一个空位长4字节,LayerII和LayerIII的空位是1字节。当读取MPEG文件时必须计算该值以便找到相邻的帧。注意:因为有填充和比特率变换,帧长度可能变化。 从头中读取比特率,采样频率和填充的值后可以进行计算, LyaerI使用公式: 帧长度(字节) = (( 每帧采样数/ 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)

如果是MPEG2 Layer III 采样率为16KHz的话那一帧要持续36毫秒,这个相差还是蛮大的,所以还是应该通过计算来获的。

3)CRC校验

如果帧头的校验位为0,则帧头后就有一个16位的CRC值,这个值是big-endian的值,把这个值和该帧通过计算得出的CRC值进行比较就可以得知该帧是否有效。 4)帧数据

在帧头后边是Side Info(姑且称之为通道信息)。对标准的立体声MP3文件来说其长度为32字节。通道信息后面是Scale factor(增益因子)信息。当解码器在读到上述信息后,就可以进行解码了。图 2.3中地址为0x880到0x89F(含),此处数据全为0。

对于mp3来说现在有两种编码方式,一种是CBR,也就是固定位率,固定位率的帧的大小在整个文件中都是是固定的(公式如上所述),只要知道文件总长度,和从第一帧帧头读出的信息,就都可以通过计算得出这个mp3文件的信息,比如总的帧数,总的播放时间等等,要定位到某一帧或某个时间点也很方便,这种编码方式不需要文件头,第一帧开始就是音频数据。另一种是VBR,就是可变位率,VBR是XING公司推出的算法,所以在MP3的FRAME里会有“Xing\\来标识的,现在很多流行的小软件也可以进行VBR压缩,它们是否遵守这个约定,那就不得而知了),它存放在MP3文件中的第一个有效帧的数据区里,它标识了这个MP3文件是VBR的。同时第一个帧里存放了MP3文件的帧的总个数,这就很容易获得了播放总时间,同时还有100个字节存放了播放总时间的100个时间分段的帧索引,假设4分钟的MP3歌曲,240S,分成100段,每两个相邻INDEX的时间差就是2.4S,所以通过这个INDEX,只要前后处理少数的FRAME,就能快速找出我们需

要快进的帧头。其实这第一帧就相当于文件头了。不过现在有些编码器在编码CBR文件时也像VBR那样将信息记入第一帧,比如著名的lame,它使用\来做CBR的标记。 5)VBR头

这里列出VBR的第一帧存储文件信息的头的格式。有两种格式,一种是常见的XINGHeader(头部包含字符‘Xing’),另一种是VBRIHeader(头部包含字符‘VBRI’)鉴于VBRIHeader不常见,下面只说XINGHeader: XING Header的起始位置,相对于第一帧帧头的位置,单位是字节

36-39 \文件为MPEG1并且不是单声道(大多数VBR的mp3文件都是如此) 21-24 \文件为MPEG1并且是单声道 21-24 \文件为MPEG2并且不是单声道 13-16 \文件为MPEG2并且是单声道

在VBR格式的第一帧中,XING Header包括帧头一共最多只需要156个字节就够了,当然也可以在XING Header后面存储编码器的信息,比如lame在其后就是存储其版本,这需要给第一帧留足够的空间才行。

3、ID3v1

ID3V1标准并不周全,存放的信息少,无法存放歌词,无法录入专辑封面、图片等。ID3V2是一个相当完备的标准,但给编写软件带来困难,虽然赞成此格式的人很多,在软件中绝大多数MP3仍在使用ID3V1标准。ID3v1标签包含艺术家,标题,唱片集,发布年代和流派。另外还有额外的注释空间。位于音频文件的最后固定为128字节。可以读取该文件的最后这128字节获得标签。 ID3V1结构如下:

AAABBBBB BBBBBBBB BBBBBBBB BBBBBBBB BCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDEEE EFFFFFFF FFFFFFFF FFFFFFFF FFFFFGHI

表3.1 ID3 V1.0文件尾说明

长度 (字

字节 说 明

节)

存放“TAG”字符,表示ID3 V1.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 MP3音乐类别,共147种。

ID3V1 的各项信息都是顺序存放,没有任何标识将其分开,比如标题信息不足30 个字节,则使用''\\0''填充,数据结构定义如下: typedef struct tagID3V1 {

charHeader[3]; /*标签头必须是\否则认为没有标签*/ charTitle[30]; /*标题*/ charArtist[30]; /*作者*/ charAlbum[30]; /*专集*/ charYear[4]; /*出品年代*/ charComment[28]; /*备注*/

charreserve; /*保留*/ chartrack; /*音轨*/ charGenre; /*类型*/ } ID3V1, *pID3V1;

例子:向MP3文件写入自己的数据帧

unit uWriteDataToMp3;

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;

procedure BitBtn1Click(Sender: TObject); procedure BitBtn2Click(Sender: TObject); private

{ Private declarations }

procedure WriteMIO_DataToMp3_ID3(mp3File: string); public

{ Public declarations } end; var

Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.WriteMIO_DataToMp3_ID3(mp3File: string); type

ID3Header = record // ID3 标签

ID3: array[0..2] of Char; // == ID3 Ver1: Byte; // 3 Ver2: Byte; // 0 Flag: Byte; //

Size: array[0..3] of Char; // 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] of Char; // 4个字符标识一个帧,说明其内容

Size: array[0..3] of Char; // Size[0]*0x100000000 + Size[1]*0x10000+ // Size[2]*0x100 + Size[3]

Flag: array[0..1] of Char; // == 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.ID := $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); // 我们使用帧大小

id3Size := id3Size + frameSize + SizeOf(ID3Header) + SizeOf(FrameHeader); // ID3 新的大小

// 帧大小

Frame.Size[0] := chr((frameSize shr 24) and $FF); Frame.Size[1] := chr((frameSize shr 16) and $FF); Frame.Size[2] := chr((frameSize shr 8) and $FF); Frame.Size[3] := chr(frameSize and $FF);

// ID3填写的大小

ID3.Size[0] := chr((id3Size shr 21) and $7F); ID3.Size[1] := chr((id3Size shr 14) and $7F); ID3.Size[2] := chr((id3Size shr 7) and $7F); ID3.Size[3] := chr(id3Size and $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 要去掉 LD3 10个字节的头

n := ms2.Size - SizeOf(ID3Header);

p := GetMemory(n);

ms2.ReadBuffer(p^, n); ms2.Free;

ms.Write(p^, n * SizeOf(Byte)); // 追加MP3数据

FreeMemory(p);

ms.SaveToFile(wMp3File); // 写文件 ms.Free;

ShowMessage('用MP3【' + mp3File + '】文件写目标文件【' + wMp3File + '】已完成!'); end;

procedure TForm1.BitBtn1Click(Sender: TObject); var

aFile: string; i: Integer; begin

aFile := '';

with TOpenDialog.Create(self) do begin

Options := [ofHideReadOnly, ofNoChangeDir, ofAllowMultiSelect, ofEnableSizing];

Filter := '(*.mp3)|*.mp3';

if Execute then aFile := FileName; Free; end;

if aFile <> '' then begin

WriteMIO_DataToMp3_ID3(aFile); end; end;

procedure TForm1.BitBtn2Click(Sender: TObject); begin Close; end; end.

本文来源:https://www.bwwdw.com/article/6wpa.html

Top