目录
参考:
FFmpeg源代码简单分析:常见结构体的初始化和销毁(AVFormatContext,AVFrame等)
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】avcodec_find_encoder和avcodec_find_decoder
【推流】
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
【FFmpeg】avio_open2函数
【FFmpeg】avformat_write_header函数
【FFmpeg】av_write_frame函数
在记录了一些主要函数的执行流程之后,可以看看几个关键结构体的初始化函数和释放函数,这样对整个FFmpeg更加熟悉一些。需要说明的是,在FFmpeg当中,很多函数都能够实现关键结构体的初始化,举例来说,对于AVFormatContext,可以使用avformat_alloc_output_context2初始化,这个函数会根据输出的格式来初始化AVFormatContext,这样的操作避免了先进行malloc,然后手动赋值的流程。不过本文记录的函数是最基本的函数,尽量避免与其他结构体交织在一起
结构体 | 初始化 | 释放 | 备注 |
---|---|---|---|
AVFormatContext | avformat_alloc_context() | avformat_free_context() | 也可以通过avformat_alloc_output_context2,根据输出的format来进行初始化 |
AVIOContext | avio_alloc_context() | avio_context_free() | 通过avio_open2进行初始化 |
AVStream | avformat_new_stream() | 单独释放函数为ff_free_stream,会在avformat_free_context中调用 | |
AVCodecContext | avcodec_alloc_context3() | avcodec_free_context() | |
AVCodec | ->init | ->close | 编解码器的初始化操作,会使用函数指针链接,例如ff_h264_decoder,其init为h264_decode_init,close为h264_decode_end |
AVFrame | av_frame_alloc() av_frame_get_buffer() | av_frame_free() | av_frame_alloc()不会分配AVFrame之中的data,需要使用av_frame_get_buffer()初始化 |
AVPacket | av_packet_alloc() | av_packet_free() | (1)av_init_packet用于已分配内存的AVPacket结构体的初始化 (2)av_new_packet用于创建新的AVPacket结构体并分配内存 (3)av_packet_alloc创建新的AVPacket结构体的同时,还设置了额外字段 |
另外,在雷博的记录之中,AVFrame的初始化除了调用av_frame_alloc()还会调用av_image_fill_arrays()进行data buffer的初始化,但是看新版本中的注释,似乎会推荐使用av_frame_get_buffer(),所以本文中记录的是av_frame_get_buffer()。对于AVPacket而言,由于FFmpeg7.0给出的examples\路径下的代码示例中,使用的是av_packet_alloc(),所以本文也重点记录这个函数,并不代表另外两个函数不重要
1.AVFormatContext
1.1 初始化(avformat_alloc_context)
avformat_alloc_context的定义位于libavformat\options.c中,从代码看,函数执行的流程为,调用av_mallocz分配内存空间,随后初始化输入输出的函数,将AVOptions中的字段设置为默认值,分配pkt和parse_pkt的内存。如果pkt和parse_pkt分配失败,则调用avformat_free_context进行释放
/**
* Allocate an AVFormatContext.
* avformat_free_context() can be used to free the context and everything
* allocated by the framework within it.
*/
AVFormatContext *avformat_alloc_context(void)
{
FFFormatContext *const si = av_mallocz(sizeof(*si));
AVFormatContext *s;
if (!si)
return NULL;
s = &si->pub;
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
s->io_close2= io_close2_default;
// 将所有AVOption字段的值设置为默认值
av_opt_set_defaults(s);
si->pkt = av_packet_alloc();
si->parse_pkt = av_packet_alloc();
if (!si->pkt || !si->parse_pkt) {
avformat_free_context(s);
return NULL;
}
#if FF_API_LAVF_SHORTEST
si->shortest_end = AV_NOPTS_VALUE;
#endif
return s;
}
1.2 释放(avformat_free_context)
函数的定义位于libavformat\avformat.c中,代码中看,会调用deinit进行format的释放,随后调用av_opt_free释放一些options,调用ff_free_stream释放stream,然后是一系列变量的释放。释放时会调用av_freep,av_opt_free和av_dict_free等函数执行,都是将free函数进行不同封装的
/**
* Free an AVFormatContext and all its streams.
* @param s context to free
*/
void avformat_free_context(AVFormatContext *s)
{
FFFormatContext *si;
if (!s)
return;
si = ffformatcontext(s);
if (s->oformat && ffofmt(s->oformat)->deinit && si->initialized)
ffofmt(s->oformat)->deinit(s);
av_opt_free(s);
if (s->iformat && s->iformat->priv_class && s->priv_data)
av_opt_free(s->priv_data);
if (s->oformat && s->oformat->priv_class && s->priv_data)
av_opt_free(s->priv_data);
for (unsigned i = 0; i < s->nb_streams; i++)
ff_free_stream(&s->streams[i]);
for (unsigned i = 0; i < s->nb_stream_groups; i++)
ff_free_stream_group(&s->stream_groups[i]);
s->nb_stream_groups = 0;
s->nb_streams = 0;
for (unsigned i = 0; i < s->nb_programs; i++) {
av_dict_free(&s->programs[i]->metadata);
av_freep(&s->programs[i]->stream_index);
av_freep(&s->programs[i]);
}
s->nb_programs = 0;
av_freep(&s->programs);
av_freep(&s->priv_data);
while (s->nb_chapters--) {
av_dict_free(&s->chapters[s->nb_chapters]->metadata);
av_freep(&s->chapters[s->nb_chapters]);
}
av_freep(&s->chapters);
av_dict_free(&s->metadata);
av_dict_free(&si->id3v2_meta);
av_packet_free(&si->pkt);
av_packet_free(&si->parse_pkt);
av_freep(&s->streams);
av_freep(&s->stream_groups);
ff_flush_packet_queue(s);
av_freep(&s->url);
av_free(s);
}
这里的deinit会根据具体情况和具体格式进行,例如FLV格式,会调用flv_deinit()进行释放
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,
.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_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;
}
ff_free_stream函数用于释放AVFormatContext中的stream,定义如下,进行一系列变量的释放
/**
* Frees a stream without modifying the corresponding AVFormatContext.
* Must only be called if the latter doesn't matter or if the stream
* is not yet attached to an AVFormatContext.
*/
// 释放一个流而不修改相应的AVFormatContext
// 必须只调用,如果后者不重要,或者如果流还没有附加到AVFormatContext
void ff_free_stream(AVStream **pst)
{
AVStream *st = *pst;
FFStream *const sti = ffstream(st);
if (!st)
return;
#if FF_API_AVSTREAM_SIDE_DATA
FF_DISABLE_DEPRECATION_WARNINGS
for (int i = 0; i < st->nb_side_data; i++)
av_freep(&st->side_data[i].data);
av_freep(&st->side_data);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
if (st->attached_pic.data)
av_packet_unref(&st->attached_pic);
// 如果使用了parser,则释放parser
av_parser_close(sti->parser);
avcodec_free_context(&sti->avctx);
av_bsf_free(&sti->bsfc);
av_freep(&sti->index_entries);
av_freep(&sti->probe_data.buf);
av_bsf_free(&sti->extract_extradata.bsf);
if (sti->info) {
av_freep(&sti->info->duration_error);
av_freep(&sti->info);
}
av_dict_free(&st->metadata);
avcodec_parameters_free(&st->codecpar);
av_freep(&st->priv_data);
av_freep(pst);
}
2.AVIOContext
2.1 初始化(avio_alloc_context)
函数的定义位于libavformat\aviobuf.c中
/**
* Allocate and initialize an AVIOContext for buffered I/O. It must be later
* freed with avio_context_free().
*
* @param buffer Memory block for input/output operations via AVIOContext.
* The buffer must be allocated with av_malloc() and friends.
* It may be freed and replaced with a new buffer by libavformat.
* AVIOContext.buffer holds the buffer currently in use,
* which must be later freed with av_free().
* @param buffer_size The buffer size is very important for performance.
* For protocols with fixed blocksize it should be set to this blocksize.
* For others a typical size is a cache page, e.g. 4kb.
* @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
* @param opaque An opaque pointer to user-specific data.
* @param read_packet A function for refilling the buffer, may be NULL.
* For stream protocols, must never return 0 but rather
* a proper AVERROR code.
* @param write_packet A function for writing the buffer contents, may be NULL.
* The function may not change the input buffers content.
* @param seek A function for seeking to specified byte position, may be NULL.
*
* @return Allocated AVIOContext or NULL on failure.
*/
// 为缓冲I/O分配并初始化AVIOContext。稍后必须使用avio_context_free释放它
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
FFIOContext *s = av_malloc(sizeof(*s));
if (!s)
return NULL;
ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
read_packet, write_packet, seek);
return &s->pub;
}
从代码看,先调用av_malloc分配内存空间,随后调用ffio_init_context进行初始化,ffio_init_context的定义如下,从代码中看,首先将ctx中的信息配置为0,随后进行一系列变量进行设置
void ffio_init_context(FFIOContext *ctx,
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
AVIOContext *const s = &ctx->pub;
memset(ctx, 0, sizeof(*ctx));
s->buffer = buffer;
ctx->orig_buffer_size =
s->buffer_size = buffer_size;
s->buf_ptr = buffer;
s->buf_ptr_max = buffer;
s->opaque = opaque;
s->direct = 0;
url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);
s->write_packet = write_packet;
s->read_packet = read_packet;
s->seek = seek;
s->pos = 0;
s->eof_reached = 0;
s->error = 0;
s->seekable = seek ? AVIO_SEEKABLE_NORMAL : 0;
s->min_packet_size = 0;
s->max_packet_size = 0;
s->update_checksum = NULL;
ctx->short_seek_threshold = SHORT_SEEK_THRESHOLD;
if (!read_packet && !write_flag) {
s->pos = buffer_size;
s->buf_end = s->buffer + buffer_size;
}
s->read_pause = NULL;
s->read_seek = NULL;
s->write_data_type = NULL;
s->ignore_boundary_point = 0;
ctx->current_type = AVIO_DATA_MARKER_UNKNOWN;
ctx->last_time = AV_NOPTS_VALUE;
ctx->short_seek_get = NULL;
}
2.2 释放(avio_context_free)
avio_context_free中调用av_freep直接释放AVIOContext
/**
* Free the supplied IO context and everything associated with it.
*
* @param s Double pointer to the IO context. This function will write NULL
* into s.
*/
void avio_context_free(AVIOContext **ps)
{
av_freep(ps);
}
3. AVStream
3.1 初始化(avformat_new_stream)
函数的定义位于libavformat\options.c中,主要功能是为一个媒体文件创建一条新的流
/**
* Add a new stream to a media file.
*
* When demuxing, it is called by the demuxer in read_header(). If the
* flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also
* be called in read_packet().
*
* When muxing, should be called by the user before avformat_write_header().
*
* User is required to call avformat_free_context() to clean up the allocation
* by avformat_new_stream().
*
* @param s media file handle
* @param c unused, does nothing
*
* @return newly created stream or NULL on error.
*/
// 向媒体文件中添加一条新的流
// 1.当解除时,它由read_header()中的解除器调用。如果在s.ctx_flags中设置了AVFMTCTX_NOHEADER标志
// 那么它也可以在read_packet()中调用
// 2.在复用时,应在avformat_write_header()之前由用户调用
// 3.用户需要调用avformat_free_context()来清理avformat_new_stream()的分配
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{
FFFormatContext *const si = ffformatcontext(s);
FFStream *sti;
AVStream *st;
AVStream **streams;
// 当前流的数量大于了最大的流数量,报错
if (s->nb_streams >= s->max_streams) {
av_log(s, AV_LOG_ERROR, "Number of streams exceeds max_streams parameter"
" (%d), see the documentation if you wish to increase it\n",
s->max_streams);
return NULL;
}
streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams));
if (!streams)
return NULL;
s->streams = streams;
// 和旧版本FFmpeg不同,这里分配了的是FFStream
// FFStream是将AVStream封装起来的FFmpeg的内部结构体
sti = av_mallocz(sizeof(*sti));
if (!sti)
return NULL;
st = &sti->pub;
st->av_class = &stream_class;
// 分配codecpar的内存
st->codecpar = avcodec_parameters_alloc();
if (!st->codecpar)
goto fail;
sti->fmtctx = s;
if (s->iformat) {
sti->avctx = avcodec_alloc_context3(NULL);
if (!sti->avctx)
goto fail;
sti->info = av_mallocz(sizeof(*sti->info));
if (!sti->info)
goto fail;
#if FF_API_R_FRAME_RATE
sti->info->last_dts = AV_NOPTS_VALUE;
#endif
sti->info->fps_first_dts = AV_NOPTS_VALUE;
sti->info->fps_last_dts = AV_NOPTS_VALUE;
/* default pts setting is MPEG-like */
avpriv_set_pts_info(st, 33, 1, 90000);
/* we set the current DTS to 0 so that formats without any timestamps
* but durations get some timestamps, formats with some unknown
* timestamps have their first few packets buffered and the
* timestamps corrected before they are returned to the user */
sti->cur_dts = RELATIVE_TS_BASE;
} else {
sti->cur_dts = AV_NOPTS_VALUE;
}
// 设置一些流的信息
st->index = s->nb_streams;
st->start_time = AV_NOPTS_VALUE;
st->duration = AV_NOPTS_VALUE;
sti->first_dts = AV_NOPTS_VALUE;
sti->probe_packets = s->max_probe_packets;
sti->pts_wrap_reference = AV_NOPTS_VALUE;
sti->pts_wrap_behavior = AV_PTS_WRAP_IGNORE;
sti->last_IP_pts = AV_NOPTS_VALUE;
sti->last_dts_for_order_check = AV_NOPTS_VALUE;
for (int i = 0; i < MAX_REORDER_DELAY + 1; i++)
sti->pts_buffer[i] = AV_NOPTS_VALUE;
st->sample_aspect_ratio = (AVRational) { 0, 1 };
sti->transferred_mux_tb = (AVRational) { 0, 1 };;
#if FF_API_AVSTREAM_SIDE_DATA
sti->inject_global_side_data = si->inject_global_side_data;
#endif
sti->need_context_update = 1;
s->streams[s->nb_streams++] = st;
return st;
fail:
ff_free_stream(&st); // 失败则释放流
return NULL;
}
从代码中看,先调用av_mallocz分配新的FFStream,然后进行一系列变量的初始化。如果inputFormat已经被设置,则会调用avcodec_alloc_context3()初始化FFStream中的AVCodecContext
3.2 释放(ff_free_stream)
前面已记录
4.AVCodecContext
4.1 初始化(avcodec_alloc_context3)
函数的定义位于libavcodec\options.c中,功能是分配AVCodecContext的内存,并且将其中的值配置成为默认值
/**
* Allocate an AVCodecContext and set its fields to default values. The
* resulting struct should be freed with avcodec_free_context().
*
* @param codec if non-NULL, allocate private data and initialize defaults
* for the given codec. It is illegal to then call avcodec_open2()
* with a different codec.
* If NULL, then the codec-specific defaults won't be initialized,
* which may result in suboptimal default settings (this is
* important mainly for encoders, e.g. libx264).
*
* @return An AVCodecContext filled with default values or NULL on failure.
*/
// 分配AVCodecContext并将其字段设置为默认值。生成的结构体应该使用avcodec_free_context()释放
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));
if (!avctx)
return NULL;
if (init_context_defaults(avctx, codec) < 0) {
av_free(avctx);
return NULL;
}
return avctx;
}
代码中看,先调用av_malloc分配空间,随后调用init_context_defaults进行AVCodecContext中变量的初始化,该函数定义位于libavformat\options.c中
static int init_context_defaults(AVCodecContext *s, const AVCodec *codec)
{
const FFCodec *const codec2 = ffcodec(codec);
int flags=0;
// 将AVCodecContext中的内容全部设置为0
memset(s, 0, sizeof(AVCodecContext));
s->av_class = &av_codec_context_class;
s->codec_type = codec ? codec->type : AVMEDIA_TYPE_UNKNOWN;
if (codec) {
s->codec = codec;
s->codec_id = codec->id;
}
if(s->codec_type == AVMEDIA_TYPE_AUDIO)
flags= AV_OPT_FLAG_AUDIO_PARAM;
else if(s->codec_type == AVMEDIA_TYPE_VIDEO)
flags= AV_OPT_FLAG_VIDEO_PARAM;
else if(s->codec_type == AVMEDIA_TYPE_SUBTITLE)
flags= AV_OPT_FLAG_SUBTITLE_PARAM;
// 将flgas中信息配置给s中
av_opt_set_defaults2(s, flags, flags);
// 释放通道布局中任何已分配的数据,并将通道计数重置为0
av_channel_layout_uninit(&s->ch_layout);
s->time_base = (AVRational){0,1};
s->framerate = (AVRational){ 0, 1 };
s->pkt_timebase = (AVRational){ 0, 1 };
// 通用函数,适用于任何类型的编码器缓冲区
s->get_buffer2 = avcodec_default_get_buffer2;
s->get_format = avcodec_default_get_format;
// 专门用于获取视频编码器的输出缓冲区
s->get_encode_buffer = avcodec_default_get_encode_buffer;
s->execute = avcodec_default_execute;
s->execute2 = avcodec_default_execute2;
// 音频的采样宽高比
s->sample_aspect_ratio = (AVRational){0,1};
// 音频声道顺序
s->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
s->pix_fmt = AV_PIX_FMT_NONE;
// 软编pix fmt
s->sw_pix_fmt = AV_PIX_FMT_NONE;
s->sample_fmt = AV_SAMPLE_FMT_NONE;
if(codec && codec2->priv_data_size){
s->priv_data = av_mallocz(codec2->priv_data_size);
if (!s->priv_data)
return AVERROR(ENOMEM);
if(codec->priv_class){
*(const AVClass**)s->priv_data = codec->priv_class;
av_opt_set_defaults(s->priv_data);
}
}
if (codec && codec2->defaults) {
int ret;
const FFCodecDefault *d = codec2->defaults;
while (d->key) {
ret = av_opt_set(s, d->key, d->value, 0);
av_assert0(ret >= 0);
d++;
}
}
return 0;
}
4.2 释放(avcodec_free_context)
/**
* Free the codec context and everything associated with it and write NULL to
* the provided pointer.
*/
// 释放编解码器上下文和与之相关的所有内容,并将NULL写入所提供的指针
void avcodec_free_context(AVCodecContext **pavctx)
{
AVCodecContext *avctx = *pavctx;
if (!avctx)
return;
// 释放AVCodecContext中的codec
ff_codec_close(avctx);
av_freep(&avctx->extradata);
av_freep(&avctx->subtitle_header);
av_freep(&avctx->intra_matrix);
av_freep(&avctx->inter_matrix);
av_freep(&avctx->rc_override);
av_channel_layout_uninit(&avctx->ch_layout);
av_frame_side_data_free(
&avctx->decoded_side_data, &avctx->nb_decoded_side_data);
av_freep(pavctx);
}
从代码中看,在进行AVCodecContext释放时,会同时将其中的AVCodec一起释放掉,调用的函数是ff_codec_close,随后进行一系列变量的释放
5.AVCodec
AVCodec的初始化过程在avcodec_open2中进行,函数位于libavcodec\avcodec.c中
int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec,
AVDictionary **options)
{
// ...
if (!(avctx->active_thread_type & FF_THREAD_FRAME) ||
avci->frame_thread_encoder) {
if (codec2->init) {
lock_avcodec(codec2);
// 调用init进行初始化
ret = codec2->init(avctx);
unlock_avcodec(codec2);
if (ret < 0) {
avci->needs_close = codec2->caps_internal & FF_CODEC_CAP_INIT_CLEANUP;
goto free_and_end;
}
}
avci->needs_close = 1;
}
// ...
}
释放函数的调用位置在ff_codec_close进行,函数位于libavcodec\avcodec.中
av_cold void ff_codec_close(AVCodecContext *avctx)
{
int i;
if (!avctx)
return;
if (avcodec_is_open(avctx)) {
AVCodecInternal *avci = avctx->internal;
if (CONFIG_FRAME_THREAD_ENCODER &&
avci->frame_thread_encoder && avctx->thread_count > 1) {
ff_frame_thread_encoder_free(avctx);
}
if (HAVE_THREADS && avci->thread_ctx)
ff_thread_free(avctx);
if (avci->needs_close && ffcodec(avctx->codec)->close)
ffcodec(avctx->codec)->close(avctx);
// ...
}
假设现在进行的是264格式的解码过程,如下所示
const FFCodec ff_h264_decoder = {
.p.name = "h264",
CODEC_LONG_NAME("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_H264,
.priv_data_size = sizeof(H264Context),
.init = h264_decode_init,
.close = h264_decode_end,
FF_CODEC_DECODE_CB(h264_decode_frame),
.p.capabilities = AV_CODEC_CAP_DR1 |
AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
AV_CODEC_CAP_FRAME_THREADS,
.hw_configs = (const AVCodecHWConfigInternal *const []) {
// ...
NULL
},
.caps_internal = FF_CODEC_CAP_EXPORTS_CROPPING |
FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
.flush = h264_decode_flush,
UPDATE_THREAD_CONTEXT(ff_h264_update_thread_context),
UPDATE_THREAD_CONTEXT_FOR_USER(ff_h264_update_thread_context_for_user),
.p.profiles = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
.p.priv_class = &h264_class,
};
进行初始化时,使用h264_decode_init()进行初始化;进行释放时,使用h264_decode_end()进行释放
5.1 h264decoder的初始化(h264_decode_init)
h264_decode_init的定义位于libavcodec\h264.c中,如下所示
static av_cold int h264_decode_init(AVCodecContext *avctx)
{
H264Context *h = avctx->priv_data;
int ret;
// h264初始化上下文
ret = h264_init_context(avctx, h);
if (ret < 0)
return ret;
// ff_h264_decode_init_vlc主要作用是为H264视频解码器准备必要的VLC表
// VLC表被用来解析比特流中的编码数据,将其转换为可识别的语法元素和预测模式
ret = ff_thread_once(&h264_vlc_init, ff_h264_decode_init_vlc);
if (ret != 0) {
av_log(avctx, AV_LOG_ERROR, "pthread_once has failed.");
return AVERROR_UNKNOWN;
}
#if FF_API_TICKS_PER_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
avctx->ticks_per_frame = 2;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
if (!avctx->internal->is_copy) {
if (avctx->extradata_size > 0 && avctx->extradata) {
ret = ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
&h->ps, &h->is_avc, &h->nal_length_size,
avctx->err_recognition, avctx);
if (ret < 0) {
int explode = avctx->err_recognition & AV_EF_EXPLODE;
av_log(avctx, explode ? AV_LOG_ERROR: AV_LOG_WARNING,
"Error decoding the extradata\n");
if (explode) {
return ret;
}
ret = 0;
}
}
}
if (h->ps.sps && h->ps.sps->bitstream_restriction_flag &&
h->avctx->has_b_frames < h->ps.sps->num_reorder_frames) {
h->avctx->has_b_frames = h->ps.sps->num_reorder_frames;
}
ff_h264_flush_change(h);
if (h->enable_er < 0 && (avctx->active_thread_type & FF_THREAD_SLICE))
h->enable_er = 0;
if (h->enable_er && (avctx->active_thread_type & FF_THREAD_SLICE)) {
av_log(avctx, AV_LOG_WARNING,
"Error resilience with slice threads is enabled. It is unsafe and unsupported and may crash. "
"Use it at your own risk\n");
}
return 0;
}
从代码中看,主要内容是先调用h264_init_context()进行初始化,然后使用ff_h264_decode_init_vlc()进行VLC表初始化。h264_init_context的定义如下,进行了一系列变量的初始化,随后是slice_ctx初始化和pic的初始化
static int h264_init_context(AVCodecContext *avctx, H264Context *h)
{
int i, ret;
h->avctx = avctx;
h->cur_chroma_format_idc = -1;
h->width_from_caller = avctx->width;
h->height_from_caller = avctx->height;
h->workaround_bugs = avctx->workaround_bugs;
h->flags = avctx->flags;
h->poc.prev_poc_msb = 1 << 16;
h->recovery_frame = -1;
h->frame_recovered = 0;
h->poc.prev_frame_num = -1;
h->sei.common.frame_packing.arrangement_cancel_flag = -1;
h->sei.common.unregistered.x264_build = -1;
h->next_outputed_poc = INT_MIN;
for (i = 0; i < FF_ARRAY_ELEMS(h->last_pocs); i++)
h->last_pocs[i] = INT_MIN;
ff_h264_sei_uninit(&h->sei);
if (avctx->active_thread_type & FF_THREAD_FRAME) {
h->decode_error_flags_pool = ff_refstruct_pool_alloc(sizeof(atomic_int), 0);
if (!h->decode_error_flags_pool)
return AVERROR(ENOMEM);
}
h->nb_slice_ctx = (avctx->active_thread_type & FF_THREAD_SLICE) ? avctx->thread_count : 1;
h->slice_ctx = av_calloc(h->nb_slice_ctx, sizeof(*h->slice_ctx));
if (!h->slice_ctx) {
h->nb_slice_ctx = 0;
return AVERROR(ENOMEM);
}
// 初始化dpb
for (i = 0; i < H264_MAX_PICTURE_COUNT; i++) {
if ((ret = h264_init_pic(&h->DPB[i])) < 0)
return ret;
}
// 初始化pic
if ((ret = h264_init_pic(&h->cur_pic)) < 0)
return ret;
if ((ret = h264_init_pic(&h->last_pic_for_ec)) < 0)
return ret;
for (i = 0; i < h->nb_slice_ctx; i++)
h->slice_ctx[i].h264 = h;
return 0;
}
5.2 h264decoder的释放(h264_decode_end)
函数进行一系列变量的释放
static av_cold int h264_decode_end(AVCodecContext *avctx)
{
H264Context *h = avctx->priv_data;
int i;
ff_h264_remove_all_refs(h);
ff_h264_free_tables(h);
// 释放dbp
for (i = 0; i < H264_MAX_PICTURE_COUNT; i++) {
h264_free_pic(h, &h->DPB[i]);
}
memset(h->delayed_pic, 0, sizeof(h->delayed_pic));
h->cur_pic_ptr = NULL;
ff_refstruct_pool_uninit(&h->decode_error_flags_pool);
av_freep(&h->slice_ctx);
h->nb_slice_ctx = 0;
ff_h264_sei_uninit(&h->sei);
ff_h264_ps_uninit(&h->ps);
ff_h2645_packet_uninit(&h->pkt);
h264_free_pic(h, &h->cur_pic);
h264_free_pic(h, &h->last_pic_for_ec);
return 0;
}
6.AVFrame
6.1 初始化(av_alloc_frame)
函数的定义位于libavutil\frame.c中,主要功能是分配AVFrame内存空间,并且将其设置为默认值
/**
* Allocate an AVFrame and set its fields to default values. The resulting
* struct must be freed using av_frame_free().
*
* @return An AVFrame filled with default values or NULL on failure.
*
* @note this only allocates the AVFrame itself, not the data buffers. Those
* must be allocated through other means, e.g. with av_frame_get_buffer() or
* manually.
*/
// 分配一个AVFrame并将其字段设置为默认值。必须使用av_frame_free()释放生成的结构体
// @note: 这里需要注意的是av_frame_alloc仅分配AVFrame本身的空间,但不包括data buffer,需要通过
// 其他的方式进行分配,例如av_frame_get_buffer
AVFrame *av_frame_alloc(void)
{
AVFrame *frame = av_malloc(sizeof(*frame));
if (!frame)
return NULL;
get_frame_defaults(frame);
return frame;
}
函数中先调用av_malloc分配空间,随后使用get_frame_defaults进行frame的初始化,如下所示
static void get_frame_defaults(AVFrame *frame)
{
memset(frame, 0, sizeof(*frame));
frame->pts =
frame->pkt_dts = AV_NOPTS_VALUE;
frame->best_effort_timestamp = AV_NOPTS_VALUE;
frame->duration = 0;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
frame->pkt_pos = -1;
frame->pkt_size = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
frame->time_base = (AVRational){ 0, 1 };
frame->sample_aspect_ratio = (AVRational){ 0, 1 };
frame->format = -1; /* unknown */
frame->extended_data = frame->data;
frame->color_primaries = AVCOL_PRI_UNSPECIFIED;
frame->color_trc = AVCOL_TRC_UNSPECIFIED;
frame->colorspace = AVCOL_SPC_UNSPECIFIED;
frame->color_range = AVCOL_RANGE_UNSPECIFIED;
frame->chroma_location = AVCHROMA_LOC_UNSPECIFIED;
frame->flags = 0;
}
从代码中看,av_frame_alloc()会进行一些变量的配置,但不会为其中的data buffer分配内存,需要使用其他函数进行分配,例如av_frame_get_buffer(),函数的定义位于libavutil\frame.c中
int av_frame_get_buffer(AVFrame *frame, int align)
{
if (frame->format < 0)
return AVERROR(EINVAL);
// 获取video的buffer
if (frame->width > 0 && frame->height > 0)
return get_video_buffer(frame, align);
else if (frame->nb_samples > 0 &&
(av_channel_layout_check(&frame->ch_layout)))
return get_audio_buffer(frame, align);
return AVERROR(EINVAL);
}
get_video_buffer的定义如下
static int get_video_buffer(AVFrame *frame, int align)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); // 获取pix fmt
int ret, padded_height, total_size;
int plane_padding = FFMAX(16 + 16/*STRIDE_ALIGN*/, align);
ptrdiff_t linesizes[4];
size_t sizes[4];
if (!desc)
return AVERROR(EINVAL);
// 检查size
if ((ret = av_image_check_size(frame->width, frame->height, 0, NULL)) < 0)
return ret;
if (!frame->linesize[0]) {
if (align <= 0)
align = 32; /* STRIDE_ALIGN. Should be av_cpu_max_align() */
for (int i = 1; i <= align; i += i) {
// 填充plane的linesizes
ret = av_image_fill_linesizes(frame->linesize, frame->format,
FFALIGN(frame->width, i));
if (ret < 0)
return ret;
if (!(frame->linesize[0] & (align-1)))
break;
}
for (int i = 0; i < 4 && frame->linesize[i]; i++)
frame->linesize[i] = FFALIGN(frame->linesize[i], align);
}
for (int i = 0; i < 4; i++)
linesizes[i] = frame->linesize[i];
padded_height = FFALIGN(frame->height, 32);
if ((ret = av_image_fill_plane_sizes(sizes, frame->format,
padded_height, linesizes)) < 0)
return ret;
total_size = 4*plane_padding;
for (int i = 0; i < 4; i++) {
if (sizes[i] > INT_MAX - total_size)
return AVERROR(EINVAL);
total_size += sizes[i];
}
// 根据前面计算的size来为buffer分配内存
frame->buf[0] = av_buffer_alloc(total_size);
if (!frame->buf[0]) {
ret = AVERROR(ENOMEM);
goto fail;
}
if ((ret = av_image_fill_pointers(frame->data, frame->format, padded_height,
frame->buf[0]->data, frame->linesize)) < 0)
goto fail;
for (int i = 1; i < 4; i++) {
if (frame->data[i])
frame->data[i] += i * plane_padding;
}
frame->extended_data = frame->data;
return 0;
fail:
av_frame_unref(frame);
return ret;
}
6.2 释放(av_frame_free)
/**
* Free the frame and any dynamically allocated objects in it,
* e.g. extended_data. If the frame is reference counted, it will be
* unreferenced first.
*
* @param frame frame to be freed. The pointer will be set to NULL.
*/
void av_frame_free(AVFrame **frame)
{
if (!frame || !*frame)
return;
av_frame_unref(*frame);
av_freep(frame);
}
7.AVPacket
7.1 初始化(av_packet_alloc)
函数的作用是分配一个AVPacket的空间,并且初始化,最后释放时必须使用av_packet_free进行。如果分配空间失败,则返回NULL。需要注意的是,这里只会分配AVPacket本身而不会申请data buffer的空间,这些信息通过其他方式分配,例如在调用avcodec_receive_packet时,将已经解码的packet取出来,此时存储在data buffer之中
/**
* Allocate an AVPacket and set its fields to default values. The resulting
* struct must be freed using av_packet_free().
*
* @return An AVPacket filled with default values or NULL on failure.
*
* @note this only allocates the AVPacket itself, not the data buffers. Those
* must be allocated through other means such as av_new_packet.
*
* @see av_new_packet
*/
AVPacket *av_packet_alloc(void);
函数的定义位于libavcodec\avpkt.c中,先调用av_malloc申请分配空间,随后调用get_packet_defaults进行初始化
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = av_malloc(sizeof(AVPacket));
if (!pkt)
return pkt;
get_packet_defaults(pkt);
return pkt;
}
get_packet_defaults的定义如下,初始化了几个最重要的信息,包括显示时间戳(play timestamp),解码时间戳(decode timestamp),位置pos和时间基time_base
static void get_packet_defaults(AVPacket *pkt)
{
memset(pkt, 0, sizeof(*pkt));
pkt->pts = AV_NOPTS_VALUE;
pkt->dts = AV_NOPTS_VALUE;
pkt->pos = -1;
pkt->time_base = av_make_q(0, 1);
}
如果使用av_new_packet,会进行buf的内存分配,其中av_new_packet的定义如下,与av_packet_alloc的区别在于会分配buf的内存
/**
* Allocate the payload of a packet and initialize its fields with
* default values.
*
* @param pkt packet
* @param size wanted payload size
* @return 0 if OK, AVERROR_xxx otherwise
*/
int av_new_packet(AVPacket *pkt, int size)
{
AVBufferRef *buf = NULL;
int ret = packet_alloc(&buf, size);
if (ret < 0)
return ret;
get_packet_defaults(pkt);
pkt->buf = buf;
pkt->data = buf->data;
pkt->size = size;
return 0;
}
7.2 释放(av_packet_free)
/**
* Free the packet, if the packet is reference counted, it will be
* unreferenced first.
*
* @param pkt packet to be freed. The pointer will be set to NULL.
* @note passing NULL is a no-op.
*/
void av_packet_free(AVPacket **pkt)
{
if (!pkt || !*pkt)
return;
av_packet_unref(*pkt);
av_freep(pkt);
}
8.写在最后的思考
不得不感慨FFmpeg随着发展,结构体和函数变得愈发的复杂,雷博记录的文章和新版本FFmpeg在大体上一致,但在细节部分还是有一些出入。有一些问题值得思考:
(1)结构体的初始化,是依靠自身进行初始化,还是结合别的信息进行初始化,怎么选择比较合适
(2)初始化时,有哪些变量没有完全初始化,需要在别的地方初始化,或者是手工初始化,或者是在别的函数中初始化,这其中的关联需要梳理
(3)在释放时,为了一些操作的简化和封装,有些结构体并没有展示出来其释放的过程,但是必须得熟悉这个释放的过程,否则容易出现内存泄漏的问题,这在音视频软件使用时是严重的问题,因为尝尝会使用视频软件进行长时间的播放,这样有可能导致系统整体卡顿,发热的问题
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen