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) {// 初始化ptsif ((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是否为0if (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);elseavpriv_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_tagif (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;}} elsepar->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_dataif (!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;}}// 设置encoderif (!(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_KEYFRAMEif (!(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的tagmov_write_mdat_tag(pb, mov);}ff_parse_creation_time_metadata(s, &mov->time, 1);if (mov->time)mov->time += 0x7C25B080; // 1970 based -> 1904 basedif (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的tagif (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()写文件头的函数分析完毕。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部