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件事:
- 调用avformat_init_output()来初始化输出文件;
- 调用avio_write_marker()来写入marker标识,如果存在AVIOContext;
- 调用AVOutputFormat的write_header()来写入文件头;
- 调用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()写文件头的函数分析完毕。