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

FFmpeg在libavformat模块提供mux封装视频的API,包括avformat_write_header()写文件头、av_write_frame()写音视频帧、av_write_trailer()写文件尾。本文主要介绍写文件头的方法avformat_write_header()。通过分析源码,与大家一起探讨FFmpeg是如何封装视频文件的。

在libavformat/avformat.h有这三个API的描述:

 * The main API functions for muxing are avformat_write_header() for writing the
 * file header, av_write_frame() / av_interleaved_write_frame() for writing the
 * packets and av_write_trailer() for finalizing the file.
 * When the muxing context is fully set up, the caller must call
 * avformat_write_header() to initialize the muxer internals and write the file
 * header. Any muxer private options must be passed in the options parameter to 
 * this function.

翻译如下:

封装文件格式的主要API包括:avformat_write_header()写文件头,av_write_frame() / av_interleaved_write_frame()写音视频帧,av_write_trailer()写文件尾。当完全创建封装上下文后,必须调用avformat_write_header()来初始化封装器内部和写入文件头。任何封装器的私有选项必须通过选项参数传递到这个函数。

avformat_write_header()函数声明位于libavformat/avformat.h:

/**
 * Allocate the stream private data and write the stream header to
 * an output media file.
 *
 * @param s Media file handle, must be allocated with avformat_alloc_context().
 *          Its oformat field must be set to the desired output format;
 *          Its pb field must be set to an already opened AVIOContext.
 * @param options  An AVDictionary filled with AVFormatContext and muxer-private options.
 *                 On return this parameter will be destroyed and replaced with a dict containing
 *                 options that were not found. May be NULL.
 *
 * @return AVSTREAM_INIT_IN_WRITE_HEADER on success if the codec had not already been fully initialized in avformat_init,
 *         AVSTREAM_INIT_IN_INIT_OUTPUT  on success if the codec had already been fully initialized in avformat_init,
 *         negative AVERROR on failure.
 *
 * @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_init_output.
 */
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);

函数的实现位于libavformat/mux.c,具体如下:

int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
    int ret = 0;
    int already_initialized = s->internal->initialized;
    int streams_already_initialized = s->internal->streams_initialized;
 
    if (!already_initialized)
		// 初始化输出
        if ((ret = avformat_init_output(s, options)) < 0)
            return ret;
 
    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
		// 写入marker标识
        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER);
    if (s->oformat->write_header) {
		// 写入文件头
        ret = s->oformat->write_header(s);
        if (ret >= 0 && s->pb && s->pb->error < 0)
            ret = s->pb->error;
        if (ret < 0)
            goto fail;
        flush_if_needed(s);
    }
    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_UNKNOWN);
 
    if (!s->internal->streams_initialized) {
		// 初始化pts
        if ((ret = init_pts(s)) < 0)
            goto fail;
    }
 
    return streams_already_initialized;
 
fail:
    deinit_muxer(s);
    return ret;
}

由此可见,avformat_write_header函数主要做4件事:

  1. 调用avformat_init_output()来初始化输出文件;
  2. 调用avio_write_marker()来写入marker标识,如果存在AVIOContext;
  3. 调用AVOutputFormat的write_header()来写入文件头;
  4. 调用init_pts()来初始化时间戳;
    我们来看下avformat_init_output()函数:
int avformat_init_output(AVFormatContext *s, AVDictionary **options)
{
    int ret = 0;
    if ((ret = init_muxer(s, options)) < 0)
        return ret;
 
    s->internal->initialized = 1;
    s->internal->streams_initialized = ret;
    if (s->oformat->init && ret) {
        if ((ret = init_pts(s)) < 0)
            return ret;
 
        return AVSTREAM_INIT_IN_INIT_OUTPUT;
    }
 
    return AVSTREAM_INIT_IN_WRITE_HEADER;
}

内部调用init_muxer()函数来初始化封装器:

static int init_muxer(AVFormatContext *s, AVDictionary **options)
{
    ......
    // 检查nb_streams是否为0
    if (s->nb_streams == 0 && !(of->flags & AVFMT_NOSTREAMS)) {
        av_log(s, AV_LOG_ERROR, "No streams to mux were specified\n");
        ret = AVERROR(EINVAL);
        goto fail;
    }
 
    for (i = 0; i < s->nb_streams; i++) {
        st  = s->streams[i];
        par = st->codecpar;
        // 检查timebase时间基,如果没有就设置默认时间基
        if (!st->time_base.num) {
            if (par->codec_type == AVMEDIA_TYPE_AUDIO && par->sample_rate)
                avpriv_set_pts_info(st, 64, 1, par->sample_rate);
            else
                avpriv_set_pts_info(st, 33, 1, 90000);
        }
 
        switch (par->codec_type) {
        case AVMEDIA_TYPE_AUDIO: // 检查音频采样率、block_align块对齐
            if (par->sample_rate <= 0) {
                ret = AVERROR(EINVAL);
                goto fail;
            }
            if (!par->block_align)
                par->block_align = par->channels *
                                   av_get_bits_per_sample(par->codec_id) >> 3;
            break;
        case AVMEDIA_TYPE_VIDEO: // 检查视频宽高、宽高比
            if ((par->width <= 0 || par->height <= 0) &&
                !(of->flags & AVFMT_NODIMENSIONS)) {
                ret = AVERROR(EINVAL);
                goto fail;
            }
            if (av_cmp_q(st->sample_aspect_ratio, par->sample_aspect_ratio)
                && fabs(av_q2d(st->sample_aspect_ratio) - av_q2d(par->sample_aspect_ratio))
    			> 0.004*av_q2d(st->sample_aspect_ratio)) {
                if (st->sample_aspect_ratio.num != 0 &&
                    st->sample_aspect_ratio.den != 0 &&
                    par->sample_aspect_ratio.num != 0 &&
                    par->sample_aspect_ratio.den != 0) {
                    ret = AVERROR(EINVAL);
                    goto fail;
                }
            }
            break;
        }
 
        desc = avcodec_descriptor_get(par->codec_id);
        if (desc && desc->props & AV_CODEC_PROP_REORDER)
            st->internal->reorder = 1;
        st->internal->is_intra_only = ff_is_intra_only(par->codec_id);
		// 检查codec_tag
        if (of->codec_tag) {
            if (   par->codec_tag
                && par->codec_id == AV_CODEC_ID_RAWVIDEO
                && (   av_codec_get_tag(of->codec_tag, par->codec_id) == 0
                    || av_codec_get_tag(of->codec_tag, par->codec_id) == MKTAG('r', 'a', 'w', ' '))
                && !validate_codec_tag(s, st)) {
                par->codec_tag = 0;
            }
            if (par->codec_tag) {
                if (!validate_codec_tag(s, st)) {
                    const uint32_t otag = av_codec_get_tag(s->oformat->codec_tag, par->codec_id);
                    ret = AVERROR_INVALIDDATA;
                    goto fail;
                }
            } else
                par->codec_tag = av_codec_get_tag(of->codec_tag, par->codec_id);
        }
 
        if (par->codec_type != AVMEDIA_TYPE_ATTACHMENT)
            s->internal->nb_interleaved_streams++;
    }
    // 检查priv_data
    if (!s->priv_data && of->priv_data_size > 0) {
        s->priv_data = av_mallocz(of->priv_data_size);
        if (!s->priv_data) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (of->priv_class) {
            *(const AVClass **)s->priv_data = of->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0)
                goto fail;
        }
    }
    // 设置encoder
    if (!(s->flags & AVFMT_FLAG_BITEXACT)) {
        av_dict_set(&s->metadata, "encoder", LIBAVFORMAT_IDENT, 0);
    } else {
        av_dict_set(&s->metadata, "encoder", NULL, 0);
    }
    ......
    return 0;
 
fail:
    av_dict_free(&tmp);
    return ret;
}

接着看s->oformat->write_header()函数,以mp4封装格式为例,位于libavformat/movenc.c。那么mp4对应的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_header函数指针指向mov_write_header,具体函数如下:

static int mov_write_header(AVFormatContext *s)
{
    AVIOContext *pb = s->pb;
    MOVMuxContext *mov = s->priv_data;
    int i, ret, hint_track = 0, tmcd_track = 0, nb_tracks = s->nb_streams;
 
    if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters)
        nb_tracks++;
    if (mov->flags & FF_MOV_FLAG_RTP_HINT) {
        hint_track = nb_tracks;
        for (i = 0; i < s->nb_streams; i++)
            if (rtp_hinting_needed(s->streams[i]))
                nb_tracks++;
    }
    if (mov->nb_meta_tmcd)
        tmcd_track = nb_tracks;
 
    for (i = 0; i < s->nb_streams; i++) {
        int j;
        AVStream *st= s->streams[i];
        MOVTrack *track= &mov->tracks[i];
 
        // 如果存在extradata就进行拷贝
        if (st->codecpar->extradata_size) {
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE)
                mov_create_dvd_sub_decoder_specific_info(track, st);
            else if (!TAG_IS_AVCI(track->tag) && st->codecpar->codec_id != AV_CODEC_ID_DNXHD) {
                track->vos_len  = st->codecpar->extradata_size;
                track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE);
                if (!track->vos_data) {
                    return AVERROR(ENOMEM);
                }
                memcpy(track->vos_data, st->codecpar->extradata, track->vos_len);
                memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
            }
        }
 
        if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO ||
            track->par->channel_layout != AV_CH_LAYOUT_MONO)
            continue;
 
        for (j = 0; j < s->nb_streams; j++) {
            AVStream *stj= s->streams[j];
            MOVTrack *trackj= &mov->tracks[j];
            if (j == i)
                continue;
 
            if (stj->codecpar->codec_type != AVMEDIA_TYPE_AUDIO ||
                trackj->par->channel_layout != AV_CH_LAYOUT_MONO ||
                trackj->language != track->language ||
                trackj->tag != track->tag)
                continue;
            track->multichannel_as_mono++;
        }
    }
 
    if (!(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) {
        if ((ret = mov_write_identification(pb, s)) < 0)
            return ret;
    }
    if (mov->reserved_moov_size){
        mov->reserved_header_pos = avio_tell(pb);
        if (mov->reserved_moov_size > 0)
            avio_skip(pb, mov->reserved_moov_size);
    }
    if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
        // 设置flag为FF_MOV_FLAG_FRAG_KEYFRAME
        if (!(mov->flags & (FF_MOV_FLAG_FRAG_KEYFRAME |
                            FF_MOV_FLAG_FRAG_CUSTOM |
                            FF_MOV_FLAG_FRAG_EVERY_FRAME)) &&
            !mov->max_fragment_duration && !mov->max_fragment_size)
            mov->flags |= FF_MOV_FLAG_FRAG_KEYFRAME;
    } else {
        if (mov->flags & FF_MOV_FLAG_FASTSTART)
            mov->reserved_header_pos = avio_tell(pb);
		// 写入mdat的tag
        mov_write_mdat_tag(pb, mov);
    }
 
    ff_parse_creation_time_metadata(s, &mov->time, 1);
    if (mov->time)
        mov->time += 0x7C25B080; // 1970 based -> 1904 based
 
    if (mov->chapter_track)
        if ((ret = mov_create_chapter_track(s, mov->chapter_track)) < 0)
            return ret;
 
    if (mov->flags & FF_MOV_FLAG_RTP_HINT) {
        for (i = 0; i < s->nb_streams; i++) {
            if (rtp_hinting_needed(s->streams[i])) {
                if ((ret = ff_mov_init_hinting(s, hint_track, i)) < 0)
                    return ret;
                hint_track++;
            }
        }
    }
    if (mov->nb_meta_tmcd) {
        const AVDictionaryEntry *t, *global_tcr = av_dict_get(
			s->metadata, "timecode", NULL, 0);
        // 初始化tmcd的track轨道
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            t = global_tcr;
 
            if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                AVTimecode tc;
                if (!t)
                    t = av_dict_get(st->metadata, "timecode", NULL, 0);
                if (!t)
                    continue;
                if (mov_check_timecode_track(s, &tc, i, t->value) < 0)
                    continue;
                if ((ret = mov_create_timecode_track(s, tmcd_track, i, tc)) < 0)
                    return ret;
                tmcd_track++;
            }
        }
    }
    avio_flush(pb);
    if (mov->flags & FF_MOV_FLAG_ISML)
        mov_write_isml_manifest(pb, mov, s);
    // 写入moov的tag
    if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV &&
        !(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) {
        if ((ret = mov_write_moov_tag(pb, mov, s)) < 0)
            return ret;
        mov->moov_written = 1;
        if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX)
            mov->reserved_header_pos = avio_tell(pb);
    }
 
    return 0;
}

总结一下,mov_write_header函数主要做4件事情:

  • 拷贝extradata;
  • 写入mdat的tag或设置fragment的flag;
  • 初始化tmcd(timecode)的track轨道;
  • 写入moov的tag;

至此, avformat_write_header()写文件头的函数分析完毕。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值