ffmpeg写文件代码解析(todo)

2,824 阅读3分钟

概述

本文主要记录和分析ffmpeg项目中写文件的流程和代码。(比如录制存储为mp4,ts之类的)

流程

avformat_write_header()

av_write_frame()

av_write_trailer()

代码分析

1.avformat_write_header()

2.av_write_frame()

3.av_write_trailer()

本函数位于libavformat/mux.c。

流程(补一个图)

具体代码如下

int av_write_trailer(AVFormatContext *s)
{
    int ret, i;

    for (;; ) {   //将剩下未写完的包写入文件
        AVPacket pkt;
        ret = interleave_packet(s, &pkt, NULL, 1);
        if (ret < 0)
            goto fail;
        if (!ret)
            break;

        ret = write_packet(s, &pkt);
        if (ret >= 0)
            s->streams[pkt.stream_index]->nb_frames++;

        av_free_packet(&pkt);

        if (ret < 0)
            goto fail;
        if(s->pb && s->pb->error)
            goto fail;
    }

fail:
    if (s->oformat->write_trailer)   //写文件尾
        if (ret >= 0) {
        ret = s->oformat->write_trailer(s);
        } else {
            s->oformat->write_trailer(s);
        }

    if (s->pb)
       avio_flush(s->pb);
    if (ret == 0)
       ret = s->pb ? s->pb->error : 0;
    for (i = 0; i < s->nb_streams; i++) {
        av_freep(&s->streams[i]->priv_data);
        av_freep(&s->streams[i]->index_entries);
    }
    if (s->oformat->priv_class)
        av_opt_free(s->priv_data);
    av_freep(&s->priv_data);
    return ret;
}

(1)循环调用interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来。
(2)调用AVOutputFormat的write_trailer(),输出文件尾。

来看一下AVOutputFormat->write_trailer(),结构体定义如下:

AVOutputFormat ff_mp4_muxer = {
    .name              = "mp4",
    .long_name         = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),
    .mime_type         = "video/mp4",
    .extensions        = "mp4",
    .priv_data_size    = sizeof(MOVMuxContext),
    .audio_codec       = AV_CODEC_ID_AAC,
    .video_codec       = CONFIG_LIBX264_ENCODER ?
                         AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
    .write_header      = mov_write_header,
    .write_packet      = mov_write_packet,
    .write_trailer     = mov_write_trailer,
    .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
    .priv_class        = &mp4_muxer_class,
};

所以我们的关注点到了mov_write_trailer()这个函数,位于libavformat/movenc.c文件。来看下代码中都做了啥:

static int mov_write_trailer(AVFormatContext *s)
{
    MOVMuxContext *mov = s->priv_data;
    AVIOContext *pb = s->pb;
    int res = 0;
    int i;
    int64_t moov_pos;

    /*
     * Before actually writing the trailer, make sure that there are no
     * dangling subtitles, that need a terminating sample.
     */
    for (i = 0; i < mov->nb_streams; i++) {
        MOVTrack *trk = &mov->tracks[i];
        if (trk->enc->codec_id == AV_CODEC_ID_MOV_TEXT &&
            !trk->last_sample_is_subtitle_end) {
            mov_write_subtitle_end_packet(s, i, trk->track_duration);
            trk->last_sample_is_subtitle_end = 1;
        }
    }

    // If there were no chapters when the header was written, but there
    // are chapters now, write them in the trailer.  This only works
    // when we are not doing fragments.
    if (!mov->chapter_track && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
        if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) {
            mov->chapter_track = mov->nb_streams++;
            if ((res = mov_create_chapter_track(s, mov->chapter_track)) < 0)
                goto error;
        }
    }

    if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {  //存为视频文件
        moov_pos = avio_tell(pb);    

        /* Write size of mdat tag */
        if (mov->mdat_size + 8 <= UINT32_MAX) {
            avio_seek(pb, mov->mdat_pos, SEEK_SET);
            avio_wb32(pb, mov->mdat_size + 8);
        } else {
            /* overwrite 'wide' placeholder atom */
            avio_seek(pb, mov->mdat_pos - 8, SEEK_SET);
            /* special value: real atom size will be 64 bit value after
             * tag field */
            avio_wb32(pb, 1);
            ffio_wfourcc(pb, "mdat");
            avio_wb64(pb, mov->mdat_size + 16);
        }
        avio_seek(pb, mov->reserved_moov_size > 0 ? mov->reserved_header_pos : moov_pos, SEEK_SET);

        if (mov->flags & FF_MOV_FLAG_FASTSTART) {  //如果faststart,将moov box移到文件前部分
            av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n");
            res = shift_data(s);
            if (res == 0) {
                avio_seek(pb, mov->reserved_header_pos, SEEK_SET);
                if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
                    goto error; //mov_write_moov_tag写moovbox内容
            }
        } else if (mov->reserved_moov_size > 0) {
            int64_t size;
            if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
                goto error;   //mov_write_moov_tag写moovbox内容
            size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos);
            if (size < 8){
                av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size);
                res = AVERROR(EINVAL);
                goto error;
            }
            avio_wb32(pb, size);
            ffio_wfourcc(pb, "free");
            ffio_fill(pb, 0, size - 8);
            avio_seek(pb, moov_pos, SEEK_SET);
        } else {
            if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
                goto error; //mov_write_moov_tag写moovbox内容
        }
        res = 0;
    } else {       //存为fmp4流式文件
        mov_auto_flush_fragment(s);
        for (i = 0; i < mov->nb_streams; i++)
           mov->tracks[i].data_offset = 0;
        if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) {
            av_log(s, AV_LOG_INFO, "Starting second pass: inserting sidx atoms\n");
            res = shift_data(s);
            if (res == 0) {
                int64_t end = avio_tell(pb);
                avio_seek(pb, mov->reserved_header_pos, SEEK_SET);
                mov_write_sidx_tags(pb, mov, -1, 0);
                avio_seek(pb, end, SEEK_SET);
                mov_write_mfra_tag(pb, mov);
            }
        } else {
            mov_write_mfra_tag(pb, mov);
        }
    }

error:
    mov_free(s);

    return res;
}

这里不深入介绍moov box的格式和书写,具体可以参考mp4格式fmp4格式及其与mp4/ts的对比 这两篇。
mov_write_trailer()主要完成了moov box的写入,对于点播的mp4文件,如果指定了要将moov移到前面,则在文件中进行内容交换。

参考