MP3格式-写数据到MP3数据帧
更新时间:2023-12-23 10:19:02 阅读量: 教育文库 文档下载
- modbus写数据格式推荐度:
- 相关推荐
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.
正在阅读:
MP3格式-写数据到MP3数据帧12-23
TMG架设L2TP(IPSec)+VPN - 图文01-22
2016-2022年中国婚纱礼服行业现状研究分析与市场前景预测报告05-05
最新部编版三年级上册语文第六单元测试卷及答案(2019-2020学年03-14
浙江省诸暨市草塔中学2020学年高二语文12月月考试题(无答案)04-29
高考试题(直线和圆的方程)03-09
2011年全国各地100套中考物理试题分类汇编7--电压 电阻 变阻器 - 图文11-22
蜂巢蜜的功效和作用05-04
神舟电脑低价策略案例06-04
- exercise2
- 铅锌矿详查地质设计 - 图文
- 厨余垃圾、餐厨垃圾堆肥系统设计方案
- 陈明珠开题报告
- 化工原理精选例题
- 政府形象宣传册营销案例
- 小学一至三年级语文阅读专项练习题
- 2014.民诉 期末考试 复习题
- 巅峰智业 - 做好顶层设计对建设城市的重要意义
- (三起)冀教版三年级英语上册Unit4 Lesson24练习题及答案
- 2017年实心轮胎现状及发展趋势分析(目录)
- 基于GIS的农用地定级技术研究定稿
- 2017-2022年中国医疗保健市场调查与市场前景预测报告(目录) - 图文
- 作业
- OFDM技术仿真(MATLAB代码) - 图文
- Android工程师笔试题及答案
- 生命密码联合密码
- 空间地上权若干法律问题探究
- 江苏学业水平测试《机械基础》模拟试题
- 选课走班实施方案
- 数据
- MP3
- 格式
- 网络信息安全论文:网络信息安全浅析
- 关于红石桥乡油房台水库
- 初二作文叙事:读《西游记》有感
- 初级会计职称之《初级会计实务》章节练习
- 统计分析题库
- 人教版二年级数学上册第一章认识毫米教案
- “关联”速度问题模型归类例析
- 信息技术课堂提问的有效性
- 园林苗圃学复习思考题有答案版
- 童年时光
- 写给情人(情侣)的道歉信
- 两学一做学习教育微党课和两学一做学习教育微型党课报告会汇编
- 大学生自我成长分析报告
- 如何对学生的学习开展评价
- 实验四 DHCP服务器实验报告
- 大气污染控制工程试卷题库全集
- 桐庐桩基试桩施工方案第一版本 - 图文
- 陕西省西安市2011届高三五大名校第一次模拟考试(数学理)
- 最新九下第一章演化的自然1-4节宇宙的起源、太阳系的形成与地球的诞生、恒星的一生、地球的演化与生命的 - 图文
- 三农政策与农业科技复习资料