多媒体科技音视频开发集锦FFmpeg

FFmpeg中mp4的demuxer(mov.c)代码阅读

2019-05-08  本文已影响4人  smallest_one

目录

  1. 参考
  2. 概述
  3. mov_read_header
  4. mov_read_packet
  5. mov_read_seek
  6. mov_read_close

1. 参考

2. 概述

mp4文件格式解析 文章对mp4文件格式进行了了解,本文主要从代码角度,学习FFmpeg中对mp4的解析、读取数据和seek等操作的实现。

以下是mp4主要box的示意图,解析部分主要分析这些box。


mp4主要box说明

3. mov_read_header

mov_read_header的调用时机如下图所示。


AVInputFormat.read_header.png

mov_read_header的工作

3.1 mov_read_default

static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int64_t total_size = 0;
    MOVAtom a;
    int i;
    c->atom_depth ++;
...
    while (total_size <= atom.size - 8 && !avio_feof(pb)) {
        int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL;
        a.size = atom.size;
        a.type=0;
        if (atom.size >= 8) {
            a.size = avio_rb32(pb);
            a.type = avio_rl32(pb);
...
            total_size += 8;
...
        }
...
        a.size -= 8;
...
        for (i = 0; mov_default_parse_table[i].type; i++)
            if (mov_default_parse_table[i].type == a.type) {
                parse = mov_default_parse_table[i].parse;
                break;
            }
...
        if (!parse) { /* skip leaf atoms data */
            avio_skip(pb, a.size);
        } else {
            int64_t start_pos = avio_tell(pb);
            int64_t left;
            int err = parse(c, pb, a);
            if (err < 0) {
                c->atom_depth --;
                return err;
            }
...
        }

        total_size += a.size;
    }
...

    c->atom_depth --;
    return 0;
}

3.2 mov_read_ftyp

3.3 mov_read_moov

3.4 mov_read_mvhd

c->fc->duration = av_rescale(c->duration, AV_TIME_BASE, c->time_scale);

3.5 mov_read_trak

  1. 建立包含每个sample信息的表,保存在AVStream.index_entries(AVIndexEntry数组)。
typedef struct AVIndexEntry {
    int64_t pos;
    int64_t timestamp; //Timestamp in AVStream.time_base units   
#define AVINDEX_KEYFRAME 0x0001
#define AVINDEX_DISCARD_FRAME  0x0002   
    int flags:2;
    int size:30; 
    int min_distance;   //Minimum distance between this and the previous keyframe, used to avoid unneeded searching. */
} AVIndexEntry;
  1. 建立表的方式是通过遍历stsc表,结合stco、stsz、stss、stts表的信息来完成的。
static void mov_build_index(MOVContext *mov, AVStream *st) {
    MOVStreamContext *sc = st->priv_data;
    int64_t current_offset;
    int64_t current_dts = 0;
    unsigned int stts_index = 0;
    unsigned int stsc_index = 0;
    unsigned int stss_index = 0;
    unsigned int stps_index = 0;
    if (!(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
          sc->stts_count == 1 && sc->stts_data[0].duration == 1)) {
        unsigned int current_sample = 0;
        unsigned int stts_sample = 0;
        unsigned int sample_size;
        unsigned int distance = 0;
        unsigned int rap_group_index = 0;
        unsigned int rap_group_sample = 0;
        int64_t last_dts = 0;
        int64_t dts_correction = 0;
        int rap_group_present = sc->rap_group_count && sc->rap_group;
        int key_off = (sc->keyframe_count && sc->keyframes[0] > 0) || (sc->stps_count && sc->stps_data[0] > 0);
        current_dts -= sc->dts_shift;
        last_dts     = current_dts;
...
        for (i = 0; i < sc->chunk_count; i++) {//遍历每个chunk
            int64_t next_offset = i+1 < sc->chunk_count ? sc->chunk_offsets[i+1] : INT64_MAX;
            current_offset = sc->chunk_offsets[i];//当前chunk的文件位置偏移。
            while (mov_stsc_index_valid(stsc_index, sc->stsc_count) &&
                i + 1 == sc->stsc_data[stsc_index + 1].first)//(i+1)等于下一组chunk的first_chunk的时候,stsc_index后移指向下一组chunk。
                stsc_index++;
...
            for (j = 0; j < sc->stsc_data[stsc_index].count; j++) {//遍历chunk中的每个sample。
...
                int keyframe = 0;
                if (!sc->keyframe_absent && (!sc->keyframe_count || current_sample+key_off == sc->keyframes[stss_index])) {//判断是否是关键帧的条件。
                    keyframe = 1;
                    if (stss_index + 1 < sc->keyframe_count)
                        stss_index++;//stss表指向一下个关键帧。
                } 
...
                if (sc->keyframe_absent
                    && !sc->stps_count
                    && !rap_group_present
                    && (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || (i==0 && j==0)))
                     keyframe = 1;
                if (keyframe)
                    distance = 0;
...
                    e = &st->index_entries[st->nb_index_entries++];
                    e->pos = current_offset;
                    e->timestamp = current_dts;
                    e->size = sample_size;
                    e->min_distance = distance;
                    e->flags = keyframe ? AVINDEX_KEYFRAME : 0;
...
                    if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->nb_index_entries < 100)
                        ff_rfps_add_frame(mov->fc, st, current_dts);
                }

                current_offset += sample_size;//sample的文件位置偏移向后移动当前sample的大小
...
//更新current_dts和last_dts
                current_dts += sc->stts_data[stts_index].duration;
                if (!dts_correction || current_dts + dts_correction > last_dts) {
                    current_dts += dts_correction;
                    dts_correction = 0;
                } else {
                    /* Avoid creating non-monotonous DTS */
                    dts_correction += current_dts - last_dts - 1;
                    current_dts = last_dts + 1;
                }
                last_dts = current_dts;
                distance++;//与前一个关键帧的距离更新
                stts_sample++;//stts_sample是针对stts,sample在每个entry中的序号,不是实际的序号。
                current_sample++;//更新当前sample的序号。
                if (stts_index + 1 < sc->stts_count && stts_sample == sc->stts_data[stts_index].count) {//stts_data[stts_index]中的sample都遍历完了。
                    stts_sample = 0;
                    stts_index++;
                }
            }
        }
        if (st->duration > 0)
            st->codecpar->bit_rate = stream_size*8*sc->time_scale/st->duration;
  }
...
    // Update start time of the stream.
    if (st->start_time == AV_NOPTS_VALUE && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->nb_index_entries > 0) {
        st->start_time = st->index_entries[0].timestamp + sc->dts_shift;
        if (sc->ctts_data) {
            st->start_time += sc->ctts_data[0].duration;
        }
    }

    mov_estimate_video_delay(mov, st);

3.6 mov_read_tkhd

3.7 mdia(mov_read_default)

3.8 mdhd(mov_read_mdhd)

3.9 hdlr(mov_read_hdlr)

3.10 minf(mov_read_default)

3.11 stbl(mov_read_default)

3.12 stsd(mov_read_stsd)

3.13 stts(mov_read_stts)

typedef struct MOVStts {
    unsigned int count;
    int duration;
} MOVStts;
if (duration)
        st->duration= FFMIN(st->duration, duration);

3.14 ctts(mov_read_ctts)

3.15 stss(mov_read_stss)

3.16 stsz/stz2(mov_read_stsz)

3.17 stsc(mov_read_stsc)

typedef struct MOVStsc {
    int first;
    int count;
    int id;
} MOVStsc;

3.18 stco/co64(mov_read_stco)

3.19 mdat(mov_read_mdat)

4. mov_read_packet

mov_read_packet的调用时机如下图所示:


av_read_frame.png

mov_read_packet中的主要工作:

mov_find_next_sample
avio_seek
av_get_packet
pkt->dts = ...; pkt->pts = ..; ...

  1. mov_find_next_sample找到要读取的sample和对应的AVStream。
  2. avio_seek(sc->pb, sample->pos, SEEK_SET); 将文件的指针定位到文件的偏移处
  3. av_get_packet(sc->pb, pkt, sample->size); 从文件的偏移读取size的数据到AVPacket中。
  4. 计算AVPacket的dts和pts。

4.1 mov_find_next_sample

  1. 从多个AVStream中选择其中当前sample的pos或者dts更小的作为当前要处理的AVStream和sample。具体代码逻辑如下(具体逻辑有些没看明白!)
static AVIndexEntry *mov_find_next_sample(AVFormatContext *s, AVStream **st)
{
    AVIndexEntry *sample = NULL;
    int64_t best_dts = INT64_MAX;
    int i;
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *avst = s->streams[i];
        MOVStreamContext *msc = avst->priv_data;
        if (msc->pb && msc->current_sample < avst->nb_index_entries) {
            AVIndexEntry *current_sample = &avst->index_entries[msc->current_sample];
            int64_t dts = av_rescale(current_sample->timestamp, AV_TIME_BASE, msc->time_scale);
            if (!sample || (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && current_sample->pos < sample->pos) ||
                ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&
                 ((msc->pb != s->pb && dts < best_dts) || (msc->pb == s->pb &&
                 ((FFABS(best_dts - dts) <= AV_TIME_BASE && current_sample->pos < sample->pos) ||
                  (FFABS(best_dts - dts) > AV_TIME_BASE && dts < best_dts)))))) {
                sample = current_sample;
                best_dts = dts;
                *st = avst;
            }
        }
    }
    return sample;
}

4.2 计算AVPacket的dts和pts。

//mov_read_packet()
    pkt->dts = sample->timestamp;
    if (sample->flags & AVINDEX_DISCARD_FRAME) {
        pkt->flags |= AV_PKT_FLAG_DISCARD;
    }
    if (sc->ctts_data && sc->ctts_index < sc->ctts_count) {
        pkt->pts = pkt->dts + sc->dts_shift + sc->ctts_data[sc->ctts_index].duration;
        /* update ctts context */
        sc->ctts_sample++;
        if (sc->ctts_index < sc->ctts_count &&
            sc->ctts_data[sc->ctts_index].count == sc->ctts_sample) {
            sc->ctts_index++;
            sc->ctts_sample = 0;
        }
    } else {
        int64_t next_dts = (sc->current_sample < st->nb_index_entries) ?
            st->index_entries[sc->current_sample].timestamp : st->duration;

        if (next_dts >= pkt->dts)
            pkt->duration = next_dts - pkt->dts;
        pkt->pts = pkt->dts;
    }
    if (st->discard == AVDISCARD_ALL)
        goto retry;
    pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0;
    pkt->pos = sample->pos;

5. mov_read_seek

mov_read_seek的调用时机如下所示。


AVInputFormat.read_seek

未完待续。。

6. mov_read_close

avformat_close_input
  - AVInputFormat.read_close --> mov_read_close 
static int mov_read_close(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    int i, j;

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        if (!sc)
            continue;
...
        if (!sc->pb_is_copied)
            ff_format_io_close(s, &sc->pb);

        sc->pb = NULL;
        av_freep(&sc->chunk_offsets);
        av_freep(&sc->stsc_data);
        av_freep(&sc->sample_sizes);
        av_freep(&sc->keyframes);
        av_freep(&sc->stts_data);
...
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读