FFmpeg源码分析:写媒体文件尾av_write_trailer()

FFmpeg在libavformat模块提供音视频的muxer封装与demuxer解封装。其中muxer封装文件包括avformat_write_header()、av_write_frame()和av_write_trailer()。本文主要探讨av_write_trailer函数如何写入文件尾,最终完成多媒体文件的封装。

关于avformat_write_header()和av_write_frame()可查看前面两篇文章:

FFmpeg源码分析:写文件头avformat_write_header()

FFmpeg源码分析:写音视频帧av_write_frame()

1、av_write_trailer

av_write_trailer()的头文件声明位于libavformat/avformat.h,描述如下:

/**
 * Write the stream trailer to an output media file and free the
 * file private data.
 *
 * May only be called after a successful call to avformat_write_header.
 *
 * @param s media file handle
 * @return 0 if OK, AVERROR_xxx on error
 */
int av_write_trailer(AVFormatContext *s);

 中文翻译大致如下:

写入音视频流的尾部到输出媒体文件,并且释放文件的私有数据。只能在avformat_write_header()调用成功后才能调该函数。

接下来,我们看看av_write_trailer()函数的实现,位于libavformat/mux.c:

int av_write_trailer(AVFormatContext *s)
{
    int i, ret1, ret = 0;
    AVPacket *pkt = s->internal->pkt;
    av_packet_unref(pkt);
	// 如果有AVBSFContext,最后写入bitstream filter数据包
    for (i = 0; i < s->nb_streams; i++) {
        if (s->streams[i]->internal->bsfc) {
            ret1 = write_packets_from_bsfs(s, s->streams[i], pkt, 1/*interleaved*/);
            if (ret1 < 0)
                av_packet_unref(pkt);
            if (ret >= 0)
                ret = ret1;
        }
    }
	// 填充空数据包
    ret1 = interleaved_write_packet(s, NULL, 1);
    if (ret >= 0)
        ret = ret1;

    if (s->oformat->write_trailer) {
		// 写入avio的marker标志
        if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
            avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
		// 调用AVOutputFormat写文件尾
        if (ret >= 0) {
        ret = s->oformat->write_trailer(s);
        } else {
            s->oformat->write_trailer(s);
        }
    }
    // 释放muxer资源
    deinit_muxer(s);
    if (s->pb)
       avio_flush(s->pb);
    if (ret == 0)
       ret = s->pb ? s->pb->error : 0;
    // 释放priv_data和index_entries
    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;
}

由源码可知,写媒体文件尾主要有6个步骤:

  1. 如果有AVBSFContext,最后写入bitstream filter数据包;
  2. 填充空数据包;
  3. 如果有AVIOContext,写入avio的marker标志;
  4. 调用AVOutputFormat写文件尾;
  5. 释放muxer资源;
  6. 释放priv_data和index_entries;

第1步的write_packets_from_bsfs和第2步的interleaved_write_packet在av_write_frame()文章有介绍。我们主要来分析第3、4、5步的处理。

2、avio_write_marker

avio_write_marker位于aviobuf.c文件,函数实现如下:

void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type)
{
    if (type == AVIO_DATA_MARKER_FLUSH_POINT) {
        if (s->buf_ptr - s->buffer >= s->min_packet_size)
            avio_flush(s);
        return;
    }
    if (!s->write_data_type)
        return;
    if (type == AVIO_DATA_MARKER_BOUNDARY_POINT && s->ignore_boundary_point)
        type = AVIO_DATA_MARKER_UNKNOWN;
    if (type == AVIO_DATA_MARKER_UNKNOWN &&
        (s->current_type != AVIO_DATA_MARKER_HEADER &&
         s->current_type != AVIO_DATA_MARKER_TRAILER))
        return;
    // 判断header和trailer的marker是否相同
    switch (type) {
    case AVIO_DATA_MARKER_HEADER:
    case AVIO_DATA_MARKER_TRAILER:
        if (type == s->current_type)
            return;
        break;
    }

    // If we've reached here, we have a new, noteworthy marker.
    // Flush the previous data and mark the start of the new data.
    avio_flush(s);
    s->current_type = type;
    s->last_time = time;
}

3、s->oformat->write_trailer

以mp4的封装格式为例,位于libavformat/movenc.c文件,对应的AVOutputFormat如下:

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,
    .init              = mov_init,
    .write_header      = mov_write_header,
    .write_packet      = mov_write_packet,
    .write_trailer     = mov_write_trailer,
    .deinit            = mov_free,
    .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
    .codec_tag         = mp4_codec_tags_list,
    .check_bitstream   = mov_check_bitstream,
    .priv_class        = &mp4_muxer_class,
};

此时,write_trailer函数指针指向mov_write_trailer(),我们来看看该函数实现:

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;
    // 判断是否要重写extradata
    if (mov->need_rewrite_extradata) {
        for (i = 0; i < s->nb_streams; i++) {
            MOVTrack *track = &mov->tracks[i];
            AVCodecParameters *par = track->par;

            track->vos_len  = par->extradata_size;
            av_freep(&track->vos_data);
            track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE);
            if (!track->vos_data)
                return AVERROR(ENOMEM);
            memcpy(track->vos_data, par->extradata, track->vos_len);
            memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
        }
        mov->need_rewrite_extradata = 0;
    }
    // 如果存在字幕轨,需要写结束包
    for (i = 0; i < mov->nb_streams; i++) {
        MOVTrack *trk = &mov->tracks[i];
        if (trk->par->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;
        }
    }
    // 判断是否需要写chapter track
    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)
                return res;
        }
    }
    if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
        moov_pos = avio_tell(pb);
        // 写入mdat tag和size
        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);
        // 写入moov tag
        if (mov->flags & FF_MOV_FLAG_FASTSTART) {
            res = shift_data(s);
            if (res < 0)
                return res;
            avio_seek(pb, mov->reserved_header_pos, SEEK_SET);
            if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
                return res;
        } else if (mov->reserved_moov_size > 0) {
            int64_t size;
            if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
                return res;
            size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos);
            if (size < 8){
                return AVERROR(EINVAL);
            }
            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)
                return res;
        }
        res = 0;
    } else {
        mov_auto_flush_fragment(s, 1);
        for (i = 0; i < mov->nb_streams; i++)
           mov->tracks[i].data_offset = 0;
	    // 写入sidx tag
        if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) {
            int64_t end;
            res = shift_data(s);
            if (res < 0)
                return res;
            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);
        }
		// 写入mfra tag
        if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) {
            avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
            res = mov_write_mfra_tag(pb, mov);
            if (res < 0)
                return res;
        }
    }
    return res;
}

 由源码可知,mp4格式写文件尾主要有7个步骤:

  1. 判断是否要重写extradata;
  2. 如果存在字幕轨,需要写结束包;
  3. 判断是否需要写chapter track;
  4. 写入mdat tag和size;
  5. 写入moov tag;
  6. 写入sidx tag;
  7. 写入mfra tag;

4、deinit_muxer

deinit_muxer()函数负责是否muxer内存资源,具体如下:

static void deinit_muxer(AVFormatContext *s)
{
    if (s->oformat && s->oformat->deinit && s->internal->initialized)
        s->oformat->deinit(s);
    s->internal->initialized =
    s->internal->streams_initialized = 0;
}

同样地,以mp4封装格式为例。由前面的ff_mp4_muxer结构体可知,deinit函数指针指向mov_free()。因此,s->oformat->deinit()最终调用到mov_free()来释放资源。函数实现如下:

static void mov_free(AVFormatContext *s)
{
    MOVMuxContext *mov = s->priv_data;
    int i;
    av_packet_free(&mov->pkt);
    if (!mov->tracks)
        return;
    if (mov->chapter_track) {
        avcodec_parameters_free(&mov->tracks[mov->chapter_track].par);
    }
    // 遍历所有nb_streams,释放相关资源
    for (i = 0; i < mov->nb_streams; i++) {
        if (mov->tracks[i].tag == MKTAG('r','t','p',' '))
            ff_mov_close_hinting(&mov->tracks[i]);
        else if (mov->tracks[i].tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd)
            av_freep(&mov->tracks[i].par);
        av_freep(&mov->tracks[i].cluster);
        av_freep(&mov->tracks[i].frag_info);
        av_packet_free(&mov->tracks[i].cover_image);

        if (mov->tracks[i].eac3_priv) {
            struct eac3_info *info = mov->tracks[i].eac3_priv;
            av_packet_free(&info->pkt);
            av_freep(&mov->tracks[i].eac3_priv);
        }
        if (mov->tracks[i].vos_len)
            av_freep(&mov->tracks[i].vos_data);
        ff_mov_cenc_free(&mov->tracks[i].cenc);
        ffio_free_dyn_buf(&mov->tracks[i].mdat_buf);
    }

    av_freep(&mov->tracks);
    ffio_free_dyn_buf(&mov->mdat_buf);
}

至此,av_write_trailer()写媒体文件尾函数分析完毕。结合前面的文章:avformat_write_header()写媒体文件头和av_write_frame()写媒体数据包,可以完整地实现muxer封装器。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐福记456

您的鼓励和肯定是我创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值