V2EX 10小时前
[C++] 用智能指针管理 ffmpeg 中的数据结构是有必要的吗?
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文探讨了在C++中利用智能指针管理FFmpeg的C风格API(如AVFormatContext、AVCodecContext等)的有效性。作者通过`avformat_alloc_context`结合`std::unique_ptr`和自定义删除器展示了管理思路,但随后指出`avformat_open_input`和`avformat_alloc_output_context2`等API需要接受二级指针,并且`avformat_alloc_output_context2`会直接覆盖传入的指针,这可能导致使用智能指针进行内存管理时出现问题,引发了对智能指针管理FFmpeg资源必要性的质疑,并寻求解决方案。

💡 **智能指针管理FFmpeg资源的初衷与实现**:FFmpeg的API多为C风格,返回裸指针,在C++环境下,使用`std::unique_ptr`并配合自定义删除器(如`avformat_free_context`)是一种自然而然的内存管理思路,旨在避免手动释放资源,提高代码安全性和健壮性,例如`std::unique_ptr fmt(avformat_alloc_context(), deleter);`。

⚠️ **`avformat_open_input`带来的挑战**:该API的函数签名要求传入`AVFormatContext**`(二级指针),即使传入的是`unique_ptr`的裸指针,也需要通过`fmt.get()`获取,再取地址传递,这种方式虽然可行,但增加了代码的复杂性,且不够优雅,让人开始质疑智能指针的引入是否真的简化了管理。

❌ **`avformat_alloc_output_context2`引发的内存泄漏风险**:与`avformat_open_input`类似,`avformat_alloc_output_context2`同样需要二级指针。然而,关键问题在于它会在内部直接将传入的指针(`*avctx`)设置为`NULL`,并分配新的内存地址。如果此时智能指针`fmt`已经管理了一个通过`avformat_alloc_context`分配的`AVFormatContext`,那么在`avformat_alloc_output_context2`执行后,`fmt`指向的内存将与`avctx`指向的内存脱钩,且`fmt`中的旧指针未被释放,导致内存泄漏。

🤔 **对智能指针管理必要性的根本质疑**:鉴于上述API的行为,特别是`avformat_alloc_output_context2`直接覆盖传入指针且不考虑其原有状态的特性,作者对在C++中使用智能指针来管理FFmpeg的资源(如`AVFormatContext`)的必要性产生了深刻的怀疑,认为这种方式可能弊大于利,并渴望获得更明智的指导。

ffmpeg 的 api 和 数据结构都是 c 风格,当我在 c++ 中使用它们时,很自然就想到用智能指针去管理(例如 AVFormatContext*AVCodecContext* 等),因为可以自定义删除器,将 ffmpeg 提供的 free 操作放进去;但 ffmpeg 中的一些 api 需要传入裸指针,一些 api 甚至会在内部直接分配空间,这样子用智能指针管理的想法会不会是没有必要的?

AVFormatContext 来举例,正常可以像这样得到一个被智能指针管理的 AVFormatContext 结构

auto deleter = [](AVFormatContext* f){    if(f) avformat_free_context(f);};std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter);

但和它相关的一个 api 是 avformat_open_input,它的函数声明如下(以下贴出一部分实现)

int avformat_open_input(AVFormatContext **ps, const char *url,                        const AVInputFormat *fmt, AVDictionary **options);                        // demux.c 下 avformat_open_input 的一部分实现int avformat_open_input(AVFormatContext **ps, const char *filename,                        const AVInputFormat *fmt, AVDictionary **options){    ...    AVFormatContext *s = *ps;    ...    if (!s && !(s = avformat_alloc_context()))        return AVERROR(ENOMEM);    ...}

可以看到 avformat_open_input 需要一个二级指针,所以需要直接传入裸指针。如果想要将一个初始化的 unique_ptr<AVFormatContext> 搭配 avformat_open_input 使用,就需要像这样(网上看到的做法)

auto deleter = [](AVFormatContext* f){    if(f) avformat_free_context(f);};std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter);auto tmp = fmt.get();avformat_open_input(&tmp, ...);

到这里我就开始怀疑用 unique_ptr 管理 AVFormatContext 的意义了,不过以上这个例子还好,只是观感上没那么优雅。但以下的例子让我质疑用智能指针做管理的必要。

AVFormatContext 还有一个相关的 api 是 avformat_alloc_output_context2,以下是函数声明和部分实现:

int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat,                                   const char *format_name, const char *filename);                                                                      int avformat_alloc_output_context2(AVFormatContext **avctx, const AVOutputFormat *oformat,                                   const char *format, const char *filename){    AVFormatContext *s = avformat_alloc_context();    int ret = 0;    *avctx = NULL;    ...    *avctx = s;}

可以看到,avformat_alloc_output_context2 同样需要传入二级指针,但与 avformat_open_input 的区别在于,它内部直接将 *avctx = NULL,并没有判断其是否为空,同时还将分配了新的内存地址给 avctx,这也就意味着以下的操作会造成内存泄漏:

auto deleter = [](AVFormatContext* f){    if(f) avformat_free_context(f);};std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter);auto tmp = fmt.get();avformat_alloc_output_context2(&tmp, ...);

至此让我产生用智能指针管理 ffmpeg 数据结构的必要性,有没有大佬来解答一下。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

FFmpeg C++ 智能指针 内存管理 RAII
相关文章