FFmpeg源码分析:avcodec_open()打开编解码器

2024-06-15 10:32

本文主要是介绍FFmpeg源码分析:avcodec_open()打开编解码器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

FFmpeg在libavcodec模块提供编解码能力,使用流程:寻找编解码器、分配编解码器上下文、打开编解码器、编码成AVPacket/解码成AVFrame、关闭编解码器。本文以avcodec_open()打开编解码器为主,对编解码整体流程进行分析。

一、寻找编解码器

寻找解码器的函数位于libavcodec/allcodecs.c。可以通过avcodec_find_encoder()根据AVCodecID寻找编码器,也可以通过avcodec_find_encoder_by_name()根据名称进行查找。同样地,可以使用avcodec_find_decoder()和avcodec_find_decoder_by_name()来寻找解码器。具体查找流程可参考:FFmpeg源码分析——寻找编解码器。

二、分配编解码器上下文

寻找到编解码器后,使用AVCodec分配编解码器上下文,代码位于libavcodec/options.c。首先使用av_malloc()来分配内存,然后调用init_context_defaults()初始化默认参数,代码如下:

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;
}

三、打开编解码器

1、avcodec_open

在分配好编解码器上下文后,使用AVCodecContext来打开编解码器。其函数声明位于libavcodec/avcodec.h:

/*** Initialize the AVCodecContext to use the given AVCodec. Prior to using this* function the context has to be allocated with avcodec_alloc_context3().** The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),* avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for* retrieving a codec.** @warning This function is not thread safe!** @note Always call this function before using decoding routines (such as* @ref avcodec_receive_frame()).** @code* av_dict_set(&opts, "b", "2.5M", 0);* codec = avcodec_find_decoder(AV_CODEC_ID_H264);* if (!codec)*     exit(1);** context = avcodec_alloc_context3(codec);** if (avcodec_open2(context, codec, opts) < 0)*     exit(1);* @endcode** @param avctx The context to initialize.* @param codec The codec to open this context for.* @param options A dictionary filled with AVCodecContext and codec-private options.** @return zero on success, a negative value on error*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

由描述可知,该函数不是线程安全,在执行编解码操作之前调用该函数。 函数实现位于libavcodec/avcodec.c,代码如下:

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
{int ret = 0;int codec_init_ok = 0;AVDictionary *tmp = NULL;AVCodecInternal *avci;// 检查codec是否已经打开if (avcodec_is_open(avctx))return 0;// 检查codec是否有效if (!codec && !avctx->codec) {return AVERROR(EINVAL);}if (codec && avctx->codec && codec != avctx->codec) {return AVERROR(EINVAL);}if (!codec)codec = avctx->codec;if (avctx->extradata_size < 0 || avctx->extradata_size >= FF_MAX_EXTRADATA_SIZE)return AVERROR(EINVAL);// 拷贝options参数集合if (options)av_dict_copy(&tmp, *options, 0);lock_avcodec(codec);// 分配AVCodecInternal内存avci = av_mallocz(sizeof(*avci));if (!avci) {ret = AVERROR(ENOMEM);goto end;}// 分配frame、packet、fifo内存avctx->internal      = avci;avci->buffer_frame   = av_frame_alloc();avci->buffer_pkt     = av_packet_alloc();avci->es.in_frame    = av_frame_alloc();avci->ds.in_pkt      = av_packet_alloc();avci->last_pkt_props = av_packet_alloc();avci->pkt_props      = av_fifo_alloc(sizeof(*avci->last_pkt_props));if (!avci->buffer_frame || !avci->buffer_pkt          ||!avci->es.in_frame  || !avci->ds.in_pkt           ||!avci->last_pkt_props || !avci->pkt_props) {ret = AVERROR(ENOMEM);goto free_and_end;}avci->skip_samples_multiplier = 1;if (codec->priv_data_size > 0) {if (!avctx->priv_data) {avctx->priv_data = av_mallocz(codec->priv_data_size);if (!avctx->priv_data) {ret = AVERROR(ENOMEM);goto free_and_end;}if (codec->priv_class) {*(const AVClass **)avctx->priv_data = codec->priv_class;av_opt_set_defaults(avctx->priv_data);}}if (codec->priv_class && (ret = av_opt_set_dict(avctx->priv_data, &tmp)) < 0)goto free_and_end;} else {avctx->priv_data = NULL;}if ((ret = av_opt_set_dict(avctx, &tmp)) < 0)goto free_and_end;if (avctx->codec_whitelist && av_match_list(codec->name, avctx->codec_whitelist, ',') <= 0) {ret = AVERROR(EINVAL);goto free_and_end;}if (!(avctx->coded_width && avctx->coded_height && avctx->width && avctx->height &&(avctx->codec_id == AV_CODEC_ID_H264 || avctx->codec_id == AV_CODEC_ID_VP6F || avctx->codec_id == AV_CODEC_ID_DXV))) {if (avctx->coded_width && avctx->coded_height)ret = ff_set_dimensions(avctx, avctx->coded_width, avctx->coded_height);else if (avctx->width && avctx->height)ret = ff_set_dimensions(avctx, avctx->width, avctx->height);if (ret < 0)goto free_and_end;}// 检查查视频宽高if ((avctx->coded_width || avctx->coded_height || avctx->width || avctx->height)&& (av_image_check_size2(avctx->coded_width, avctx->coded_height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0|| av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0)) {ff_set_dimensions(avctx, 0, 0);}// 检查视频宽高比if (avctx->width > 0 && avctx->height > 0) {if (av_image_check_sar(avctx->width, avctx->height,avctx->sample_aspect_ratio) < 0) {avctx->sample_aspect_ratio = (AVRational){ 0, 1 };}}// 检查音频声道if (avctx->channels > FF_SANE_NB_CHANNELS || avctx->channels < 0) {ret = AVERROR(EINVAL);goto free_and_end;}// 如果是音频解码器,检查声道数if (av_codec_is_decoder(codec) &&codec->type == AVMEDIA_TYPE_AUDIO &&!(codec->capabilities & AV_CODEC_CAP_CHANNEL_CONF) &&avctx->channels == 0) {ret = AVERROR(EINVAL);goto free_and_end;}// 检查音频采样率if (avctx->sample_rate < 0) {ret = AVERROR(EINVAL);goto free_and_end;}// 检查块对齐if (avctx->block_align < 0) {ret = AVERROR(EINVAL);goto free_and_end;}avctx->codec = codec;if ((avctx->codec_type == AVMEDIA_TYPE_UNKNOWN || avctx->codec_type == codec->type) &&avctx->codec_id == AV_CODEC_ID_NONE) {avctx->codec_type = codec->type;avctx->codec_id   = codec->id;}// 检查codec_id和codec_typeif (avctx->codec_id != codec->id || (avctx->codec_type != codec->type&& avctx->codec_type != AVMEDIA_TYPE_ATTACHMENT)) {ret = AVERROR(EINVAL);goto free_and_end;}avctx->frame_number = 0;avctx->codec_descriptor = avcodec_descriptor_get(avctx->codec_id);// 检查codec能力集,codec是否属于实验性的if ((avctx->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) &&avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {const char *codec_string = av_codec_is_encoder(codec) ? "encoder" : "decoder";const AVCodec *codec2;codec2 = av_codec_is_encoder(codec) ? avcodec_find_encoder(codec->id) : avcodec_find_decoder(codec->id);if (!(codec2->capabilities & AV_CODEC_CAP_EXPERIMENTAL))ret = AVERROR_EXPERIMENTAL;goto free_and_end;}// 检查音频时间基if (avctx->codec_type == AVMEDIA_TYPE_AUDIO &&(!avctx->time_base.num || !avctx->time_base.den)) {avctx->time_base.num = 1;avctx->time_base.den = avctx->sample_rate;}if (av_codec_is_encoder(avctx->codec))ret = ff_encode_preinit(avctx);elseret = ff_decode_preinit(avctx);if (ret < 0)goto free_and_end;// 初始化编码器线程if (CONFIG_FRAME_THREAD_ENCODER && av_codec_is_encoder(avctx->codec)) {unlock_avcodec(codec);ret = ff_frame_thread_encoder_init(avctx, options ? *options : NULL);lock_avcodec(codec);if (ret < 0)goto free_and_end;}if (HAVE_THREADS&& !(avci->frame_thread_encoder && (avctx->active_thread_type&FF_THREAD_FRAME))) {ret = ff_thread_init(avctx);if (ret < 0) {goto free_and_end;}}if (!HAVE_THREADS && !(codec->caps_internal & FF_CODEC_CAP_AUTO_THREADS))avctx->thread_count = 1;if (   avctx->codec->init && (!(avctx->active_thread_type&FF_THREAD_FRAME)|| avci->frame_thread_encoder)) {ret = avctx->codec->init(avctx);if (ret < 0) {codec_init_ok = -1;goto free_and_end;}codec_init_ok = 1;}ret=0;// 如果是解码器if (av_codec_is_decoder(avctx->codec)) {// 检查码率if (!avctx->bit_rate)avctx->bit_rate = get_bit_rate(avctx);// 检查音频声道布局if (avctx->channel_layout) {int channels = av_get_channel_layout_nb_channels(avctx->channel_layout);if (!avctx->channels)avctx->channels = channels;else if (channels != avctx->channels) {char buf[512];av_get_channel_layout_string(buf, sizeof(buf), -1, avctx->channel_layout);avctx->channel_layout = 0;}}if (avctx->channels && avctx->channels < 0 ||avctx->channels > FF_SANE_NB_CHANNELS) {ret = AVERROR(EINVAL);goto free_and_end;}// 检查每个sample的位数if (avctx->bits_per_coded_sample < 0) {ret = AVERROR(EINVAL);goto free_and_end;}if (avctx->sub_charenc) {if (avctx->codec_type != AVMEDIA_TYPE_SUBTITLE) {ret = AVERROR(EINVAL);goto free_and_end;} else if (avctx->codec_descriptor->props & AV_CODEC_PROP_BITMAP_SUB) {avctx->sub_charenc_mode = FF_SUB_CHARENC_MODE_DO_NOTHING;} else {if (avctx->sub_charenc_mode == FF_SUB_CHARENC_MODE_AUTOMATIC)avctx->sub_charenc_mode = FF_SUB_CHARENC_MODE_PRE_DECODER;if (avctx->sub_charenc_mode == FF_SUB_CHARENC_MODE_PRE_DECODER) {ret = AVERROR(ENOSYS);goto free_and_end;}}}}if (codec->priv_data_size > 0 && avctx->priv_data && codec->priv_class) {av_assert0(*(const AVClass **)avctx->priv_data == codec->priv_class);}end:unlock_avcodec(codec);if (options) {av_dict_free(options);*options = tmp;}return ret;
free_and_end:if (avctx->codec && avctx->codec->close &&(codec_init_ok > 0 || (codec_init_ok < 0 &&avctx->codec->caps_internal & FF_CODEC_CAP_INIT_CLEANUP)))avctx->codec->close(avctx);if (HAVE_THREADS && avci->thread_ctx)ff_thread_free(avctx);if (codec->priv_class && avctx->priv_data)av_opt_free(avctx->priv_data);av_opt_free(avctx);if (av_codec_is_encoder(avctx->codec)) {av_freep(&avctx->extradata);avctx->extradata_size = 0;}av_dict_free(&tmp);av_freep(&avctx->priv_data);av_freep(&avctx->subtitle_header);av_frame_free(&avci->buffer_frame);av_packet_free(&avci->buffer_pkt);av_packet_free(&avci->last_pkt_props);av_fifo_freep(&avci->pkt_props);av_packet_free(&avci->ds.in_pkt);av_frame_free(&avci->es.in_frame);av_bsf_free(&avci->bsf);av_buffer_unref(&avci->pool);av_freep(&avci);avctx->internal = NULL;avctx->codec = NULL;goto end;
}

由源码可知,avcodec_open()主要是分配各种内存,然后进行各种参数检查,包括:视频宽高、音频采样率、声道数、声道布局、时间基、codec能力集等等。

2、avcodec_parameters_from_context

由于AVCodecContext已经被标记为过时,建议使用AVCodecParameters代替。在avcodec_par.c提供avcodec_parameters_from_context()把AVCodecContext参数拷贝到AVCodecParameters中。相反,如果需要把AVCodecParameters参数拷贝到AVCodecContext中,则调用avcodec_parameters_to_context()。

四、音视频编解码

在FFmpeg3.4版本,旧的编解码API已经标记为过时,新版改为异步方式。我们对新旧版本API进行介绍:

1、编码

旧版的视频编码API为avcodec_encode_video2(),音频编码API为avcodec_encode_audio2()。而新版本API统一为avcodec_send_frame/avcodec_receive_packet。

2、解码

旧版的视频编码API为avcodec_decode_video2(),音频编码API为avcodec_decode_audio4()。而新版本API统一为avcodec_send_packet/avcodec_receive_frame。

五、关闭编解码器

1、avcodec_close

avcodec_close()函数用于关闭编解码器。函数声明位于libavcodec/avcodec.h,声明如下:

/*** Close a given AVCodecContext and free all the data associated with it* (but not the AVCodecContext itself).** @note Do not use this function. Use avcodec_free_context() to destroy a* codec context (either open or closed). */
int avcodec_close(AVCodecContext *avctx);

 由描述可知,该函数用于关闭给定的编解码器上下文,以及关联的数据,但不包括AVCodecContext本身。代码如下:

int avcodec_close(AVCodecContext *avctx)
{int i;if (!avctx)return 0;if (avcodec_is_open(avctx)) {// 退出线程if (CONFIG_FRAME_THREAD_ENCODER &&avctx->internal->frame_thread_encoder && avctx->thread_count > 1) {ff_frame_thread_encoder_free(avctx);}if (HAVE_THREADS && avctx->internal->thread_ctx)ff_thread_free(avctx);if (avctx->codec && avctx->codec->close)avctx->codec->close(avctx);avctx->internal->byte_buffer_size = 0;av_freep(&avctx->internal->byte_buffer);av_frame_free(&avctx->internal->buffer_frame);av_packet_free(&avctx->internal->buffer_pkt);av_packet_unref(avctx->internal->last_pkt_props);// 清空fifo队列while (av_fifo_size(avctx->internal->pkt_props) >=sizeof(*avctx->internal->last_pkt_props)) {av_fifo_generic_read(avctx->internal->pkt_props,avctx->internal->last_pkt_props,sizeof(*avctx->internal->last_pkt_props),NULL);av_packet_unref(avctx->internal->last_pkt_props);}av_packet_free(&avctx->internal->last_pkt_props);av_fifo_freep(&avctx->internal->pkt_props);av_packet_free(&avctx->internal->ds.in_pkt);av_frame_free(&avctx->internal->es.in_frame);av_buffer_unref(&avctx->internal->pool);// 硬件加速反初始化if (avctx->hwaccel && avctx->hwaccel->uninit)avctx->hwaccel->uninit(avctx);av_freep(&avctx->internal->hwaccel_priv_data);// 释放bitstream filter资源av_bsf_free(&avctx->internal->bsf);av_freep(&avctx->internal);}for (i = 0; i < avctx->nb_coded_side_data; i++)av_freep(&avctx->coded_side_data[i].data);av_freep(&avctx->coded_side_data);avctx->nb_coded_side_data = 0;av_buffer_unref(&avctx->hw_frames_ctx);av_buffer_unref(&avctx->hw_device_ctx);if (avctx->priv_data && avctx->codec && avctx->codec->priv_class)av_opt_free(avctx->priv_data);av_opt_free(avctx);av_freep(&avctx->priv_data);if (av_codec_is_encoder(avctx->codec)) {av_freep(&avctx->extradata);}avctx->codec = NULL;avctx->active_thread_type = 0;return 0;
}

2、avcodec_free_context

avcodec_free_context()函数用于释放AVCodecContext上下文,与上面的分配上下文avcodec_alloc_context3()相对应。同样位于options.c,首先调用avcodec_close()函数来关闭编解码器,最终释放AVCodecContext结构体,具体代码如下:

void avcodec_free_context(AVCodecContext **pavctx)
{AVCodecContext *avctx = *pavctx;if (!avctx)return;// 调用avcodec_close关闭编解码器avcodec_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);// 释放AVCodecContext结构体av_freep(pavctx);
}

这篇关于FFmpeg源码分析:avcodec_open()打开编解码器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063197

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。