首页 > 其他 > 详细

MP3文件分析之ID3v2.3版本

时间:2017-02-07 14:15:10      阅读:672      评论:0      收藏:0      [点我收藏+]

所有的分析都基于ID3官方网站www.id3.org.同时所有的代码都在我的github中,我将它们用闭包集成在了一起,可以直接使用,非常方便,地址:https://github.com/jslinuxboy/ID3MusicDealer

MP3ID3v2.3版本文件基本分为两部分,一个为标签,其它的为数据。

文件标签分为标签头标签帧

1.标签头

而标签头有十个字节,即在文件最开始的10个字节,它的数据结构如下:

char Header[3];//这个字符串一定为"ID3"

char version;//版本号,而针对在下要讲的版本,理应为3.即为ID3v2.3

char revision;//副版本号,好像一直都是0,没看到过它有变过

char flags;//一些特殊的消息标记,只会使用此字节的高3位,其它的五位并没有什么卵用

char size[4];//代表整个标签帧的大小,但是不包括这开始的10个字节,所以这里得到的size需要加上10才代表整个标签帧的大小

这里加起来总共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中的%abc00000,其中高三位表示如下:

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中的4?%0xxxxxxx表示的是4个字节,后面的%0xxxxxxx就是一个字节8位了。

然后计算标签帧的大小,ID3规定这四个字节中每个字节的最高位恒为0不使用,格式如下:

0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx

JS代码如下:

var size = size4 & 0x7f | ((size3 & 0x7f) << 7) | ((size2 & 0x7f) << 14) | ((size1 & 0x7f) << 21);

注意:这里的帧大小,并不包含帧头的10个字节,只表示帧内容的大小

这里再说一个特殊消息标记的Extended header处理,当Extended header这个标记位设置为1时,在这最开始的10个字节后面会增加有Extended header的内容,这部分内容非常有意思,因为它所占用的大小不算在之前10个字节的size中,就相当这里会凭空多出一些字节。

然后这个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
};

文件分析图片:

技术分享


2.标签帧内容

帧头的定义

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的字符串组成,占用4个字节

  • 帧大小: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跟前面说的都一样为特殊标记

常见有用的帧标识

  1. TIT2:歌曲标题名字
  2. TPE1:作者名字
  3. TALB:作品专辑
  4. TYER:作品产生年代
  5. COMM:备注信息
  6. APIC:专辑图片

帧标记说明

只定义了6位,另外的10位为0,一般这些标记也不用,通常为0,格式如下:

flags %abc00000 ijk00000 

a -- 标签保护标志,设置时认为此帧作废

b -- 文件保护标志,设置时认为此帧作废

c -- 只读标志,设置时认为此帧不能修改(但我没有找到一个软件理会这个标志) 

i -- 压缩标志,设置时一个字节存放两个BCD 码表示数字 

j -- 加密标志(没有见过哪个MP3 文件的标签用了加密) 

k -- 组标志,设置时说明此帧和其他的某帧是一组

帧标识这一块有太多,各种各样的,到官方网站去看,这里会主要区分三种主要的标识信息(其他的都拜拜吧,通过看官方网站的信息你就知道为什么拜拜了)。

1.T*,即以T开头的帧标识,为文本标识,分为三部分

第一部分为1个字节,这个字节一定是[0x00,0x01,0x02,0x03]中的一种,0x00代表这个标签帧后续的数据为iso-8859-1编码,0x01则是utf-16编码,0x02则是utf-16be编码,0x03则是utf-8编码

第二部分根据编码确定是否存在
如果为0x00编码的话就不会存在,字节就是直接读取

如果为0x010x02那么这里会占用2个字节,会出现两种可能的数据,一种为FF FE表示小端,即数据存储是高数据在高位,一种为FE FF表示大端与小端相反

如果为0x03编码则是会占用三个字节 EF BB BF

第三部分就是数据

这下面的官方的描述:

<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();
};

其中getTextEncodinggetStringWidthCharsetAt是个人进行集成了的函数,会对上述的数据进行相应的处理(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开头的一样,分为四种0x00,0x01,0x02,0x03

第二个为MIME type数据了,表示的是什么类型图片,有image/jpeg,image/png…等,
这里的字节数不确定,是用0x00作为字符串的结束标志,来停止读取的,也就是说MIME type数据需要一直读取,知道读取到了0x00也就是我们常见的字符串结束标志\0.

第三个为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()
    };
};

自己对着看吧

到这里三种类型就分析完了

到此基本就可以了,标签分析完成,个人不进行歌曲的数据帧分析。


这里进行编码数据扩充


1.utf-16,即为UCS-2,这种编码会出现两种形式,一个为2字节也就是一个字,一个为四字节也就是两个字,
当第一个字节小于0xD8或者大于0xDF,则是第一种情况,否则就是第二种,其中0xDB?0xDF为代理区(http://blog.csdn.net/wm_1991/article/details/52230716这个地址可以了解更多)
当然在这个音乐文件中有小端和大端区分,所以我们经常会看到如下编码选项 UCS-2 Big Endian(大端),UCS-2 Little Endian(小端)

2.utf-8,这个编码可以说是最操蛋的,网上的解释也参差不齐,俺也懒得去看官网了,这里讲解的只是最常用的,大众的。
这个编码分为三种,一个为1字节(这里很明显是用一个字节来表示英文字母),然后就是2字节,接着就是3字节
区分:

  • 第一字节小于0x80则为1个字节

  • 第一字节大于等于0xC2小于0xE0则是2字节

  • 第一字节大于等于0xE0小于0xF0则是3字节

3.其他的编码就一股脑的读取一个字节就可以了

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将数据转换为数组的函数

MP3文件分析之ID3v2.3版本

原文:http://blog.csdn.net/qq_18661257/article/details/54908775

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!