当前位置: 首页 > news >正文

【FFmpeg】关键结构体的初始化和释放(AVFormatContext、AVIOContext等)

目录

  • 1.AVFormatContext
    • 1.1 初始化(avformat_alloc_context)
    • 1.2 释放(avformat_free_context)
  • 2.AVIOContext
    • 2.1 初始化(avio_alloc_context)
    • 2.2 释放(avio_context_free)
  • 3. AVStream
    • 3.1 初始化(avformat_new_stream)
    • 3.2 释放(ff_free_stream)
  • 4.AVCodecContext
    • 4.1 初始化(avcodec_alloc_context3)
    • 4.2 释放(avcodec_free_context)
  • 5.AVCodec
    • 5.1 h264decoder的初始化(h264_decode_init)
    • 5.2 h264decoder的释放(h264_decode_end)
  • 6.AVFrame
    • 6.1 初始化(av_alloc_frame)
    • 6.2 释放(av_frame_free)
  • 7.AVPacket
    • 7.1 初始化(av_packet_alloc)
    • 7.2 释放(av_packet_free)
  • 8.写在最后的思考

参考:
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,然后手动赋值的流程。不过本文记录的函数是最基本的函数,尽量避免与其他结构体交织在一起

结构体初始化释放备注
AVFormatContextavformat_alloc_context()avformat_free_context()也可以通过avformat_alloc_output_context2,根据输出的format来进行初始化
AVIOContextavio_alloc_context()avio_context_free()通过avio_open2进行初始化
AVStreamavformat_new_stream()单独释放函数为ff_free_stream,会在avformat_free_context中调用
AVCodecContextavcodec_alloc_context3()avcodec_free_context()
AVCodec->init->close编解码器的初始化操作,会使用函数指针链接,例如ff_h264_decoder,其init为h264_decode_init,close为h264_decode_end
AVFrameav_frame_alloc()
av_frame_get_buffer()
av_frame_free()av_frame_alloc()不会分配AVFrame之中的data,需要使用av_frame_get_buffer()初始化
AVPacketav_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_SHORTESTsi->shortest_end = AV_NOPTS_VALUE;
#endifreturn 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_WARNINGSfor (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
#endifif (st->attached_pic.data)av_packet_unref(&st->attached_pic);// 如果使用了parser,则释放parserav_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_RATEsti->info->last_dts      = AV_NOPTS_VALUE;
#endifsti->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_DATAsti->inject_global_side_data = si->inject_global_side_data;
#endifsti->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中的内容全部设置为0memset(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);// 释放通道布局中任何已分配的数据,并将通道计数重置为0av_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 fmts->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中的codecff_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_WARNINGSavctx->ticks_per_frame = 2;
FF_ENABLE_DEPRECATION_WARNINGS
#endifif (!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);}// 初始化dpbfor (i = 0; i < H264_MAX_PICTURE_COUNT; i++) {if ((ret = h264_init_pic(&h->DPB[i])) < 0)return ret;}// 初始化picif ((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);// 释放dbpfor (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_WARNINGSframe->pkt_pos             = -1;frame->pkt_size            = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endifframe->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的bufferif (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 fmtint 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);// 检查sizeif ((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的linesizesret = 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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 使用Charles实现Android抓包,附带Charles破解教程
  • Autoware内容学习与初步探索(一)
  • AI微电影制作教程:轻松打造高清小人国画面
  • C++(Qt)-GIS开发-简易瓦片地图下载器
  • 解决Spring Boot中的安全漏洞与防护策略
  • 【QT】按钮类控件
  • html的作业
  • 场景管理分析平台介绍
  • Linux——进程间通信一(共享内存、管道、systrem V)
  • celery执行任务报错ValueError: not enough values to unpack
  • Arduino ESP8266 开发环境搭建
  • 针对tcp不出网打——HTTP隧道代理(以CFS演示)
  • 小米MIX Fold 4折叠屏手机背面渲染图曝光
  • 单链表的学习与基础运用p
  • python脚本“文档”撰写——“诱骗”ai撰写“火火的动态”python“自动”脚本文档
  • 深入了解以太坊
  • 【css3】浏览器内核及其兼容性
  • axios 和 cookie 的那些事
  • java正则表式的使用
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • React 快速上手 - 07 前端路由 react-router
  • sessionStorage和localStorage
  • 产品三维模型在线预览
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 区块链共识机制优缺点对比都是什么
  • 微服务核心架构梳理
  • 优化 Vue 项目编译文件大小
  • nb
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​2021半年盘点,不想你错过的重磅新书
  • ​iOS实时查看App运行日志
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​字​节​一​面​
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (145)光线追踪距离场柔和阴影
  • (31)对象的克隆
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (黑马C++)L06 重载与继承
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (四)js前端开发中设计模式之工厂方法模式
  • ***详解账号泄露:全球约1亿用户已泄露
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .net core 管理用户机密
  • .Net 基于.Net8开发的一个Asp.Net Core Webapi小型易用框架
  • .NET中 MVC 工厂模式浅析
  • .考试倒计时43天!来提分啦!
  • /usr/bin/env: node: No such file or directory
  • /var/log/cvslog 太大
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • [ 网络基础篇 ] MAP 迈普交换机常用命令详解
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [android] 练习PopupWindow实现对话框
  • [AutoSar]BSW_Memory_Stack_004 创建一个简单NV block并调试
  • [BZOJ]4817: [Sdoi2017]树点涂色