移动多媒体编程节选下—MP3分析

MP3

MP3文件主要由3部分组成,依次是IDV2,MP3帧和IDV1,分析MP3文件,必须首先从这3个部分入手。


ID3V1是固定长度的,共128个字节,位于MP3文件的尾部。结构如下,

这个结构比较简单,因此解析ID3v1的代码并不复杂,在initialize()方法中,创建一个RandomAccessFile对象,调用seek()方法将文件的游标定位到ID3v1开始的位置,然后按照结构依次读取就可以了。

public void initialize() throws MP3Exception {
try {
//可以随机访问文件的任意部分
RandomAccessFile raf = new RandomAccessFile(file, "r");
//跳到 ID3V1 开始的位置
raf.seek(raf.length() - 128);
byte[] tag = new byte[3];
//读取 Header
raf.read(tag);
if (!new String(tag).equals("TAG")) {
throw new MP3Exception("No ID3V1 found");
}
byte[] tags = new byte[125];
raf.read(tags);
//逐一读取 ID3V1 中的各个字段
readTag(tags);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readTag(byte[] array) {
title = new String(array, 0, 30).trim();
artist = new String(array, 30, 30).trim();
album = new String(array, 60, 30).trim();
year = new String(array, 90, 4);
comment = new String(array, 94, 28).trim();
reserve = array[122];
track = array[123];
genre = array[124];

ID3v2

ID3v2定义在MP3文件的头部,这与ID3V1不同,ID3V2是变长的,这一特性使ID3V2具有良好的扩展性,甚至个人也可以定义ID3V2中的帧,只要符合ID3V2的布局即可。

ID3v2的结构图如下,其中深色标识的结构式可选的。在ID3v2中,比特顺序采用Big endian方式排列,也就是高字节存储在高位,和java的存储是一致的,和c是相反的。


ID3v2头(Header)的长度时固定的,共10个字节,布局如下,

File ID(3)---》Version(2)---->Flags(1)----->Size(4)

前 3 个字节总是“ID3”,可以通过检查这个文件标识来判断是否是 ID3V2 头。随后 2个字节是 ID3V2 的版本,其中第 4 个字节代表 ID3V2 的主版本号,第 5 个字节代表 ID3V2
的修订版本号。目前 ID3V2 的 2.3 和 2.4 版本应用最广泛。随后的一个字节是标志位,目前此字节的前 4 位在使用,其他位为 0,标志位的第 2 比特标识了 ID3V2 头后面是否有扩展头,标志位的第 4 位标志了 ID3V2 最后是否含有 Footer。最后的 4 个字节标识了 ID3V2 的大小,其中包括 10 个字节的 ID3V2 头。由于每个字节的第 1 位永远是 0,因此只有 28 个字节用来表示大小。计算大小时可以采用下面的代码

int tagSize = (header[9] & 0xff) + ((header[8] & 0xff) << 7)
+ ((header[7] & 0xff) << 14) + ((header[6] & 0xff) << 21)

每个 ID3V2 标签含有一个或者多个 ID3V2 帧,每个帧由 ID3V2 帧头和帧体构成。ID3V2帧头由 4 个字节(btye  ASCII码中,一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间,8位二进制数)的帧 ID、4 个字节的大小标志和 2 个字节的标志位组成,共计 10 个字节。帧头的布局如图 7-10 所示。其中帧 ID 由 4 个字符组成,字符可以是 0~9 和 A~Z,例如TIT2、TALB 等。紧随其后是 4 个字节的尺寸标识,4 个字节的每个比特都可以使用,共计32 位用来表示帧的大小。需要注意的是,这个大小表示的是帧体的大小,不包括帧头的 10个字节,因此整个尺寸应该是帧体的大小加上 10 个字节。标签帧并没有固定的顺序要求,TIT2 可以出现在 TALB 前面,也可以出现在 TALB 的后面。

FrameID(4)---->Size(4)------>Flags(2)

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 为键,以帧的内容为值

public void initialize() throws MP3Exception, IOException {
if (file == null)
throw new NullPointerException("MP3 file is not found");
FileInputStream is = new FileInputStream(file);
byte[] header = new byte[10];
is.read(header);
//判断是否是合法的 ID3V2 头
if (header[0] != 'I' || header[1] != 'D' || header[2] != '3') {
throw new MP3Exception("not invalid mp3 ID3 tag");
}
//计算 ID3V2 的帧大小
tagSize = (header[9] & 0xff) + ((header[8] & 0xff) << 7)
+ ((header[7] & 0xff) << 14) + ((header[6] & 0xff) << 21);
int pos = 10;
while (pos < tagSize) {
byte[] tag = new byte[10];
//读取 ID3V2 的帧头,如果 tag[0]=0,则跳出循环,结束解析 ID3V2
is.read(tag);
if (tag[0] == 0) {
break;
}
String tagName = new StringBuffer().append((char) tag[0]).append(
(char) tag[1]).append((char) tag[2]).append((char) tag[3])
.toString();
//计算 ID3V2 帧的大小,不包括前面的帧头大小
int length = ((tag[4] & 0xff) << 24) + ((tag[5] & 0xff) << 16)
+ ((tag[6] & 0xff) << 8) + tag[7];
byte[] data = new byte[length];
is.read(data);
//将帧头和帧体存储在 HashMap 中
tags.put(tagName, data);
pos = pos + length + 10;
}
is.close();
}
public int getTagSize() {
return tagSize;
}
public String tit2() {
return getTagText("TIT2");
}
public String talb() {
return getTagText("TALB");
}
public String tpe1() {
return getTagText("TPE1");
}
private String getTagText(String tag) {
byte[] data = (byte[]) tags.get(tag);
//查询帧体的编码方式
String encoding = encoding(data[0]);
try {
return new String(data, 1, data.length - 1, encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}



private String encoding(byte data) {
String encoding = null;
switch (data) {
case 0:
encoding = "ISO-8859-1";
break;
case 1:
encoding = "UTF-16";
break;
case 2:
encoding = "UTF-16BE";
break;
case 3:
encoding = "UTF-8";
break;
default:
encoding = "ISO-8859-1";
}
return encoding;
}


MP3帧结构



介于 ID3V2 和 ID3V1 之间的部分称作 MP3 帧,这些帧构成了 MP3 的音频部分。每个MP3 帧由帧头和数据块组成,之间还可能包含 2 个字节的 CRC 校验位,校验位是否存在依赖于帧头的第 16 比特位的值。以比特率为区分标准,MP3 可以分为可变比特率和不变比特率两种格式。比特率代表每秒钟的数据量,一般单位是 kbps。比特率越高,MP3 的音质越好,但是文件也越大。每个 MP3 帧固定时长为 26ms,因此可变比特率的帧大小可能是不同的,而不变比特率的帧大小是固定的,只要分析了第 1 个帧的大小就可以知道后面帧的大小。

参考: http://mcuol.com/download/upfile/OPhone7-8.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值