MP3文件解析详解

此文章为skoootxl2008原创,转载请注明出处,尊重他人劳动成果。

http://blog.youkuaiyun.com/skoootxl2008/article/details/23948829

概述


     MP3文件是我们常见的音频文件格式,它内部的结构如何?音乐播放器是如何解析MP3文件的呢?下面我就说说我对MP3的理解,以及源码的分析。
     MP3是MPEG-1 Layer3 的简称,MPEG规范中有MPEG-1、MPEG-2、MPEG-4、MPEG-7、MPEG-21,每个就不具体讲解了,想知道就百度,只是让大家知道MP3是规范系列中的位置。MPEG-1规范定义的是音频文件,MPEG-1有3层,MPEG-1 Layer1、MPEG-1 Layer2、MPEG-1 Layer3。这三个层次的区别是他们的编解码器的复杂程度和压缩比不一样,层次越高越复杂,压缩包越高。MPEG-1 Layer3的压缩比可以达到10:1~12:1。

     音频编码原理大致原理如下:

      
     
     
     上面这幅图是从Adobe Audition中进行放大后截出来的,波形就是一段模拟音频信号,上面的正方形点就是采样点,每个点我们需要保存的信息就是它的幅度,我们可以用8位或者是16位二进制编码进行表示,目前一般的音乐都采用16位表示一个点。编码位数表示是编码的精度,当然位数越多,精度越高,音质越好。

     MP3的文件构成都是由帧构成的,下面就是MP3文件的大致结构:
     
     ID3是用来存放一些曲目信息的,比如说歌手名、专辑、封面、年代、风格等信息。至于ID3V1、ID3V2是指ID3的版本,ID3现在已经有5个版本了,看看代码我们就知道了。
     enum Version {
        ID3_UNKNOWN,
        ID3_V1,
        ID3_V1_1,
        ID3_V2_2,
        ID3_V2_3,
        ID3_V2_4,
    };
     这个是ID3.h中定义的一个枚举变量。可以看到有ID3V1.0、ID3V1.1、ID3V2.2、ID3V2.3、ID3V2.4。版本越高定义的信息就越丰富,大家可能有注意到,ID3V2是可选的,有的音频文件就没有ID3V2,用UE打开MP3文件时,第一个字节就是FF FB,直接就是数据的开始,如果是IDV2的话,它的标志是“ID3”,ID3V1的标志是“TAG”,在实例分析的时候我们会看到。这是MP3文件大致的介绍,下面详细介绍ID3标签帧和数据帧的帧结构。     

     标签帧结构
 ID3V2
  
帧头   

ID3V2帧头长度为10个字节,位于文件首部,其数据结构如下:

char Header[3]; /* 字符串 "ID3" */

char Ver;       /* 版本号ID3V2.3 就记录3 */

char Revision; /* 副版本号此版本记录为0 */

char Flag;     /* 存放标志的字节,这个版本只定义了三位,很少用到,可以忽略 */

char Size[4]; /* 标签大小,除了标签头的10 个字节的标签帧的大小 */

标签大小为四个字节,但每个字节只用低7位,最高位不使用,恒为0,其格式如下:

0x0xxx 0x0xxx 0x0xxx 0x0xxx

计算公式如下:

ID3V2_frame_size = (int)(Size[0] & 0x7F) << 21

                 | (int)(Size[1] & 0x7F) << 14

                  | (int)(Size[2] & 0x7F) << 7

                  | (int)(Size[3] & 0x7F);
具体的描述会在下面的源码分析中讲到,如果我们通过比较发现了这个帧头,我就可以开始获取ID3v2中的信
息了。

标签帧

     标签帧的帧头同样也是10个字节

char FrameID[4];   /*用四个字符标识一个帧,说明其内容 ,从下面代码中可以看到,例如“TALB”*/

char Size[4];      /* 帧内容的大小,不包括帧头,不得小于1 */

char Flags[2];     /* 存放标志,只定义了6 位,此处不再说明 */

    static const Map kMap[] = {
        { kKeyAlbum, "TALB", "TAL" },
        { kKeyArtist, "TPE1", "TP1" },
        { kKeyAlbumArtist, "TPE2", "TP2" },
        { kKeyComposer, "TCOM", "TCM" },
        { kKeyGenre, "TCON", "TCO" },
        { kKeyTitle, "TIT2", "TT2" },
        { kKeyYear, "TYE", "TYER" },
        { kKeyAuthor, "TXT", "TEXT" },
        { kKeyCDTrackNumber, "TRK", "TRCK" },
        { kKeyDiscNumber, "TPA", "TPOS" },
        { kKeyCompilation, "TCP", "TCMP" },
    };

我们找到帧头后,根据帧内容的大小,就可以读取出帧标识(“TPE1”艺术家)所对应的内容(如:刘德华)。通过遍历我们把所有的这些信息保存在meta数据中,见如下代码:
meta->setCString(kMap[i].key, s);
其中s就是我们解析出来的内容。

下面就看看动力火车的一首歌的ID3V2信息:

可以看到从地址0000000h开始就是标识符“ID3”,说明这个有ID3V2信息,里面圈出来的就是标签帧的帧头中的FrameID。

下面看看一首没有ID3V2信息的歌曲,对比一下:



这个张国荣的一首歌暴风一族,它就没有ID3V2信息,直接就是数据帧的帧头(数据帧帧头格式下面再讲,相信我画红色框的4个字节就是数据帧的帧头)。

看完了帧头,再看看帧尾的ID3V1吧:

这个是动力火车的“背叛情歌”的结尾ID3V1,它的标志是“TAG”。ID3V2的标识是“ID3”。
下面详细讲解ID3V1的帧结构。

ID3V1

ID3V1的数据结构如下:

char Header[3];    /* 标签头必须是"TAG"否则认为没有标签 */

char Title[30];    /* 标题 ,可以看到上面选择部分确实是30个字节”*/

char Artist[30];   /* 作者 */

char Album[30];    /* 专集 */

char Year[4];      /* 出品年代 */

char Comment[28]; /* 备注 */

char reserve;      /* 保留 */

char track;;       /* 音轨 */

char Genre;        /* 类型 */

     从这样看来ID3V1的帧结构貌似比ID3v2要简单得多,所以选择好多MP3都没有使用ID3V2,它只是作为ID3V1的一个扩展。 

     

     数据帧结构

          数据帧的帧头的长度是4个字节,别看只有32bit的信息,但是它的信息量不小,因为这4个字节里面存放的不是字符,如果是字符的话就顶多就4个字符信息。这个里面存放的是标志位,然后通过真值表查出对于的信息,下面就贴一张网上的图片:

      MP3数据帧头字节使用说明

名称

位长

          

同步信息

11

12字节

所有位均为1,第1字节恒为FF

版本

2

00-MPEG 2.5   01-未定义     10-MPEG 2     11-MPEG 1

2

00-未定义      01-Layer 3     10-Layer 2      11-Layer 1

CRC校验

1

0-校验        1-不校验

位率

4

3字节

 取样率,单位是kbps,例如采用MPEG-1 Layer 364kbps是,值为0101

bits

V1,L1

V1,L2

V1,L3

V2,L1

V2,L2

V2,L3

0000

free

free

free

free

free

free

0001

32

32

32

32(32)

32(8)

8 (8)

0010

64

48

40

64(48)

48(16)

16 (16)

0011

96

56

48

96(56)

56(24)

24 (24)

0100

128

64

56

128(64)

64(32)

32 (32)

0101

160

80

64

160(80)

80(40)

64 (40)

0110

192

96

80

192(96)

96(48)

80 (48)

0111

224

112

96

224(112)

112(56)

56 (56)

1000

256

128

112

256(128)

128(64)

64 (64)

1001

288

160

128

288(144)

160(80)

128 (80)

1010

320

192

160

320(160)

192(96)

160 (96)

1011

352

224

192

352(176)

224(112)

112 (112)

1100

384

256

224

384(192)

256(128)

128 (128)

1101

416

320

256

416(224)

320(144)

256 (144)

1110

448

384

320

448(256)

384(160)

320 (160)

1111

bad

bad

bad

bad

bad

bad

V1 - MPEG 1    V2 - MPEG 2 and MPEG 2.5
L1 - Layer 1     L2 - Layer 2     L3 - Layer 3
"free"
 表示位率可变    "bad"  表示不允许值

采样频率

2

采样频率,对于MPEG-1  00-44.1kHz    01-48kHz    10-32kHz      11-未定义

          对于MPEG-2  00-22.05kHz   01-24kHz    10-16kHz      11-未定义

          对于MPEG-2.5 00-11.025kHz 01-12kHz    10-8kHz       11-未定义

帧长调节

1

用来调整文件头长度,0-无需调整,1-调整,具体调整计算方法见下文。

保留字

1

没有使用。

声道模式

2

4字节

表示声道, 00-立体声Stereo    01-Joint Stereo    10-双声道        11-单声道

扩充模式

2

当声道模式为01是才使用。

Value

强度立体声

MS立体声

00

off

off

01

on

off

10

off

on

11

on

on

版权

1

文件是否合法,0-不合法   1-合法

原版标志

1

是否原版,    0-非原版   1-原版

强调方式

2

用于声音经降噪压缩后再补偿的分类,很少用到,今后也可能不会用。

00-未定义     01-50/15ms     10-保留       11-CCITT J.17


     看这么多还不如来个实例分析理解得更加清楚,我们就拿“暴风一族“来进行分析吧,”暴风一族“的帧头是
     FF FB 90 04 换成二进制后就是: 1111 1111 1111 1011 1001 0000 0000 0100




     以上只是我们对着表得出的结果,但是是不是真的对呢,我们用一个第三方软件进行检验一下。
     
     

     通过红线框内的信息与刚刚查表查出来的信息对吧,验证了我们查表查出来的是正确的。

    Duration、帧长的计算

     在计算Duration、帧长前,我们先看看一张表:      

 

MPEG 1

MPEG 2 (LSF)

MPEG 2.5 (LSF)

Layer I

384

384

384

Layer II

1152

1152

1152

Layer III

1152

576

576

     
     这个表中列的是MPEG规范中规定的不同层中每帧的采样点的个数,这个是固定的,比如MP3中每一个数据帧中的采样点数就是1152个。

     Duration
          对于CBR的MP3文件,Duration的计算方式是
     时长 = 文件大小*8/比特率(秒);//乘以8是为了都统一成bit来进行计算。
     
     例如上面截图中的”暴风一族“
     文件大小是5396689Byte,比特率是128kbps
     duration = 5396689*8/128000 
                  = 337.29s
      转换成分钟就是:5分37秒,对比一下是正确的。

      帧长的计算
     




MP3Extractor源码分析

     概述
     通过上面的讲解,我们已经知道了MP3文件大致的结构,现在我们结合代码进行分析,看看实际应用中如何来解析一个MP3文件。
     代码路径在:
     frameworks/av/media/libstagefright/MP3Extractor.cpp
  
     由于MP3是由帧构成的,所以找到帧头在MP3文件解析中是非常重要的,找到了帧头,我们就可以知道这个真的长度以及各种信息。下面这个函数的目的就是为了找每帧的帧头,虽然这个函数笔记长,但是我还是详细分析下,我个人觉得非常重要。
     Resync函数分析
//this purpose is find a data frame header
static bool Resync(
        const sp<DataSource> &source, uint32_t match_header,
        off64_t *inout_pos, off64_t *post_id3_pos, uint32_t *out_header) {
// inout_pos就是要开始同步的位置 ,即把inout_pos移向最近的一个帧头位置。     
    if (post_id3_pos != NULL) {
        *post_id3_pos = 0;
    }
     //这个if语句里面所做的时间就是如果这个MP3文件有ID3V2的话就直接跳过ID3V2。
    if (*inout_pos == 0) {
         //下面是个无限循环,开始查找ID3V2的标志“ID3”,虽然说是个
         //无限循环,只要条件满足,很快就可以退出循环了。
        for (;;) {
            uint8_t id3header[10];
            //读取10个字节的数据,因为文件头就占10个字节的数据。
            if (source->readAt(*inout_pos, id3header, sizeof(id3header))
                    < (ssize_t)sizeof(id3header)) {
                return false;
            }
          //判断文件头的前三个字节是不是“ID3”,如果不是ID3,返回一个非0值,
          //说明没有ID3V2信息,直接break跳出循环。
          //我们要找的是数据帧的帧头。
            if (memcmp("ID3", id3header, 3)) {
                    ALOGD("------------>Find ID3, id3header");
                break;
            }

            // Skip the ID3v2 header.
           //这个地方是发现文件有ID3V2信息,我们要跳过去,所以要知道ID3V2信息占了多长
          //ID3V2的size位于帧头的后4个字节,而且只用到每个字节的后7位
          //所以每个字节要与0x7f进行与操作,存放的形式为0x0xxx 0x0xxx 0x0xxx 0x0xxx
          //这个len就是ID3V2的长度
            size_t len =
                ((id3header[6] & 0x7f) << 21)
                | ((id3header[7] & 0x7f) << 14)
                | ((id3header[8] & 0x7f) << 7)
                | (id3header[9] & 0x7f);

            len += 10; 

          *inout_pos += len;
               ALOGD("len = %d, inout_pos = %p", len, inout_pos);
        }

        if (post_id3_pos != NULL) {
            *post_id3_pos = *inout_pos;
        }
    }

    off64_t pos = *inout_pos;
    bool valid = false;

    const size_t kMaxReadBytes = 1024;
    const size_t kMaxBytesChecked = 128 * 1024;
    uint8_t buf[kMaxReadBytes];
    ssize_t bytesToRead = kMaxReadBytes;
    ssize_t totalBytesRead = 0;
    ssize_t remainingBytes = 0;
    bool reachEOS = false;
    uint8_t *tmp = buf;
     ALOGD("post_id3_pos = %lld", post_id3_pos);

     //下面这个do-while()循环就是为了找到数据帧的帧头。
    do {
          //指针的位置超出了要查找的最大长度就不再查找了。
        if (pos >= *inout_pos + kMaxBytesChecked) {
            // Don't scan forever.
            ALOGD("giving up at offset %lld", pos);
            break;
        }
          //read 1024 byte to buf
          //在这里读取了1024个字节,这个if语句只会走一次。
          //这里之所以与4作比较是因为header的长度是4个字节,如果都不够4个字节了,就需要
          //重新读取文件。
        if (remainingBytes < 4) {
            if (reachEOS) {
               break;
            } else {
                memcpy(buf, tmp, remainingBytes);
                bytesToRead = kMaxReadBytes - remainingBytes;

                /*
                 * The next read position should start from the end of
                 * the last buffer, and thus should include the remaining
                 * bytes in the buffer.
                 */

                totalBytesRead = source->readAt(pos + remainingBytes,
                                                buf + remainingBytes,
                                                bytesToRead);
                if (totalBytesRead <= 0) {
                    break;
                }
                reachEOS = (totalBytesRead != bytesToRead);
                totalBytesRead += remainingBytes;
                remainingBytes = totalBytesRead;
                tmp = buf;
                continue;
            }
        }
          ALOGD("remainingBytes = %d", remainingBytes);
          //把tmp这个指针开始往后4个字节,转换成一个32位的无符号整形
        uint32_t header = U32_AT(tmp);
          //如果不符合我们的规则,就继续查找
        if (match_header != 0 && (header & kMask) != (match_header & kMask)) {
               ALOGD("Go on to find header !!");
            ++pos;
            ++tmp;
            --remainingBytes;
            continue;
        }

        ALOGV("found possible 1st frame at %lld (header = 0x%08x)", pos, header);
          //如果不符合我们的规则,就继续查找
        size_t frame_size;
        int sample_rate, num_channels, bitrate;
        if (!GetMPEGAudioFrameSize(
                    header, &frame_size,
                    &sample_rate, &num_channels, &bitrate)) {
          //这个pos最终就是要指向文件头。if语句括号里面判断当前的header不是文件头。
          //所以pos往后面移一位,继续查找。
          //tmp也向后移,这两个要保持同步,因为tmp是从pos那个位置读取出来的buffer
          //header是从tmp出开始取4个字节的。 
          //往前移了一位,所以剩下没有检查的字节数remainingBytes就应该减1,如果少于4个字节会重新读取。
           ++pos; 
            ++tmp;
            --remainingBytes;
            continue;
        }


        //在这里当前pos指向的这个header已经确认是正确的帧头了。
         //但是为了保险起见,还是继续测试后面的3帧是否能够正确读取得到
          //下一帧的位置当然就是当前header的地址加上当前帧的长度 就是下一帧的
          //帧头,如下面的test_pos,然后我们判断下从test_pos开始读取4个字节
          //是不是一个帧头。

        off64_t test_pos = pos + frame_size;
          
        valid = true;
        for (int j = 0; j < 3; ++j) {
            uint8_t tmp[4];
            if (source->readAt(test_pos, tmp, 4) < 4) {
                valid = false;
                break;
            }
              //获取下一帧的帧头
            uint32_t test_header = U32_AT(tmp);

            ALOGD("subsequent header is %08x", test_header);

            if ((test_header & kMask) != (header & kMask)) {
                valid = false;
                break;
            }

            size_t test_frame_size;
            if (!GetMPEGAudioFrameSize(
                        test_header, &test_frame_size)) {
                valid = false;
                break;
            }

            ALOGD("found subsequent frame #%d at %lld", j + 2, test_pos);
               //继续跳到下一阵的帧头位置。
            test_pos += test_frame_size;
        }

        if (valid) {
         // 如果都没有问题,我们就把同步好的地址传回。
            *inout_pos = pos;
             if (out_header != NULL) {
                *out_header = header;
            }
        } else {
            ALOGD("no dice, no valid sequence of frames found.");
        }

        ++pos;
        ++tmp;
        --remainingBytes;
    } while (!valid);
     
    return valid;
}

     以上就是MP3Extractor中最重要的一个函数,基本已经分析完了,下面讲解下我觉得第二重要的函数,就是read()函数,看看解析器是如何从MP3里面读取数据的。

     read函数分析

          这个函数里面就做了三件事,简单点说就是:seek、Resync、read。
          首先判断是否有seek的操作,如果有的话,通过ReadOptions对象获取要seek到的哪里去,函数如下。
     options->getSeekTo(&seekTimeUs, &mode)
     结果存在seekTimeUs里面,然后通过seekTimeUs来计算出当前指针应该处于哪个位置。
     mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 8000000;这个应该不用解释了,就是S = S0 + Vt 的关系。虽然算出了一个新的指针,但是当前指针不一定刚刚指在数据帧的帧头。所以首先进行判断当前是不是帧头,如果是就没有必要在同步了,如果不是帧头,那么就需要做同步。看看下面的代码:
     
     for(; ; ;) { 
          ... ....
          //has sync
        if ((header & kMask) == (mFixedHeader & kMask)
            && GetMPEGAudioFrameSize(
                header, &frame_size, &sample_rate, NULL,
                &bitrate, &num_samples)) {
               .......
               //如果已经指在帧头位置,那么就直接跳出循环。
            break;
        }

        // Lost sync. 这里是没有指在帧头,所以要同步,调用Resync
        off64_t pos = mCurrentPos;
        if (!Resync(mDataSource, mFixedHeader, &pos, NULL, NULL)) {
               // 同步不上就返回错误。
            buffer->release();
            buffer = NULL;
            return ERROR_END_OF_STREAM;
        }
        mCurrentPos = pos;
          //同步上了,得到新的地址,然后再去读取数据。
        // Try again with the new position.
    }
     ... ...
}     
     等从上面的for循环出来后,mCurrentPos 指向的就是帧头位置了,现在就可以开始读取音频数据了。
     ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), frame_size);
     MP3Extractor的源码分析就到此结束。
解析MP3音频格式 一、概述 MP3 文件是由帧(frame)构成的,帧是MP3 文件最小的组成单位。MP3 的全称应为MPEG1 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 文件,并使压缩后的文件在回放时能够达到比较接近原音源的声音效果。 名词解释: 比特率是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,传送的数据越大。 比特率比特率表示经过编码(压缩)后的音、视频数据每秒钟需要用多少个比特来表示,而比特就是二进制里面最小的单位,要么是0,要么是1。比特率与音、视频压缩的关系,简单的说就是比特率越高,音、视频的质量就越好,但编码后的文件就越大;如果比特率越少则情况刚好相反。 采样率(也称为采样速度或者采样频率)定义了每秒从连续信号中提取并组成离散信号的采样个数,单位用赫兹(Hz)来表示。采样频率的倒数是采样周期(也称为采样时间),它表示采样之间的时间间隔。这里要注意不要将采样率与位速相混淆。 二、MP3文件整体结构 MP3文件大体分为三部分:TAG_V2(ID3V2),Frame, TAG_V1(ID3V1) ID3V2标签帧 ID3v2在文件头,以字符串“ID3”为标志,包含了演唱者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量。 音频数据帧 一系列的帧,个数由文件大小和帧长决定,每个FRAME的长度可能不固定,也可能固定,由位率bitrate决定,每个FRAME又分为帧头和数据实体两部分,帧头记录了mp3的位率,采样率,版本等信息,每个帧之间相互独立 ID3V1标签帧 ID3v1在文件结尾,以字符串“TAG”为标记,其长度是固定的128个字节,包含了演唱者、歌名、专辑、年份等信息。 三、MP3结构具体分析 1.ID3V2标签 每个ID3V2.3 的标签都一个标签头和若干个标签帧或一个扩展标签头组成。关于曲目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必要的,但每个标签至少要有一个标签帧。标签头和标签帧一起顺序存放在MP3 文件的首部。 标签头 在文件的首部顺序记录10 个字节的ID3V2.3 的头部。数据结构如下: char Header[3]; /*必须为"ID3"否则认为标签不存在*/ char Ver; /*版本号ID3V2.3 就记录3*/ char Revision; /*副版本号此版本记录为0*/ char Flag; /*存放标志的字节,这个版本只定义了三位,稍后详细解说*/ char Size[4]; /*标签大小,包括标签头的10 个字节和所有的标签帧的大小*/ 注:最后4个字节表示ID3V2标签的大小,在实际寻找首帧的过程中,我发现大部分的mp3文件的标签大小是包含标签头的,但有的又是不包含的,可能是某些mp3编码器写标签的BUG,所以为了兼容只好认为其是包含的,如果按大小找不到,再向后搜索,直到找到首帧为止。 计算ID3V2标签帧的大小: 最后四个字节表示,但每个字节只用7 位,最高位不使用恒为0。所以格式如下: 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 计算大小时要将0 去掉,得到一个28 位的二进制数,就是标签大小(不懂为什么要这样做),计算公式如 下: int ID3V2_Size; ID3V2_Size = (Size[0]&0x7F;)*0x200000 +(Size[1]&0x7F;)*0x4000 +(Size[2]&0x7F;)*0x80 +(Size[3]&0x7F;) (2) 标签帧 每个标签帧都有一个10 个字节的帧头和至少一个字节的不固定长度的内容组成。它们也是顺序存放在文件中,和标签头和其他的标签帧也没有特殊的字符分隔。得到一个完整的帧的内容只有从帧头中的到内容大小后才能读出,读取时要注意大小,不要将其他帧的内容或帧头读入。 一般我们从标签头中获取信息计算出整个ID3V2标签的大小即可,对于ID3V2的标签帧就不再仔细分析。 2.音频数据 数据帧往往有多个,至于有多少,由文件大小和帧大小来决定。每个帧都有一个四字节长的帧头,接下来可能有两个字节的CRC校验,其存在由帧头中的具体信息决定。接着就是帧的实体数据,也就是MAIN_DATA了。 (1)音频数据帧的帧头 格式如下: AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM 符号 长度(bit) 位置(bit) 描述 A 11 (31~21) 帧同步(所有位置1) B 2 (20~19) MPEG 音频版本ID 00 – MPEG 2.5 01 – 保留 10 – MPEG 2 (ISO/IEC 13818-3) 11 – MPEG 1 (ISO/IEC 11172-3) C 2 (18~17) Layer描述 00 - 保留 01 - Layer III 10 - Layer II 11 - Layer I D 1 (16) 校验位(0 - 紧跟帧头后有16位即2个字节用作CRC校验1 - 没有校验) E 4 (15~12) 比特率(位率)(见比特率索引表) F 2 (11~10) 采样率(见采样率索引表) G 1 (9) 填充位(填充用来达到正确的比特率。) 0 – 没有填充 1 – 填充了一个额外的空位 H 1 (8) 私有bit,可以用来做特殊应用。例如可以用来触发应用程序的特殊事件。 I 2 (7~6) 声道 00 立体声 01 联合立体声(立体声) 10 双声道(立体声) 11 单声道(单声) J 2 (5~4) 扩展模式(仅在联合立体声时有效) 扩展模式用来连接对立体声效果无用的信息,来减少所需的资源。这两个位在联合立体声模式下有编码器动态指定。 K 1 (3) 版权 0无版权 1有版权 L 1 (2) 原创 0 原创拷贝 1 原创 M 2 (1) 强调 00 - 无 01 - 50/15 ms 10 - 保留 11 - CCIT J.17 【帧头信息表】 索引值 MPEG1 MPEG2&MPEG2;.5 Layer1 Layer2 Layer3 Layer1 Layer2&3 0000 Free 0001 32 32 32 32 8 0010 64 48 40 48 16 0011 96 56 48 56 24 0100 128 64 56 64 32 0101 160 80 64 80 40 0110 192 96 80 96 48 0111 224 112 96 112 56 1000 256 128 112 128 64 1001 288 160 128 144 80 1010 320 192 160 160 96 1011 352 224 192 176 112 1100 384 256 224 192 128 1101 416 320 256 224 144 1110 448 384 320 256 160 1111 Bad 【比特率索引表】 Bits MPEG1 MPEG2 MPEG3 00 44100 22050 11025 01 48000 24000 12000 10 32000 16000 8000 11 保留 【采样率索引表】 (2)如何计算音频数据帧长度 我们首先区分两个术语:帧大小和帧长度。帧大小即每帧采样数表示一帧中采样的个数,这是恒定值。其值入下表所示 MPEG1 MPEG2 MPEG2.5 Layer1 384 384 384 Layer2 1152 1152 1152 Layer3 1152 576 576 【每帧采【每帧采样数表】 帧长度是压缩时每一帧的长度,包括帧头。它将填充的空位也计算在内。LayerI的一个空位长4字节,LayerII和LayerIII的空位是1字节。当读取MPEG文件时必须计算该值以便找到相邻的帧。 注意:因为有填充和比特率变换,帧长度可能变化。 从头中读取比特率,采样频率和填充, LyaerI使用公式: 帧长度(字节) = 每帧采样数 / 采样频率(HZ) * 比特率(bps)/ 8 +填充 * 4 LyerII和LyaerIII使用公式: 帧长度(字节)= 每帧采样数 / 采样频率(HZ) * 比特率(bps)/ 8 + 填充 例: LayerIII 比特率 128000,采样频率 44100,填充0 =〉帧大小 417字节 (3)计算每帧的持续时间 之前看了一些文章都说mp3的一帧的持续时间是26ms,结果在实际程序的编写中发现无法正确按时间定位到帧,然后又查了一些文章才知道,所谓26ms一帧只是针对MPEG1 Layer III而且采样率为44.1KHz来说是对的,但mp3文件并不都是如此,其实这个时间也是可以通过计算来获得,下面给出计算公式 每帧持续时间(秒) = 每帧采样数 / 采样频率(HZ) 可以这么理解:每帧采用数就是要采取的总数,采样率就是采取的速度,相除 就得到时间。 这样通过计算可知 MPEG1 Layer III 采样率为44.1KHz的一帧持续时间为26.12...不是整数,不过我们权且认为它就是26毫秒吧。 如果是MPEG2 Layer III 采样率为16KHz的话那一帧要持续36毫秒,这个相差还是蛮大的,所以还是应该通过计算来获的,当然可以按MPEG版本,层数和采样率来建一个表,这样直接查表就可以知道时间了。 3.ID3V1标签 ID3v1标签用来描述MPEG音频文件。包含艺术家,标题,唱片集,发布年代和流派。另外还有额外的注释空间。位于音频文件的最后固定为128字节。可以读取该文件的最后这128字节获得标签。以最后128个字节的头三个字节为“TAG”作为有ID3V1的判断依据。 结构如下: AAABBBBB BBBBBBBB BBBBBBBB BBBBBBBB BCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDEEE EFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFG 符号 长度(byte) 位置 描述 A 3 (0~2) 标签标志。如果存在标签并且正确的话,必须包含'TAG'。 B 30 (3~32) 标题 C 30 (33~62) 艺术家 D 30 (63~92) 唱片集 E 4 (93~96) 年代 F 30 (97~126) 注释 G 1 (127) 流派 有了上述的这些信息,我们就可以自己写代码,从MP3文件中抓取信息以及修改文件名了。但是,如果真的想写一个播放软件,还是需要读它的数据帧,并进行解码。 四、解析方法 当你想读取MPEG文件的信息时,解析前三个字节,判断是否有ID3V2标签,有则根据上面的方法算出ID3V2标签的总大小,这样就找到了音频数据帧的第一帧,读取它的头信息,获取比特率、采样率、MPEG版本号、Layer描述号等信息,根据上面提供的方法算出每帧的长度和每帧持续时间,对于定比特率的其它帧是相同的,也就是说解析第一帧就达到了目的。但这也不是所有情况。变比特率的MPEG文件使用使用所谓比特变换,也就是说每一帧的比特率依照具体内容变化。这时就需要你每一帧都解析
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值