深耕ffmpeg系列之mp3 demux

120 阅读11分钟

Persistence will dawn on you, turning effort into the light of reality.

从前面翻译ISO/IEC 11172-3 mp3编码标准中可以看出mp3文件的构成大致为:

+------------------------------------------------------+
|                        ID3v2 标签 (可选)               |
|  [ID3v2 Header] + [ID3v2 Frames] + [Padding]         |
+------------------------------------------------------+
|                        音频帧 1                      |
|  [Sync Word] + [Frame Header] + [Side Information] + |
|               [Main Data (Part 1)]                    |
+------------------------------------------------------+
|                        音频帧 2                      |
|  [Sync Word] + [Frame Header] + [Side Information] + |
|               [Main Data (Part 2)]                    |
+------------------------------------------------------+
|                        ...                             |
+------------------------------------------------------+
|                        音频帧 N                      |
|  [Sync Word] + [Frame Header] + [Side Information] + |
|               [Main Data (Last Part)]                 |
+------------------------------------------------------+
|                   Xing/Info 头(可选)                |
|  [Xing Identifier] + [Flags] + [Metadata Fields]      |
+------------------------------------------------------+

下面以一个demo看看mp3 demux(mp3dec.c)的每个接口如何被调用以及如何读取到数据的. 此文看完后可以解答一下几个问题:

  1. mp3_read_probe函数中如何识别此文件是mp3文件?并给出多少匹配分?
  2. mp3_read_header中如何获取到stream info?
  3. mp3_read_packet每次读取多少数据,每一个avpackt中是否都是完整帧?
  4. mp3_seek是如何找到对应seekpoint的数据帧的?

demo code

include <libavformat/avformat.h>

int main(int argc, char *argv[]) {
    AVFormatContext *fmt_ctx = NULL;
    AVPacket *pkt = NULL;
    int ret, stream_index;
    const char *filename;

    filename = "/data/ffmpeg/demo/test.mp3";


    // 打开输入文件
    avformat_open_input(&fmt_ctx, filename, NULL, NULL));
       
    // 获取流信息
    avformat_find_stream_info(fmt_ctx, NULL));

    // 查找第一个音频流
    stream_index = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            stream_index = i;
            break;
        }
    }
 
    // 分配 AVPacket
    pkt = av_packet_alloc();
    
    printf("Reading packets from '%s' (Audio Stream Index: %d):\n", filename, stream_index);
    printf("----------------------------------------\n");
// 循环读取数据包
    while ((ret = av_read_frame(fmt_ctx, pkt)) >= 0) {
        if (pkt->stream_index == stream_index) {
            // 打印当前包的原始数据大小
            printf("Packet Size: %6d bytes | PTS: %lld | DTS: %lld\n",
                   pkt->size,
                   pkt->pts,
                   pkt->dts);
        }
        av_packet_unref(pkt);  // 释放包内存(保留头部)
    }

    if (ret == AVERROR_EOF) {
        printf("----------------------------------------\n");
        printf("End of file reached.\n");
    } else {
        char errbuf[128];
        av_strerror(ret, errbuf, sizeof(errbuf));
        printf("Error occurred: %s\n", errbuf);
    }

    // 清理资源
    av_packet_free(&pkt);
    avformat_close_input(&fmt_ctx);

    return 0;
}

mp3_read_probe

mp3_read_probe总结先行

1.demux.c会从文件中读取2048个字节传入到AVProbeData,并把AVProbeData作为参数传入mp3_read_probe

2.mp3_read_probe首先会忽略空bit,依次从非空bit的第一个字节,第二个字节,第三个字节...开始header字段校验,看看在不同起时位置能解析到多少个正确的frame

3.如果从非空bit的第一个字节开始解析,能正确解析到7个frame,得分51

4.否则,利用解析到正确的最大帧数目和最大帧size作为条件返回得分

5.最后通过header计算出frame_size(一帧内有多少字节的数据),返回。

使用gdb运行demo: gdb mp3_demux_demo

Reading symbols from ./mp3-demux-demo...
(gdb) b mp3_read_probe //设置断点
Function "mp3_read_probe" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mp3_read_probe) pending.
(gdb) r //运行程序
Starting program: /data/ffmpeg/demo/build/mp3-demux-demo 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, mp3_read_probe (p=0x7fffffffdcd0) at libavformat/mp3dec.c:68
68	{
(gdb) bt //查看调用关系
#0  mp3_read_probe (p=0x7fffffffdcd0) at libavformat/mp3dec.c:68
#1  0x00007ffff7dd7526 in av_probe_input_format3 (pd=pd@entry=0x7fffffffdda0, is_opened=is_opened@entry=1, score_ret=score_ret@entry=0x7fffffffdd44) at libavformat/demux.h:135
#2  0x00007ffff7dd7766 in av_probe_input_format2 (pd=pd@entry=0x7fffffffdda0, is_opened=is_opened@entry=1, score_max=score_max@entry=0x7fffffffdd8c) at libavformat/format.c:238
#3  0x00007ffff7dd79bf in av_probe_input_buffer2 (pb=0x555555561d40, fmt=0x555555559048, filename=filename@entry=0x555555556008 "data/ffmpeg/demo/test.mp3", logctx=logctx@entry=0x555555559040, 
    offset=offset@entry=0, max_probe_size=1048576) at libavformat/format.c:312
#4  0x00007ffff7db94a2 in init_input (options=0x7fffffffde20, filename=0x555555556008 "/data/ffmpeg/demo/test.mp3", s=0x555555559040) at libavformat/demux.c:182
#5  avformat_open_input (ps=0x7fffffffdec8, filename=0x555555556008 "/home/mi/data/ffmpeg/demo/test.mp3", fmt=<optimized out>, options=0x0) at libavformat/demux.c:247
#6  0x00005555555552f8 in main (argc=1, argv=0x7fffffffe068) at /data/ffmpeg/demo/mp3_demux.c:13

从上面的backtrace开看,会从mp3文件中读取一段buffer给到mp3_read_probe进行判断当前文件是否为MP3,并给出打分。 用gdb 查看下AVProbeData的信息,可以看出读了2048个字节。 下面继续看下从这2048个字节能获取到什么信息。

(gdb) p *p
$2 = {filename = 0x555555556008 "/data/ffmpeg/demo/test.mp3", buf = 0x555555561ed0 "\377\373\320d", buf_size = 2048, mime_type = 0x0}
static int mp3_read_probe(const AVProbeData *p)

{
    int max_frames, first_frames = 0;
    int whole_used = 0;
    int frames, ret;
    int framesizes, max_framesizes;
    uint32_t header;
    const uint8_t *buf, *buf0, *buf2, *buf3, *end;

    buf0 = p->buf;
    end = p->buf + p->buf_size - sizeof(uint32_t);
    //清除文件开头的空字节
    while (buf0 < end && !*buf0)
        buf0++;

    max_frames = 0;
    max_framesizes = 0;
    buf = buf0;
    //第0个,第1个,第2个...字节匹配MP3标准header
    for (; buf < end; buf = buf2+1) {
        buf2 = buf;
        for (framesizes = frames = 0; buf2 < end; frames++) {
            MPADecodeHeader h;
            int header_emu = 0;
            int available;
            header = AV_RB32(buf2);//解析开头的4个字节,把结果解析到MPADecodeHeader
            ret = avpriv_mpegaudio_decode_header(&h, header);
            if (ret != 0)
                break;
            //下一段buffer的大小
            available = FFMIN(h.frame_size, end - buf2);
            //从上一个header后面,第0个,第1个,第2个...字节匹配MP3标准header
            // 如果匹配到两个及以上个合法的header说明此文件就问题,不是一个合法的mp3文件
            for (buf3 = buf2 + 4; buf3 < buf2 + available; buf3++) {
                uint32_t next_sync = AV_RB32(buf3);
                header_emu += (next_sync & MP3_MASK) == (header & MP3_MASK);
            }
            if (header_emu > 2)
                break;
            //正确的framesize
            framesizes += h.frame_size;
            if (available < h.frame_size) {
                //正确的frame count
                frames++;
                break;
            }
            buf2 += h.frame_size;
        }

        max_frames = FFMAX(max_frames, frames);
        max_framesizes = FFMAX(max_framesizes, framesizes);
        if (buf == buf0) {
            first_frames= frames;
            if (buf2 == end + sizeof(uint32_t))
                whole_used = 1;
        }
   }

    // keep this in sync with ac3 probe, both need to avoid
    // issues with MPEG-files!
    //如果从prob buffer第一个字节开始匹配,匹配到7个以上的正确的frame说明高准却性,得分很高51分
    if (first_frames>=7) return AVPROBE_SCORE_EXTENSION + 1;
    //得分50分
    else if (max_frames>200 && p->buf_size < 2*max_framesizes)return AVPROBE_SCORE_EXTENSION;

    else if (max_frames>=4 && p->buf_size < 2*max_framesizes) return AVPROBE_SCORE_EXTENSION / 2;

    else if (ff_id3v2_match(buf0, ID3v2_DEFAULT_MAGIC) && 2*ff_id3v2_tag_len(buf0) >= p->buf_size)

    return p->buf_size < PROBE_BUF_MAX ? AVPROBE_SCORE_EXTENSION / 4 : AVPROBE_SCORE_EXTENSION - 2;

    else if (first_frames > 1 && whole_used) return 5;

    else if (max_frames>=1 && p->buf_size < 10*max_framesizes) return 1;

    else return 0;

    //mpegps_mp3_unrecognized_format.mpg has max_frames=3

}

我们来看下下面测试MP3文件,看看能得多少分?

00000000: fffb d064 0000 c66c 6924 b32c 36b2 6f4d  ...d...li$.,6.oM
00000010: 04c2 0023 0261 f1ea ba55 d780 293f 3257  ...#.a...U..)?2W
00000020: 4a80 2004 68a0 ad2c 0204 1c90 6907 2b26  J. .h..,....i.+&
00000030: 5443 0c96 b8ac 2070 e5ef 6a8d 999d c04f  TC.... p..j....O
00000040: 22e7 614b f448 0403 a863 017b 9d48 dd4b  ".aK.H...c.{.H.K
00000050: 9127 e59b b107 8e1a 84c3 f28a 6b12 ba09  .'..........k...
00000060: d97c 423b 0a64 ed62 0d8b 4829 ff56 6755  .|B;.d.b..H).VgU
00000070: 9815 cac5 92d9 842e 4368 8cca 85d3 2220  ........Ch...."
00000080: e84b 4c89 f948 726c 1185 0399 48f4 b670  .KL..Hrl....H..p
00000090: 9504 b671 55e5 c2a9 388a 3f8e e60d efac  ...qU...8.?.....
000000a0: 3b29 1a8f 8669 16ab 590b ce2c db30 b483  ;)...i..Y..,.0..
000000b0: 2684 6e6c 1e51 11e1 640d 5a17 50f3 6616  &.nl.Q..d.Z.P.f.
000000c0: 7293 31f3 ec59 5292 040f a8b6 cb65 903c  r.1..YR......e.<
000000d0: e456 8279 2b40 9993 b0ef 597d 9ce5 0c26  .V.y+@....Y}...&
000000e0: 64ae 37d2 4ab2 2927 596f d272 2b7f fd96  d.7.J.)'Yo.r+...
000000f0: a940 0016 6569 4cd2 9959 9d84 f339 93ca  .@..eiL..Y...9..
00000100: 6961 25c9 e11f e733 4a6e 94ca c23b 09e1  ia%....3Jn...;..
00000110: 1cc9 e534 b092 9958 47fb ccd2 9ba5 32b9  ...4...XG.....2.
00000120: 1dc9 e13c 2278 a695 8929 a3c2 7864 f08d  ...<"x...)..xd..
reakpoint 1, mp3_read_probe (p=0x7fffffffdcd0) at libavformat/mp3dec.c:68
68	{
(gdb) n
76	    buf0 = p->buf;
(gdb) n
77	    end = p->buf + p->buf_size - sizeof(uint32_t);
(gdb) n
78	    while (buf0 < end && !*buf0)
(gdb) n
85	    for (; buf < end; buf = buf2+1) {
(gdb) n
92	            header = AV_RB32(buf2);
(gdb) n
93	            ret = avpriv_mpegaudio_decode_header(&h, header);
(gdb) p/x header
$1 = 0xfffbd064
(gdb) n
94	            if (ret != 0)
(gdb) n
97	            available = FFMIN(h.frame_size, end - buf2);
(gdb) p h
$2 = {frame_size = 835, error_protection = 0, layer = 3, sample_rate = 44100, sample_rate_index = 0, bit_rate = 256000, nb_channels = 2, mode = 1, mode_ext = 2, lsf = 0}
(gdb) b mp3dec.c:120
Breakpoint 2 at 0x7ffff7e5c330: file libavformat/mp3dec.c, line 121.
(gdb) c
Continuing.

Breakpoint 2, mp3_read_probe (p=0x7fffffffdcd0) at libavformat/mp3dec.c:121
121	    if   (first_frames>=7) return AVPROBE_SCORE_EXTENSION + 1;
(gdb) p first_frames
$3 = 3
(gdb) p max_frames
$4 = 3
(gdb) p max_framesizes
$5 = 2507
(gdb) n
122	    else if (max_frames>200 && p->buf_size < 2*max_framesizes)return AVPROBE_SCORE_EXTENSION;
(gdb) n
123	    else if (max_frames>=4 && p->buf_size < 2*max_framesizes) return AVPROBE_SCORE_EXTENSION / 2;
(gdb) n
124	    else if (ff_id3v2_match(buf0, ID3v2_DEFAULT_MAGIC) && 2*ff_id3v2_tag_len(buf0) >= p->buf_size)
(gdb) n
126	    else if (first_frames > 1 && whole_used) return 5;
(gdb) n
127	    else if (max_frames>=1 && p->buf_size < 10*max_framesizes) return 1;
(gdb) n
av_probe_input_format3 (pd=pd@entry=0x7fffffffdda0, is_opened=is_opened@entry=1, score_ret=score_ret@entry=0x7fffffffdd44) at libavformat/format.c:195
195	            if (score)
(gdb) p socre
No symbol "socre" in current context.
(gdb) p score
$6 = 1
(gdb) 

从上面gdb得到的信息来看,

  • 通过header 4个字节:0xfffbd064解析到了下面的信息:
$2 = {
      frame_size = 835, 
      error_protection = 0, 
      layer = 3, 
      sample_rate = 44100, 
      sample_rate_index = 0, 
      bit_rate = 256000, 
      nb_channels = 2, 
      mode = 1, 
      mode_ext = 2, 
      lsf = 0}
  • 计算framesize和framecount,最终得分1分。得分第主要是由于prob buffer数据量比较小从而解析到帧数比小于7个。
first_frames: 3
max_frames: 3
max_framesizes:2507
score:1

avpriv_mpegaudio_decode_header

下面结合代码,看看0xfffbd064为何能解析到上述那么多信息。

// 0xfffbd064二进制表示
1111 1111 1111 1011 1101 0000 0110 0100
// ISO/IEC 11172-3规定的MP3 header
//header()
//{
//    syncword                             12 bit  bslbf
//    ID                                    1 bit  bslbf
//    layer                                 2 bit  bslbf
//    protection_bit                        1 bit  bslbf
//    bitrate_index                         4 bit  bslbf
//    sampling_frequency                    2 bit  bslbf
//    padding_bit                           1 bit  bslbf         
//    private_bit                           1 bit  bslbf
//    mode                                  2 bit  bslbf
//    mode-extension                        2 bit  bslbf
//    copyright                             1 bit  bslbf
//    original/copy                         1 bit  bslbf
//    emphasis                              2 bit  bslbf    
//}

int avpriv_mpegaudio_decode_header(MPADecodeHeader *s, uint32_t header)
{
    int sample_rate, frame_size, mpeg25, padding;
    int sample_rate_index, bitrate_index;
    int ret;

    ret = ff_mpa_check_header(header);
    if (ret < 0)
        return ret;
    //第20 bit为1,19bit为1,所以lsf为0
    if (header & (1<<20)) {
        s->lsf = (header & (1<<19)) ? 0 : 1;
        mpeg25 = 0;
    } else {
        s->lsf = 1;
        mpeg25 = 1;
    }
    //右移17位后,最低两bit为01, 与3与后为1,所以layer为3
    s->layer = 4 - ((header >> 17) & 3);
    //右移10位后,最低两bit为11, 所以sample_rate_index为3
    sample_rate_index = (header >> 10) & 3;
    if (sample_rate_index >= FF_ARRAY_ELEMS(ff_mpa_freq_tab))
        sample_rate_index = 0;
    // const uint16_t ff_mpa_freq_tab[3] = { 44100, 48000, 32000 };
    // 3超过了ff_mpa_freq_tab的size,所以sample_rate_index被设置为0
    // 由于lsf和mpeg25都是0,则sample_rate为44100
    sample_rate = ff_mpa_freq_tab[sample_rate_index] >> (s->lsf + mpeg25);
    sample_rate_index += 3 * (s->lsf + mpeg25);
    s->sample_rate_index = sample_rate_index;
    s->error_protection = ((header >> 16) & 1) ^ 1;
    s->sample_rate = sample_rate;
    
    //bitrate_index为13
    bitrate_index = (header >> 12) & 0xf;
    padding = (header >> 9) & 1;
    //extension = (header >> 8) & 1;
    s->mode = (header >> 6) & 3;
    s->mode_ext = (header >> 4) & 3;
    //copyright = (header >> 3) & 1;
    //original = (header >> 2) & 1;
    //emphasis = header & 3;

    //根据mode判断声道数目
    if (s->mode == MPA_MONO)
        s->nb_channels = 1;
    else
        s->nb_channels = 2;

    if (bitrate_index != 0) {
        //ff_mpa_bitrate_tab这个表也是ISO/IEC 11172-3中定义的
        //ff_mpa_bitrate_tab[0][2][13] = 256
        frame_size = ff_mpa_bitrate_tab[s->lsf][s->layer - 1][bitrate_index];
        s->bit_rate = frame_size * 1000;
        switch(s->layer) {
        case 1:
            frame_size = (frame_size * 12000) / sample_rate;
            frame_size = (frame_size + padding) * 4;
            break;
        case 2:
            frame_size = (frame_size * 144000) / sample_rate;
            frame_size += padding;
            break;
        default:
        case 3:
         // 由于MP3标准规定一帧有1152个采样点,那么在不同采样率下,这1152个采样点分辨表多长时间呢?
         // frame_druation = 1152 / sample_rate
         // frame_size = bitrate(kbps) *1000 * frame_duration / 8 (Byte)
         // frame_size = (bitrate(kbps) * 1000 * 1152)/ (sample_rate * 8)
         // frame_size = (bitrate * 1152 * 1000/8)/ sample_rate
         // frame_size = (bitrate * 144000)/ sample_rate
         //因此frame_size = 256 * 144000 / 44100 = 835 字节
            frame_size = (frame_size * 144000) / (sample_rate << s->lsf);
            frame_size += padding;
            break;
        }
        s->frame_size = frame_size;
    } else {
        /* if no frame size computed, signal it */
        return 1;
    }

    return 0;
}

mp3_read_header

首先来看下backtrace,由此可见avformat_open_input中会调用到mp3_read_header,那么再看看mp3_read_header中具体做了什么事情?

结论先行

  1. 创建stream
  2. 填充st->codecpar中的信息,如:codec_id
  3. 读取64K数据进行header字段检查,并检查frame_size是否合法
  4. 设置need_parsing = AVSTREAM_PARSE_FULL_RAW,使能ff_mpegaudio_parser进行分帧处理
  5. 将avio的position再重新seek back到文件头
Reading symbols from ./mp3-demux-demo...
(gdb) b mp3_read_header
Function "mp3_read_header" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mp3_read_header) pending.
(gdb) r
Starting program: /home/mi/data/ffmpeg/demo/build/mp3-demux-demo 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, mp3_read_header (s=0x555555559040) at libavformat/mp3dec.c:363
363	{
(gdb) bt
#0  mp3_read_header (s=0x555555559040) at libavformat/mp3dec.c:363
#1  0x00007ffff7db9129 in avformat_open_input (ps=0x7fffffffdec8, filename=0x555555556008 "/data/ffmpeg/demo/test.mp3", fmt=<optimized out>, options=0x0) at libavformat/demux.h:135
#2  0x00005555555552f8 in main (argc=1, argv=0x7fffffffe068) at /data/ffmpeg/demo/mp3_demux.c:13

源码分析:

static int mp3_read_header(AVFormatContext *s)
{
    FFFormatContext *const si = ffformatcontext(s);
    MP3DecContext *mp3 = s->priv_data;
    AVStream *st;
    FFStream *sti;
    int64_t off;
    int ret;
    int i;

    s->metadata = si->id3v2_meta;
    si->id3v2_meta = NULL;
   //创建stream
    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);
    sti = ffstream(st);

    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
    st->codecpar->codec_id = AV_CODEC_ID_MP3;
    //此flag非常重要,设置后会demux.c中启动ff_mpegaudio_parser进行分帧处理
    sti->need_parsing = AVSTREAM_PARSE_FULL_RAW;
    st->start_time = 0;

    // lcm of all mp3 sample rates
    avpriv_set_pts_info(st, 64, 1, 14112000);

    ffiocontext(s->pb)->maxsize = -1;
    off = avio_tell(s->pb);

    if (!av_dict_count(s->metadata))
        ff_id3v1_read(s);
    //获取文件大小
    if (s->pb->seekable & AVIO_SEEKABLE_NORMAL)
        mp3->filesize = avio_size(s->pb);
    //处理非标准附加的信息
    if (mp3_parse_vbr_tags(s, st, off) < 0)
        avio_seek(s->pb, off, SEEK_SET);

    ret = ff_replaygain_export(st, s->metadata);
    if (ret < 0)
        return ret;

    ret = ffio_ensure_seekback(s->pb, 64 * 1024 + MPA_MAX_CODED_FRAME_SIZE + 4);
    if (ret < 0)
        return ret;

    off = avio_tell(s->pb);
    //获取frame size
    for (i = 0; i < 64 * 1024; i++) {
        uint32_t header, header2;
        int frame_size;
        frame_size = check(s->pb, off + i, &header);
        if (frame_size > 0) {
            ret = check(s->pb, off + i + frame_size, &header2);
            if (ret >= 0 && (header & MP3_MASK) == (header2 & MP3_MASK))
                break;
        } else if (frame_size == CHECK_SEEK_FAILED) {
            av_log(s, AV_LOG_ERROR, "Failed to find two consecutive MPEG audio frames.\n");
            return AVERROR_INVALIDDATA;
        }
    }
    if (i == 64 * 1024) {
        off = avio_seek(s->pb, off, SEEK_SET);
    } else {
        av_log(s, i > 0 ? AV_LOG_INFO : AV_LOG_VERBOSE, "Skipping %d bytes of junk at %"PRId64".\n", i, off);
        off = avio_seek(s->pb, off + i, SEEK_SET);
    }
    if (off < 0)
        return off;

    // the seek index is relative to the end of the xing vbr headers
    for (int i = 0; i < sti->nb_index_entries; i++)
        sti->index_entries[i].pos += off;

    /* the parameters will be extracted from the compressed bitstream */
    return 0;
}

mp3_read_packet

从mp3_read_packet函数中可以看到每次从avio中读取1024字节。

(gdb) p *pkt
$3 = {buf = 0x555555572640, pts = -9223372036854775808, dts = -9223372036854775808, 
  data = 0x55555555bd40 "...", 'size = 1024', stream_index = 0, flags = 0, side_data = 0x0, side_data_elems = 0, duration = 0, pos = 46080, 
  opaque = 0x0, opaque_ref = 0x0, time_base = {num = 0, den = 1}}

但是从av_read_frame中得到的avpackt.size是836:

Packet Size:    835 bytes | PTS: 0 | DTS: 0
Packet Size:    836 bytes | PTS: 368640 | DTS: 368640
Packet Size:    836 bytes | PTS: 737280 | DTS: 737280
Packet Size:    836 bytes | PTS: 1105920 | DTS: 1105920
Packet Size:    836 bytes | PTS: 1474560 | DTS: 1474560
Packet Size:    836 bytes | PTS: 1843200 | DTS: 1843200
Packet Size:    836 bytes | PTS: 2211840 | DTS: 2211840
Packet Size:    836 bytes | PTS: 2580480 | DTS: 2580480
Packet Size:    836 bytes | PTS: 2949120 | DTS: 2949120
Packet Size:    836 bytes | PTS: 3317760 | DTS: 3317760
Packet Size:    836 bytes | PTS: 3686400 | DTS: 3686400
Packet Size:    836 bytes | PTS: 4055040 | DTS: 4055040
Packet Size:    836 bytes | PTS: 4423680 | DTS: 4423680

原因是在ff_mpegaudio_parser中会进行分帧处理,因为在mp3_read_header中设置了

sti->need_parsing = AVSTREAM_PARSE_FULL_RAW;

原因是在ff_mpegaudio_parser中会进行分帧处理流程如下:

输入数据

  • 包1[帧1头...部分数据]
  • 包2[剩余数据...帧2头]

解析步骤

  1. 处理包1:发现帧1头,但数据不足,返回 -1,数据暂存到 pc->buffer
  2. 处理包2​​:合并 pc->buffer 和包2,找到完整帧1,返回帧数据;残留数据(如帧2头)暂存到 pc->buffer
#define MP3_PACKET_SIZE 1024
static int mp3_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    MP3DecContext *mp3 = s->priv_data;
    int ret, size;
    int64_t pos;

    size = MP3_PACKET_SIZE;//每次读取1024字节数据
    pos = avio_tell(s->pb);
    if (mp3->filesize > ID3v1_TAG_SIZE && pos < mp3->filesize)
        size= FFMIN(size, mp3->filesize - pos);

    ret = av_get_packet(s->pb, pkt, size);
    if (ret <= 0) {
        if(ret<0)
            return ret;
        return AVERROR_EOF;
    }

    pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
    pkt->stream_index = 0;

    return ret;
}

mp3_seek

此函数随长,只需要弄懂一句话即可:

ie->pos = av_rescale(timestamp, filesize, st->duration) + si->data_offset;

也就是: seek_pos = (seek_timestamp / st->duration) * filesize; 即:文件seek位置 = (seek时间戳/文件总时长)*文件总字节数

算出seek_pos后直接调用avio_seek,seek到想要快进的位置,当下次再avio_read就可以读取到seek后的新数据了。

static int mp3_seek(AVFormatContext *s, int stream_index, int64_t timestamp,
                    int flags)
{
    FFFormatContext *const si = ffformatcontext(s);
    MP3DecContext *mp3 = s->priv_data;
    AVIndexEntry *ie, ie1;
    AVStream *st = s->streams[0];
    FFStream *const sti = ffstream(st);
    int64_t best_pos;
    int fast_seek = s->flags & AVFMT_FLAG_FAST_SEEK;
    int64_t filesize = mp3->header_filesize;

    if (filesize <= 0) {
        int64_t size = avio_size(s->pb);
        if (size > 0 && size > si->data_offset)
            filesize = size - si->data_offset;
    }

    if (mp3->xing_toc && (mp3->usetoc || (fast_seek && !mp3->is_cbr))) {
        int64_t ret = av_index_search_timestamp(st, timestamp, flags);

        // NOTE: The MP3 TOC is not a precise lookup table. Accuracy is worse
        // for bigger files.
        av_log(s, AV_LOG_WARNING, "Using MP3 TOC to seek; may be imprecise.\n");

        if (ret < 0)
            return ret;

        ie = &sti->index_entries[ret];
    } else if (fast_seek && st->duration > 0 && filesize > 0) {
        if (!mp3->is_cbr)
            av_log(s, AV_LOG_WARNING, "Using scaling to seek VBR MP3; may be imprecise.\n");

        ie = &ie1;
        timestamp = av_clip64(timestamp, 0, st->duration);
        ie->timestamp = timestamp;
        ie->pos       = av_rescale(timestamp, filesize, st->duration) + si->data_offset;
    } else {
        return -1; // generic index code
    }

    best_pos = mp3_sync(s, ie->pos, flags);
    if (best_pos < 0)
        return best_pos;

    if (mp3->is_cbr && ie == &ie1 && mp3->frames) {
        int frame_duration = av_rescale(st->duration, 1, mp3->frames);
        ie1.timestamp = frame_duration * av_rescale(best_pos - si->data_offset, mp3->frames, mp3->header_filesize);
    }

    avpriv_update_cur_dts(s, st, ie->timestamp);
    return 0;
}