FFmpeg相关记录:
示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染
流程分析:
【FFmpeg】编码链路上主要函数的简单分析
【FFmpeg】解码链路上主要函数的简单分析
结构体分析:
【FFmpeg】AVCodec结构体
【FFmpeg】AVCodecContext结构体
【FFmpeg】AVStream结构体
【FFmpeg】AVFormatContext结构体
【FFmpeg】AVIOContext结构体
【FFmpeg】AVPacket结构体
函数分析:
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
avformat_write_header内函数调用关系为
1.avformat_write_header
/**
* 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 \ref AVFormatContext.oformat "oformat" field must be set
* to the desired output format;
* Its \ref AVFormatContext.pb "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.
*
* @retval AVSTREAM_INIT_IN_WRITE_HEADER On success, if the codec had not already been
* fully initialized in avformat_init_output().
* @retval AVSTREAM_INIT_IN_INIT_OUTPUT On success, if the codec had already been fully
* initialized in avformat_init_output().
* @retval AVERROR A negative AVERROR on failure.
*
* @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_init_output.
*/
// 函数的功能:分配流私有数据并将流标头写入输出媒体文件
// c语言中,如果一个函数的返回值没有被使用,编译器会发出警告信息
// 而av_warn_unused_result宏的作用就是告诉编译器,这个函数的返回值虽然没有被使用
// 但是它是有意义的,不应该发出警告信息
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
函数的定义位于libavformat\mux.c中,主要的工作流程为:
(1)如果没有进行初始化,则进行初始化输出(avformat_init_output)
(2)进行头信息的写入(write_header)
(3)如果流还没有被初始化,则写入pts(init_pts)
int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
FFFormatContext *const si = ffformatcontext(s);
int already_initialized = si->initialized;
int streams_already_initialized = si->streams_initialized;
int ret = 0;
// ----- 1.如果没有初始化,则进行output的初始化 ----- //
if (!already_initialized)
if ((ret = avformat_init_output(s, options)) < 0)
return ret;
// ----- 2.进行头信息的写入 ----- //
if (ffofmt(s->oformat)->write_header) {
if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
// 将写入的字节流标记为特定类型
// 输出中省略长度为零的范围
avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER);
ret = ffofmt(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);
// ----- 3.如果流还没有被初始化,则初始化pts ----- //
if (!si->streams_initialized) {
if ((ret = init_pts(s)) < 0)
goto fail;
}
return streams_already_initialized;
fail:
deinit_muxer(s);
return ret;
}
1.1 如果没有初始化,则进行output的初始化(avformat_init_output)
函数进行输出的初始化,主要调用函数init_muxer和init_pts。其中,init_muxer的初始化,准备将音频、视频和其他数据流混合成一个单一的文件格式;init_pts用于初始化解码后视频帧的时间戳(PTS),确保视频能够按照正确的时间顺序显示,当处理包含B帧的复杂编码时,init_pts的作用非常重要,因为B帧依赖多个参考帧,其解码和显示顺序不同。
int avformat_init_output(AVFormatContext *s, AVDictionary **options)
{
FFFormatContext *const si = ffformatcontext(s);
int ret = 0;
// 初始化复用器
if ((ret = init_muxer(s, options)) < 0)
return ret;
si->initialized = 1;
si->streams_initialized = ret;
// 初始化pts
if (ffofmt(s->oformat)->init && ret) {
if ((ret = init_pts(s)) < 0)
return ret;
return AVSTREAM_INIT_IN_INIT_OUTPUT;
}
return AVSTREAM_INIT_IN_WRITE_HEADER;
}
1.1.1 初始化复用器(init_muxer)
该函数用于初始化复用器,函数的定义位于libavformat\mux.c中,函数主要的工作流程为:
(1)将传入的options设置到AVFormatContext之中
(2)遍历每个AVStream,设置和检查相关信息
(a)设置pts
(b)检查音频的sample_rate和block_align
(c)检查视频的长宽比
(d)其他一些检查
(3)初始化格式(.init)
(4)如果初始化失败,会释放格式(.deinit)
static int init_muxer(AVFormatContext *s, AVDictionary **options)
{
FFFormatContext *const si = ffformatcontext(s);
AVDictionary *tmp = NULL;
const FFOutputFormat *of = ffofmt(s->oformat);
AVDictionaryEntry *e;
static const unsigned default_codec_offsets[] = {
[AVMEDIA_TYPE_VIDEO] = offsetof(AVOutputFormat, video_codec),
[AVMEDIA_TYPE_AUDIO] = offsetof(AVOutputFormat, audio_codec),
[AVMEDIA_TYPE_SUBTITLE] = offsetof(AVOutputFormat, subtitle_codec),
};
unsigned nb_type[FF_ARRAY_ELEMS(default_codec_offsets)] = { 0 };
int ret = 0;
if (options)
av_dict_copy(&tmp, *options, 0);
// 1.将传入的options设置到AVFormatContext之中
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
if (s->priv_data && s->oformat->priv_class && *(const AVClass**)s->priv_data==s->oformat->priv_class &&
(ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0)
goto fail;
if (!s->url && !(s->url = av_strdup(""))) {
ret = AVERROR(ENOMEM);
goto fail;
}
// some sanity checks
// 完整性检查
if (s->nb_streams == 0 && !(of->p.flags & AVFMT_NOSTREAMS)) {
av_log(s, AV_LOG_ERROR, "No streams to mux were specified\n");
ret = AVERROR(EINVAL);
goto fail;
}
// 2.遍历每个AVStream,设置信息
for (unsigned i = 0; i < s->nb_streams; i++) {
AVStream *const st = s->streams[i];
FFStream *const sti = ffstream(st);
AVCodecParameters *const par = st->codecpar;
const AVCodecDescriptor *desc;
// 设置pts信息
if (!st->time_base.num) {
/* fall back on the default timebase values */
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) {
// 检查音频的sample_rate和block_align
case AVMEDIA_TYPE_AUDIO:
if (par->sample_rate <= 0) {
av_log(s, AV_LOG_ERROR, "sample rate not set\n");
ret = AVERROR(EINVAL);
goto fail;
}
if (!par->block_align)
par->block_align = par->ch_layout.nb_channels *
av_get_bits_per_sample(par->codec_id) >> 3;
break;
// 检查视频的长宽比
case AVMEDIA_TYPE_VIDEO:
if ((par->width <= 0 || par->height <= 0) &&
!(of->p.flags & AVFMT_NODIMENSIONS)) {
av_log(s, AV_LOG_ERROR, "dimensions not set\n");
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) {
av_log(s, AV_LOG_ERROR, "Aspect ratio mismatch between muxer "
"(%d/%d) and encoder layer (%d/%d)\n",
st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
par->sample_aspect_ratio.num,
par->sample_aspect_ratio.den);
ret = AVERROR(EINVAL);
goto fail;
}
}
break;
}
// (1) FF_OFMT_FLAG_MAX_ONE_OF_EACH
// 按照注释翻译过来,意思是:"如果设置此标志,则表示对于每种编解码器类型,其对应的默认编解码器都设置为只能混合一个该类型的流
// 它进一步表明,编解码器类型没有默认编解码器或其默认编解码器为AV_CODEC_ID_NONE的流不能被混合"
// 换句话说,在使用FFmpeg进行多媒体数据处理时,每种输出格式(OutputFormat)最多只能有一个实例被激活和使用
// (2) FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
// 按照注释翻译,意思是:"如果设置了这个标志,那么唯一允许的音频/视频/字幕编解码器编号是
// AVOutputFormat.audio/video/subtitle_codec;如果后者中的任何一个未设置(即等于AV_CODEC_ID_NONE),
// 则不支持相应类型的流。此外,没有默认编解码器字段的编解码器类型是不允许的"
// 换句话说,如果定义了这个flag,那么只能使用默认的编解码器
if (of->flags_internal & (FF_OFMT_FLAG_MAX_ONE_OF_EACH | FF_OFMT_FLAG_ONLY_DEFAULT_CODECS)) {
enum AVCodecID default_codec_id = AV_CODEC_ID_NONE;
unsigned nb;
if ((unsigned)par->codec_type < FF_ARRAY_ELEMS(default_codec_offsets)) {
nb = ++nb_type[par->codec_type];
if (default_codec_offsets[par->codec_type])
default_codec_id = *(const enum AVCodecID*)((const char*)of + default_codec_offsets[par->codec_type]);
}
// 检查codec_id是否匹配
if (of->flags_internal & FF_OFMT_FLAG_ONLY_DEFAULT_CODECS &&
default_codec_id != AV_CODEC_ID_NONE && par->codec_id != default_codec_id) {
av_log(s, AV_LOG_ERROR, "%s muxer supports only codec %s for type %s\n",
of->p.name, avcodec_get_name(default_codec_id), av_get_media_type_string(par->codec_type));
ret = AVERROR(EINVAL);
goto fail;
} else if (default_codec_id == AV_CODEC_ID_NONE ||
(of->flags_internal & FF_OFMT_FLAG_MAX_ONE_OF_EACH && nb > 1)) {
const char *type = av_get_media_type_string(par->codec_type);
av_log(s, AV_LOG_ERROR, "%s muxer does not support %s stream of type %s\n",
of->p.name, default_codec_id == AV_CODEC_ID_NONE ? "any" : "more than one",
type ? type : "unknown");
ret = AVERROR(EINVAL);
goto fail;
}
}
#if FF_API_AVSTREAM_SIDE_DATA
FF_DISABLE_DEPRECATION_WARNINGS
/* if the caller is using the deprecated AVStream side_data API,
* copy its contents to AVStream.codecpar, giving it priority
over existing side data in the latter */
for (int i = 0; i < st->nb_side_data; i++) {
const AVPacketSideData *sd_src = &st->side_data[i];
AVPacketSideData *sd_dst;
sd_dst = av_packet_side_data_new(&st->codecpar->coded_side_data,
&st->codecpar->nb_coded_side_data,
sd_src->type, sd_src->size, 0);
if (!sd_dst) {
ret = AVERROR(ENOMEM);
goto fail;
}
memcpy(sd_dst->data, sd_src->data, sd_src->size);
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
desc = avcodec_descriptor_get(par->codec_id);
if (desc && desc->props & AV_CODEC_PROP_REORDER)
sti->reorder = 1;
sti->is_intra_only = ff_is_intra_only(par->codec_id);
// 设置codec_tag
if (of->p.codec_tag) {
if ( par->codec_tag
&& par->codec_id == AV_CODEC_ID_RAWVIDEO
&& ( av_codec_get_tag(of->p.codec_tag, par->codec_id) == 0
|| av_codec_get_tag(of->p.codec_tag, par->codec_id) == MKTAG('r', 'a', 'w', ' '))
&& !validate_codec_tag(s, st)) {
// the current rawvideo encoding system ends up setting
// the wrong codec_tag for avi/mov, we override it here
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);
av_log(s, AV_LOG_ERROR,
"Tag %s incompatible with output codec id '%d' (%s)\n",
av_fourcc2str(par->codec_tag), par->codec_id, av_fourcc2str(otag));
ret = AVERROR_INVALIDDATA;
goto fail;
}
} else
par->codec_tag = av_codec_get_tag(of->p.codec_tag, par->codec_id);
}
if (par->codec_type != AVMEDIA_TYPE_ATTACHMENT &&
par->codec_id != AV_CODEC_ID_SMPTE_2038)
si->nb_interleaved_streams++;
}
si->interleave_packet = of->interleave_packet;
if (!si->interleave_packet)
si->interleave_packet = si->nb_interleaved_streams > 1 ?
ff_interleave_packet_per_dts :
ff_interleave_packet_passthrough;
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->p.priv_class) {
*(const AVClass **)s->priv_data = of->p.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;
}
}
/* set muxer identification string */
if (!(s->flags & AVFMT_FLAG_BITEXACT)) {
av_dict_set(&s->metadata, "encoder", LIBAVFORMAT_IDENT, 0);
} else {
av_dict_set(&s->metadata, "encoder", NULL, 0);
}
for (e = NULL; e = av_dict_get(s->metadata, "encoder-", e, AV_DICT_IGNORE_SUFFIX); ) {
av_dict_set(&s->metadata, e->key, NULL, 0);
}
if (options) {
av_dict_free(options);
*options = tmp;
}
// 3.初始化格式
// 可以在这里分配数据,并在发送数据包之前设置任何需要设置的AVFormatContext或AVStream参数
// 对于flv格式,这里会调用flv_init()
if (of->init) {
if ((ret = of->init(s)) < 0) {
if (of->deinit)
of->deinit(s); // 4.对于flv格式,这里会调用flv_deinit()
return ret;
}
return ret == 0;
}
return 0;
fail:
av_dict_free(&tmp);
return ret;
}
下面以FLV格式为例,记录如何初始化format
1.1.1.1 初始化format(flv_init)
函数的定义位于libavformat\flvenc.c中,主要用于FLV格式的初始化。具体来说,会对数据源当中的每一条流进行解析,将stream中的信息设置到AVFormatContext中的priv_data之中。原先一直对priv_data不是很理解,从这里看,似乎这样的一个void类型数据能够用于不同类型的上下文的转换,例如下面代码中的FLContext
static int flv_init(struct AVFormatContext *s)
{
int i;
FLVContext *flv = s->priv_data;
// 如果nb_stream超过了FLV_STREAM_TYPE_NB(4),说明当前的流存在错误
// 正常FLV最多包含4条流,分别是视频、音频、字幕和DATA
if (s->nb_streams > FLV_STREAM_TYPE_NB) {
av_log(s, AV_LOG_ERROR, "invalid number of streams %d\n",
s->nb_streams);
return AVERROR(EINVAL);
}
// 遍历每一条流,将流当中的信息设置到flv当中
for (i = 0; i < s->nb_streams; i++) {
AVCodecParameters *par = s->streams[i]->codecpar;
switch (par->codec_type) {
// 视频
case AVMEDIA_TYPE_VIDEO:
if (s->streams[i]->avg_frame_rate.den &&
s->streams[i]->avg_frame_rate.num) {
flv->framerate = av_q2d(s->streams[i]->avg_frame_rate);
}
if (flv->video_par) {
av_log(s, AV_LOG_ERROR,
"at most one video stream is supported in flv\n");
return AVERROR(EINVAL);
}
flv->video_par = par;
if (!ff_codec_get_tag(flv_video_codec_ids, par->codec_id))
return unsupported_codec(s, "Video", par->codec_id);
if (par->codec_id == AV_CODEC_ID_MPEG4 ||
par->codec_id == AV_CODEC_ID_H263) {
int error = s->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL;
av_log(s, error ? AV_LOG_ERROR : AV_LOG_WARNING,
"Codec %s is not supported in the official FLV specification,\n", avcodec_get_name(par->codec_id));
if (error) {
av_log(s, AV_LOG_ERROR,
"use vstrict=-1 / -strict -1 to use it anyway.\n");
return AVERROR(EINVAL);
}
} else if (par->codec_id == AV_CODEC_ID_VP6) {
av_log(s, AV_LOG_WARNING,
"Muxing VP6 in flv will produce flipped video on playback.\n");
}
break;
// 音频
case AVMEDIA_TYPE_AUDIO:
if (flv->audio_par) {
av_log(s, AV_LOG_ERROR,
"at most one audio stream is supported in flv\n");
return AVERROR(EINVAL);
}
flv->audio_par = par;
if (get_audio_flags(s, par) < 0)
return unsupported_codec(s, "Audio", par->codec_id);
if (par->codec_id == AV_CODEC_ID_PCM_S16BE)
av_log(s, AV_LOG_WARNING,
"16-bit big-endian audio in flv is valid but most likely unplayable (hardware dependent); use s16le\n");
break;
// 数据
case AVMEDIA_TYPE_DATA:
if (par->codec_id != AV_CODEC_ID_TEXT && par->codec_id != AV_CODEC_ID_NONE)
return unsupported_codec(s, "Data", par->codec_id);
flv->data_par = par;
break;
// 字幕
case AVMEDIA_TYPE_SUBTITLE:
if (par->codec_id != AV_CODEC_ID_TEXT) {
av_log(s, AV_LOG_ERROR, "Subtitle codec '%s' for stream %d is not compatible with FLV\n",
avcodec_get_name(par->codec_id), i);
return AVERROR_INVALIDDATA;
}
flv->data_par = par;
break;
default:
av_log(s, AV_LOG_ERROR, "Codec type '%s' for stream %d is not compatible with FLV\n",
av_get_media_type_string(par->codec_type), i);
return AVERROR(EINVAL);
}
avpriv_set_pts_info(s->streams[i], 32, 1, 1000); /* 32 bit pts in ms */
flv->last_ts[i] = -1;
}
flv->delay = AV_NOPTS_VALUE;
return 0;
}
1.1.1.2 释放format(flv_deinit)
static void flv_deinit(AVFormatContext *s)
{
FLVContext *flv = s->priv_data;
FLVFileposition *filepos = flv->head_filepositions;
while (filepos) {
FLVFileposition *next = filepos->next;
av_free(filepos);
filepos = next;
}
flv->filepositions = flv->head_filepositions = NULL;
flv->filepositions_count = 0;
}
1.1.2 初始化pts(init_pts)
init_pts用于初始化解码或编码过程中的时间戳(Presentation Time Stamp, PTS)
static int init_pts(AVFormatContext *s)
{
FFFormatContext *const si = ffformatcontext(s);
/* init PTS generation */
// 初始化pts生成
for (unsigned i = 0; i < s->nb_streams; i++) {
AVStream *const st = s->streams[i];
FFStream *const sti = ffstream(st);
int64_t den = AV_NOPTS_VALUE;
switch (st->codecpar->codec_type) {
case AVMEDIA_TYPE_AUDIO:
den = (int64_t)st->time_base.num * st->codecpar->sample_rate;
break;
case AVMEDIA_TYPE_VIDEO:
den = (int64_t)st->time_base.num * st->time_base.den;
break;
default:
break;
}
if (den != AV_NOPTS_VALUE) {
if (den <= 0)
return AVERROR_INVALIDDATA;
frac_init(&sti->priv_pts, 0, 0, den);
}
}
si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_UNKNOWN;
if (s->avoid_negative_ts < 0) {
av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);
if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_DISABLED;
si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_DISABLED;
} else
s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;
} else if (s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_DISABLED)
si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_DISABLED;
return 0;
}
1.2 头信息的写入(write_header)
不同的FFOutputFormat会对应不同的写入方法,以FLV格式为例,会调用flv_write_header进行格式的写入
const FFOutputFormat ff_flv_muxer = {
.p.name = "flv",
.p.long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
.p.mime_type = "video/x-flv",
.p.extensions = "flv",
.priv_data_size = sizeof(FLVContext),
.p.audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
.p.video_codec = AV_CODEC_ID_FLV1,
.init = flv_init,
.write_header = flv_write_header, // 调用flv_write_header进行write_header
.write_packet = flv_write_packet,
.write_trailer = flv_write_trailer,
.deinit = flv_deinit,
.check_bitstream= flv_check_bitstream,
.p.codec_tag = (const AVCodecTag* const []) {
flv_video_codec_ids, flv_audio_codec_ids, 0
},
.p.flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
AVFMT_TS_NONSTRICT,
.p.priv_class = &flv_muxer_class,
};
flv_write_header的定义如下
static int flv_write_header(AVFormatContext *s)
{
int i;
AVIOContext *pb = s->pb;
FLVContext *flv = s->priv_data;
// 开始写入
// signature,文件标识
avio_write(pb, "FLV", 3);
// version,版本号
avio_w8(pb, 1);
// !! 的目的是将非0转为1
// flags,前5位保留,第6位表示是否存在音频tag,第7位保留且必须为0
// 第8位表示是否存在视频tag
avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_par +
FLV_HEADER_FLAG_HASVIDEO * !!flv->video_par);
// header size,表示从file header开始到file body之间的字节数,
avio_wb32(pb, 9);
// header结束
// previous tag size,表示前一个tag的长度
avio_wb32(pb, 0);
for (i = 0; i < s->nb_streams; i++)
// codecpar->codec_tag为5,表示视频流采用了MPEG Video编码格式
if (s->streams[i]->codecpar->codec_tag == 5) {
avio_w8(pb, 8); // message type
avio_wb24(pb, 0); // include flags
avio_wb24(pb, 0); // time stamp
avio_wb32(pb, 0); // reserved
avio_wb32(pb, 11); // size
flv->reserved = 5;
}
if (flv->flags & FLV_NO_METADATA) {
pb->seekable = 0;
} else {
write_metadata(s, 0);
}
// 写入codec的头
for (i = 0; i < s->nb_streams; i++) {
flv_write_codec_header(s, s->streams[i]->codecpar, 0);
}
flv->datastart_offset = avio_tell(pb);
return 0;
}
flv_write_codec_header的定义如下
static void flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par, int64_t ts) {
int64_t data_size;
AVIOContext *pb = s->pb;
FLVContext *flv = s->priv_data;
if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
|| par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC
|| par->codec_id == AV_CODEC_ID_AV1 || par->codec_id == AV_CODEC_ID_VP9) {
int64_t pos;
avio_w8(pb,
par->codec_type == AVMEDIA_TYPE_VIDEO ?
FLV_TAG_TYPE_VIDEO : FLV_TAG_TYPE_AUDIO); // 写入codec_type
avio_wb24(pb, 0); // size patched later
put_timestamp(pb, ts);
avio_wb24(pb, 0); // streamid
pos = avio_tell(pb);
// 写入AAC信息
if (par->codec_id == AV_CODEC_ID_AAC) {
avio_w8(pb, get_audio_flags(s, par));
avio_w8(pb, 0); // AAC sequence header
if (!par->extradata_size && (flv->flags & FLV_AAC_SEQ_HEADER_DETECT)) {
PutBitContext pbc;
int samplerate_index;
int channels = par->ch_layout.nb_channels
- (par->ch_layout.nb_channels == 8 ? 1 : 0);
uint8_t data[2];
for (samplerate_index = 0; samplerate_index < 16;
samplerate_index++)
if (par->sample_rate
== ff_mpeg4audio_sample_rates[samplerate_index])
break;
init_put_bits(&pbc, data, sizeof(data));
put_bits(&pbc, 5, par->profile + 1); //profile
put_bits(&pbc, 4, samplerate_index); //sample rate index
put_bits(&pbc, 4, channels);
put_bits(&pbc, 1, 0); //frame length - 1024 samples
put_bits(&pbc, 1, 0); //does not depend on core coder
put_bits(&pbc, 1, 0); //is not extension
flush_put_bits(&pbc);
avio_w8(pb, data[0]);
avio_w8(pb, data[1]);
av_log(s, AV_LOG_WARNING, "AAC sequence header: %02x %02x.\n",
data[0], data[1]);
}
avio_write(pb, par->extradata, par->extradata_size);
} else { // 如果是视频编码格式
if (par->codec_id == AV_CODEC_ID_HEVC) { // hevc
avio_w8(pb, FLV_IS_EX_HEADER | PacketTypeSequenceStart | FLV_FRAME_KEY); // ExVideoTagHeader mode with PacketTypeSequenceStart
avio_write(pb, "hvc1", 4);
} else if (par->codec_id == AV_CODEC_ID_AV1 || par->codec_id == AV_CODEC_ID_VP9) { // av1和vp9
avio_w8(pb, FLV_IS_EX_HEADER | PacketTypeSequenceStart | FLV_FRAME_KEY);
avio_write(pb, par->codec_id == AV_CODEC_ID_AV1 ? "av01" : "vp09", 4);
} else {
avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags
avio_w8(pb, 0); // AVC sequence header
avio_wb24(pb, 0); // composition time
}
if (par->codec_id == AV_CODEC_ID_HEVC)
ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0);
else if (par->codec_id == AV_CODEC_ID_AV1)
ff_isom_write_av1c(pb, par->extradata, par->extradata_size, 1);
else if (par->codec_id == AV_CODEC_ID_VP9)
ff_isom_write_vpcc(s, pb, par->extradata, par->extradata_size, par);
else
ff_isom_write_avcc(pb, par->extradata, par->extradata_size);
}
data_size = avio_tell(pb) - pos;
avio_seek(pb, -data_size - 10, SEEK_CUR);
avio_wb24(pb, data_size);
avio_skip(pb, data_size + 10 - 3);
avio_wb32(pb, data_size + 11); // previous tag size
}
}
2.小结
avformat_write_header用于写入头信息,大体来说,可以分为两个部分:(1)为写入信息做初始化,调用init_muxer、of->init实现;(2)初始化之后,使用write_header实现头信息的写入。初始化的信息比如时间戳pts,codec_tag等,write_header会实现写入头信息。这里的信息比较琐碎,暂时还不完全理解,后续在实际工程之中再做理解
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen