所有的分析都基于ID3官方网站www.id3.org.同时所有的代码都在我的github中,我将它们用闭包集成在了一起,可以直接使用,非常方便,地址:https://github.com/jslinuxboy/ID3MusicDealer
MP3的ID3v2.3版本文件基本分为两部分,一个为标签,其它的为数据。
文件标签分为标签头和标签帧
而标签头有十个字节,即在文件最开始的
char Header[3];//这个字符串一定为"ID3"
char version;//版本号,而针对在下要讲的版本,理应为3.即为ID3v2.3
char revision;//副版本号,好像一直都是0,没看到过它有变过
char flags;//一些特殊的消息标记,只会使用此字节的高3位,其它的五位并没有什么卵用
char size[4];//代表整个标签帧的大小,但是不包括这开始的10个字节,所以这里得到的size需要加上10才代表整个标签帧的大小
这里加起来总共
然而我想说的是,这些东西都是些大佬定的,一种文件格式从来不是用代码来定义的,id3.org官网定义非常简单,感觉也比较明了,如下:
ID3v2/file identifier "ID3"
ID3v2 version $03 00
ID3v2 flags %abc00000
ID3v2 size 4 * %0xxxxxxx
再缩减一下
$49 44 33 yy yy xx zz zz zz zz
49 44 33就是ID3,然后yy yy,这是真正的版本,虽然上面大佬们将version分为了主的版本和副的版本,xx则是标记,然后最后四个zz则是大小了
ID3v2 flags中的
a - Unsynchronisation
表示是否同步(自己乱翻译的),这个搞不清是什么鬼,个人英语不是很行,大概是为了数据帧同步帧数据,校正数据用的
b - Extended header
表示是否有扩展头部,这个扩展头部是用来补充标签信息的,原文如下:
The extended header contains information that is not vital to the correct parsing of the tag information, hence the extended header is optional.
c - Experimental indicator
表示是否为试验测试,这个东西是什么鬼也不知道,没见过MP3音乐文件这个位进行了设置
ID3v2 size中的
然后计算标签帧的大小,ID3规定这四个字节中每个字节的最高位恒为
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
JS代码如下:
var size = size4 & 0x7f | ((size3 & 0x7f) << 7) | ((size2 & 0x7f) << 14) | ((size1 & 0x7f) << 21);
注意:这里的帧大小,并不包含帧头的
这里再说一个特殊消息标记的Extended header处理,当Extended header这个标记位设置为
然后这个Extended header信息内容格式如下:
Extended header size $xx xx xx xx
Extended Flags $xx xx
Size of padding $xx xx xx xx
...
Extended header size有四个字节,表示接下来的数据占用多少个字节。
Extended Flags 这两个字节不知道干什么
接下来就都是扩展头部的数据了(我猜Extended Flags这两个字节好像没有,扩展头部本来就没有多大用,一般直接就滤掉了)
这里是JS代码实现标签头的识别:
getByteAt(iOffset);//得到iOffset位置的一个字节数据
isBitSetAt(iOffset, iBit);//判断iOffset位置的字节的iBit位是1还是0
readSynchsafeInteger32At(data, iOffset);//这是处理标签头的size
getLongAt(iOffset, bBigEndian);//得到iOffset位置的Long数据,bBigEndian表示是低端还是高端
/****************/
var offset = 0,
major = data.getByteAt(offset + 3),
revision = data.getByteAt(offset + 4),
unsynch = data.isBitSetAt(offset + 5, 7),
xheader = data.isBitSetAt(offset + 5, 6),
xtest = data.isBitSetAt(offset + 5, 5),
size = this.readSynchsafeInteger32At(data, offset + 6);
offset += 10;
if (xheader) {
var xheadersize = data.getLongAt(offset, true);
offset += xheadersize + 4;
}
var id3 = {
"version": ‘2.‘ + major + ‘.‘ + revision,
"major": major,
"flags": {
"unsynchronisation": unsynch,
"extended_header": xheader,
"experimental_indicator": xtest
},
"size": size
};
文件分析图片:
帧头的定义
char ID[4];//用四个字符标识一个帧,表明这个帧的内容是什么
char size[4];//帧内容的大小,不包括帧头
char flags[2];//特殊的消息标记
官方定义
Frame ID $xx xx xx xx (four characters)
Size $xx xx xx xx
Flags $xx xx
帧标识:The frame ID made out of the characters capital A-Z and
0-9.FrameID会是一串由A-Z和0-9的字符串组成,占用
帧大小:The frame ID is followed by a size descriptor, making a total header size of ten bytes in every frame. The size is calculated as frame size excluding frame header
帧标记:最后这个flags跟前面说的都一样为特殊标记
常见有用的帧标识
帧标记说明
只定义了
flags %abc00000 ijk00000
a -- 标签保护标志,设置时认为此帧作废
b -- 文件保护标志,设置时认为此帧作废
c -- 只读标志,设置时认为此帧不能修改(但我没有找到一个软件理会这个标志)
i -- 压缩标志,设置时一个字节存放两个BCD 码表示数字
j -- 加密标志(没有见过哪个MP3 文件的标签用了加密)
k -- 组标志,设置时说明此帧和其他的某帧是一组
帧标识这一块有太多,各种各样的,到官方网站去看,这里会主要区分三种主要的标识信息(其他的都拜拜吧,通过看官方网站的信息你就知道为什么拜拜了)。
1 .T*,即以T 开头的帧标识,为文本标识,分为三部分
第一部分为
第二部分根据编码确定是否存在
如果为
如果为
如果为
第三部分就是数据
这下面的官方的描述:
<Header for ‘User defined text information frame‘, ID: "TXXX">
Text encoding $xx
Description <text string according to encoding> $00 (00)
Value <text string according to encoding>
虽然Text encoding就是一个字节,但是Description的字节数就是不确定的,最后就是数据内容(个人感觉这里好像少了些东西,感觉Description不是第二部分,也就是说虽然官方上有这个属性描述,但是实际上音乐文件中根本没有这玩意,有的只是确定编码格式的数据,也就是第二部分)
JS代码讲解如下:
ID3v2.readFrameData[‘T*‘] = function(offset, length, data) {
var charset = ID3v2.getTextEncoding(data.getByteAt(offset));
return data.getStringWidthCharsetAt(offset + 1, length - 1, charset).toString();
};
其中getTextEncoding和getStringWidthCharsetAt是个人进行集成了的函数,会对上述的数据进行相应的处理(github中有源代码)
2 .APIC,专辑图片,好像整个MP3的数据就只有这个标识有图片
这里直接以官方说明来讲解比较好:
<Header for ‘Attached picture‘, ID: "APIC">
Text encoding $xx
MIME type <text string> $00
Picture type $xx
Description <text string according to encoding> $00 (00)
Picture data <binary data>
第一个为数据编码,和以T开头的一样,分为四种
第二个为MIME type数据了,表示的是什么类型图片,有image/jpeg,image/png…等,
这里的字节数不确定,是用
第三个为Picture type,表示的是图片代表什么,是作者还是一些什么内容。
第四个为Description,就是简单的图片描述了,这里和MIME type数据一样,是以\0为结束的,这里多说一句的是,这个属性好像也不经常用,它的值经常为”“
第五部分就是图片数据了,记住这不是base64编码的数据。
Picture type的值扩充说明(就是这些值表示这张图片的大概内容)
$00 Other
$01 32x32 pixels ‘file icon‘ (PNG only)
$02 Other file icon
$03 Cover (front)
$04 Cover (back)
$05 Leaflet page
$06 Media (e.g. lable side of CD)
$07 Lead artist/lead performer/soloist
$08 Artist/performer
$09 Conductor
$0A Band/Orchestra
$0B Composer
$0C Lyricist/text writer
$0D Recording Location
$0E During recording
$0F During performance
$10 Movie/video screen capture
$11 A bright coloured fish
$12 Illustration
$13 Band/artist logotype
$14 Publisher/Studio logotype
JS代码:
ID3v2.readFrameData[‘APIC‘] = function(offset, length, data, v) {
v = v || ‘3‘;//这里是为了以后扩展用的
var start = offset,
charset = ID3v2.getTextEncoding(data.getByteAt(offset));
switch (v) {
case ‘2‘:
break;
case ‘3‘:
var format = data.getStringWidthCharsetAt(offset + 1, length, ‘‘);
offset += 1 + format.bytesReadCount;
break;
}
var bite = data.getByteAt(offset),
type = ID3v2.pictureType[bite],
desc = data.getStringWidthCharsetAt(offset + 1, length - (offset - start), charset);
offset += 1 + desc.bytesReadCount;
return {
"format": format.toString(),
"type": type,
"description": desc.toString(),
"data": data.getBytesAt(offset, (start + length) - offset)
};
};
这些代码分别对应该类型数据处理
3 .COMM,备注消息,这个玩意一直在飞,全程都是懵逼的,这个属性感觉并没有什么卵用
这里还是官方说明来讲解比较好
<Header for ‘Comment‘, ID: "COMM">
Text encoding $xx
Language $xx xx xx
Short content descrip. <text string according to encoding> $00 (00)
The actual text <full text string according to encoding>
第一个不想说了,跟前面的一模一样;
第二个表示接下来是什么语言,就是说是中文还是英文还是其它语言,一般是英文就是eng
然后就是短描叙了,这里的字节数也是不确定的,也就是说这里是以\0为结尾的数据,需要不断读取直到\0结束
接下来就是最终的数据了
JS代码:
ID3v2.readFrameData[‘COMM‘] = function(offset, length, data) {
var start = offset,
charset = ID3v2.getTextEncoding(data.getByteAt(offset)),
language = data.getStringAt(offset + 1, 3),
shortdesc = data.getStringWidthCharsetAt(offset + 4, length - 4, charset);
offset += 4 + shortdesc.bytesReadCount;
var text = data.getStringWidthCharsetAt(offset, (start + length) - offset, charset);
return {
language: language,
short_description: shortdesc.toString(),
text: text.toString()
};
};
自己对着看吧
到这里三种类型就分析完了
到此基本就可以了,标签分析完成,个人不进行歌曲的数据帧分析。
这里进行编码数据扩充
当第一个字节小于
当然在这个音乐文件中有小端和大端区分,所以我们经常会看到如下编码选项 UCS-2 Big Endian(大端),UCS-2 Little Endian(小端)
这个编码分为三种,一个为
区分:
第一字节小于
第一字节大于等于
第一字节大于等于
JS代码如下:
var stringUtils = {
readUTF16String: function(bytes, bBigEndian) {//utf-16
var ix = 0,
offset1 = 1,
offset2 = 0,
bytesLength = bytes.length,
bigEndian = bBigEndian;
if (bytes[0] == 0xFE && bytes[1] == 0xFF) {
bigEndian = true;
ix = 2;
} else if (bytes[0] == 0xFF && bytes[1] == 0xFE) {
bigEndian = false;
ix = 2;
}
if (bigEndian) {
offset1 = 0;
offset2 = 1;
}
var arr = [];
while (ix < bytesLength) {
var byte1 = bytes[ix + offset1],
byte2 = bytes[ix + offset2],
word1 = (byte1 << 8) + byte2;
ix += 2;
if (word1 == 0x0000) {
break;
} else if (byte1 < 0xD8 || byte1 > 0xDF) {
arr.push(String.fromCharCode(word1));
} else {
var byte3 = bytes[ix + offset1],
byte4 = bytes[ix + offset2],
word2 = (byte3 << 8) + byte4;
ix += 2;
arr.push(String.fromCharCode(word1, word2));
}
}
var string = new String(arr.join(""));
string.bytesReadCount = ix;
return string;
},
readUTF8String: function(bytes) {//utf-8
var ix = 0,
bytesLength = bytes.length;
if (bytes[0] == 0xEF && bytes[1] == 0XBB && bytes[2] == 0xBF) {
ix = 3;
}
var arr = [];
while (ix < bytesLength) {
var byte1 = bytes[ix++];
if (byte1 == 0x00) {
break;
} else if (byte1 < 0x80) {
arr.push(String.fromCharCode(byte1));
} else if (byte1 >= 0xC2 && byte1 < 0xE0) {
var byte2 = bytes[ix++];
arr.push(String.fromCharCode(((byte1 & 0x1F) << 6) + (byte2 & 0x3F)));
} else if (byte1 >= 0xE0 && byte1 < 0xF0) {
var byte2 = bytes[ix++],
byte3 = bytes[ix++];
arr.push(String.fromCharCode(((byte1 & 0xFF) << 12) + ((byte2 & 0x3F) << 6) + (byte3 & 0x3F)));
}
}
var string = new String(arr.join(""));
string.bytesReadCount = ix;
return string;
},
readNullTerminatedString: function(bytes) {
var arr = [],
bytesLength = bytes.length,
i;
for (i = 0; i < bytesLength;) {
var byte1 = bytes[i++];
if (byte1 == 0x00) break;
arr.push(String.fromCharCode(byte1));
}
var string = new String(arr.join(""));
string.bytesReadCount = i;
return string;
}
};
数据分析整体流程图
这里多说一下个人写的代码中的对象ID3module
StringUtils专门处理字符串的对象,比如通过UTF-16编码获取
binaryFile专门对数据进行处理的对象,比如获得多少个字节
b64Utils专门对数据进行base64编码解码的对象
fileAPIReader专门生成文件二进制流的对象
framesTable专门存储标签帧标识的对象
_shortcuts用户可能需要的数据
_defaultShortcuts默认对象可能需要的数据
getFrameData将数据转换为数组的函数
原文:http://blog.csdn.net/qq_18661257/article/details/54908775