FFmpeg 解码内存泄漏汇总,sws_getContext函数无法释放问题

文章详细讨论了在使用Atlas200开发板进行ffmpeg拉流和解码时遇到的内存泄漏问题,特别提到了av_read_frame可能导致的内存风险,需要正确使用av_packet_unref进行释放。此外,AVPacket和AVFrame的释放顺序以及sws_getContext函数的内存管理也是关键点,使用sws_getCachedContext可以避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面在使用atlas 200开发板进行ffmpeg库进行拉流+dvpp硬件解码+npu进行yuv转码rgb时,进行长时间连续解码和压力测试,会有明显内存增加问题。连续工作21个小时后就会被linux 内核kill掉。

通过逐步注掉代码、和华为技术人员沟通等方式。最后发现内存泄漏有几个地方,这里不讲npu转码部分,只提ffmpeg拉流和解码(本次没用到)可能存在的内存泄漏的风险:

一、av_read_frame的问题

av_read_frame有内存泄漏风险,av_read_frame 每次循环后必须执行av_packet_unref(pkt)进行释放。

对于多个 AVPacket 共享同一个缓存空间,FFmpeg 使用的引用计数的机制来管理(即浅拷贝机制)

  • AVBufferFFmpeg中的缓冲区,一开始时AVBuffer的引用计数(refcount)初始化为 0
  • 当有新的Packet引用共享的缓存空间时,就将引用计数再 +1
  • Packet释放掉对AVBuffer这块共享缓存空间的引用时,将引用计数 -1
  • 只有当refcount为 0 的时候,才会释放掉缓存空间AVBuffer

如果在循环内退出需要释放pkt,如果作为while的条件不成立时需要在while代码块之外也需要释放。最后需要执行av_packet_free和av_free再进行释放packet的外壳。

while (av_read_frame(pa->fmt_ctx, pkt) >= 0) 
{
    int64_t dts;
    if(pkt->stream_index != pa->videoStream)
    {
        av_packet_unref(pkt);     
        continue;
     }
    av_packet_unref(pkt);
}    
av_packet_unref(pkt);
if(pa->packet != NULL)
{
    av_packet_unref(pa->packet); 
}
av_packet_free(&pa->packet);
av_free(pa->packet);

 av_packet_alloc 和 av_packet_free 必须要配对使用,否则会造成内存泄漏。av_packet_free 实际是释放AVPacket 的空间。

FFmpeg 中 av_init_packet() 和 av_packet_alloc()av_packet_unref() 三者的区别以及用法

av_packet_alloc 中并没有调用 av_init_packet , 但av_packet_alloc 中调用了 av_packet_unref ,而 av_packet_unref 内部才调用 av_init_packet (老版本的 ffmpeg 中 av_packet_alloc 是直接调用 av_init_packet )

 在注意,在 av_init_packet 中初始化并不涉及 data 和 size 成员,它们必须分别初始化。

av_new_packet 的作用是为 pkt 分配一个指定大小的内存。av_new_packet 中为 pkt 的 buf 分配一个大小,且内部还调用了 av_init_packet 对 pkt 进行初始化。而且注意,该函数的返回值并不是引用计数,之前在网上看到有些博客写返回的是引用计数(是不对的),这个函数返回值永远都是 0,没有具体意义。

一般在开发过程中都会遵循以下几步
AVPacket *pkt = NULL;
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
av_packet_free(&pkt);

下面通过一个测试示例来应用下这几个 API

#define MEM_ITEM_SIZE (20*1024*102)
void av_packet_test1()
{
    AVPacket *pkt = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE);  
    //把数据写到data区域,  av_init_packet 内部初始化并不是涉及 data 和 size
    memccpy(pkt->data,(void*)&av_packet_test1,1,MEM_ITEM_SIZE);
    av_packet_free(&pkt);
}

av_packet_alloc 只是为 pkt 分配了内存空间,buf 依然为 NULL。在 av_new_packet 之后真正的为 buf 分配内存空间。

av_packet_free 先把 pkt 中的内容清空,然后再把指针清空,让 pkt 彻底无法使用了,如果需要重新使用,需要重新分配内存。

二、AVFrame* 结构体的释放

   AVFrame结构体需要

1、先执行av_frame_unref()释放内部

2、调用av_frame_free(&pa->srcFrame);释放外壳

三、注意释放顺序

  内存释放要按顺序进行释放。以防释放掉外壳后,内部就不能进行释放了。

如下面是释放上下文的顺序。

sws_freeContext(pSwsContext);               
av_frame_free(&pAVFrame);
avcodec_close(pAVCodecContext);
avformat_close_input(&pAVFormatContext);

四、sws_getContext函数无法释放问题

  经过反复查找,发现sws_getContext函数调用后存在内存泄漏问题。非常奇怪的是

程序中已调用了sws_freeContext(pa->sws_ctx)进行释放,内存依然泄漏。

     后发现有类型函数 sws_getCachedContext(),替换后发现内存不再泄漏!!!!

参考:FFmpeg开发笔记(四):ffmpeg解码的基本流程详解_ffmpeg3.x解码流程图_长沙红胖子Qt的博客-CSDN博客

<think>好的,我现在需要解决用户提供的FFmpeg代码中的两个问题:变量未声明和av_register_all()报错不存在。首先,我得仔细看看用户提供的代码片段,确认问题所在。 用户提到的代码中,有几个变量如codecCtx、frame、packet、filename等都没有声明。这可能是因为示例代码省略了变量声明部分,用户在实际编译时遇到了错误。我需要补全这些变量的声明,并确保类型正确。 接下来是av_register_all()报错的问题。我记得FFmpeg在较新的版本中已经弃用了av_register_all(),特别是在4.0版本之后,这个方法被移除了。用户可能使用了新版本的FFmpeg,导致编译错误。需要更新代码,去掉这个函数,并确认其他初始化步骤是否也需要调整。 然后,我需要检查整个FFmpeg初始化和解码流程是否正确。比如,avformat_open_input的参数是否正确,是否处理了错误情况。同时,解码循环中的av_read_frame、avcodec_send_packet和avcodec_receive_frame的使用是否正确,是否需要处理返回值和错误码。 另外,用户可能对如何将解码后的帧转换为QImage并显示在Qt界面上有疑问。这部分需要解释如何从AVFrame提取数据,处理像素格式转换(比如从YUV到RGB),然后创建QImage,并可能使用QPainter或QVideoWidget进行渲染。 还要考虑内存管理和资源释放问题,比如在退出时是否正确释放了AVFormatContext、AVCodecContext、AVPacket和AVFrame等资源,避免内存泄漏。 最后,需要提醒用户注意FFmpeg库的版本兼容性,以及编译项目时链接正确的库文件和包含路径。可能还需要指导用户如何安装和配置FFmpeg,特别是在不同操作系统下的步骤差异。 总结步骤: 1. 补全变量声明,确保所有使用的变量都已正确声明和初始化。 2. 移除已弃用的av_register_all(),更新初始化代码。 3. 检查并处理FFmpeg函数的返回值,添加错误处理逻辑。 4. 解释如何将AVFrame转换为QImage并显示。 5. 添加资源释放代码,防止内存泄漏。 6. 提供编译和配置FFmpeg的注意事项。 在思考过程中,需要确保代码示例准确,变量命名一致,并符合FFmpeg的最新API使用规范。同时,要使用用户能够理解的中文进行解释,避免专业术语过多导致理解困难。</think>以下是更新后的FFmpeg解码代码实现,包含完整变量声明和适配新版FFmpeg的修改: ```cpp // FFmpeg头文件 extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> } // 变量声明部分 AVFormatContext *pFormatCtx = nullptr; // 封装格式上下文 AVCodecParameters *origin_par = nullptr; // 原始编解码参数 AVCodecContext *codecCtx = nullptr; // 解码器上下文 AVPacket *packet = nullptr; // 压缩数据包 AVFrame *frame = nullptr; // 解码后帧数据 const AVCodec *codec = nullptr; // 解码器 int video_stream_index = -1; // 视频流索引 const char *filename = "input.cavs"; // 输入文件名 // 初始化FFmpeg(新版无需av_register_all()) void init_ffmpeg() { // 自动初始化编解码器和协议 avformat_network_init(); // 如果需要网络流支持 } // 主要解码流程 void decode_video() { // 打开文件 if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { qDebug() << "无法打开文件"; return; } // 获取流信息 if(avformat_find_stream_info(pFormatCtx, NULL) < 0) { qDebug() << "无法获取流信息"; return; } // 查找视频流 for(unsigned int i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; origin_par = pFormatCtx->streams[i]->codecpar; break; } } if(video_stream_index == -1) { qDebug() << "未找到视频流"; return; } // 查找解码器(需确认AV_CODEC_ID_CAVS是否支持) codec = avcodec_find_decoder(origin_par->codec_id); if(!codec) { qDebug() << "找不到解码器"; return; } // 创建解码器上下文 codecCtx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codecCtx, origin_par); // 打开解码器 if(avcodec_open2(codecCtx, codec, NULL) < 0) { qDebug() << "无法打开解码器"; return; } // 分配帧和包内存 frame = av_frame_alloc(); packet = av_packet_alloc(); // 解码循环 while(av_read_frame(pFormatCtx, packet) >= 0) { if(packet->stream_index == video_stream_index) { if(avcodec_send_packet(codecCtx, packet) == 0) { while(avcodec_receive_frame(codecCtx, frame) == 0) { // 转换为QImage(示例YUV420P转RGB) QImage image(frame->width, frame->height, QImage::Format_RGB32); SwsContext *swsCtx = sws_getContext( frame->width, frame->height, codecCtx->pix_fmt, frame->width, frame->height, AV_PIX_FMT_RGB32, SWS_BILINEAR, NULL, NULL, NULL ); uint8_t *destData[1] = { image.bits() }; int destLinesize[1] = { (int)image.bytesPerLine() }; sws_scale(swsCtx, frame->data, frame->linesize, 0, frame->height, destData, destLinesize); sws_freeContext(swsCtx); // 发送图像到界面显示(需要信号槽机制) emit frameReady(image); } } } av_packet_unref(packet); } // 清理资源 av_packet_free(&packet); av_frame_free(&frame); avcodec_free_context(&codecCtx); avformat_close_input(&pFormatCtx); } ``` 关键修改说明: 1. **移除废弃函数**: - `av_register_all()` 在FFmpeg 4.0+ 已废弃,现在自动注册编解码器 - 新增 `avformat_network_init()` 用于网络流支持(可选) 2. **现代API适配**: ```cpp // 旧版 codec = avcodec_find_decoder(AV_CODEC_ID_CAVS); // 新版推荐写法 codec = avcodec_find_decoder(origin_par->codec_id); ``` 3. **资源管理**: ```cpp // 内存分配 codecCtx = avcodec_alloc_context3(codec); // 参数传递 avcodec_parameters_to_context(codecCtx, origin_par); // 释放资源 avcodec_free_context(&codecCtx); ``` 4. **像素格式转换**: - 使用 `sws_getContext` + `sws_scale` 进行色彩空间转换 - 支持不同像素格式到RGB32的转换 5. **错误处理建议**: ```cpp if(avcodec_send_packet(...) != 0) { // 处理发送失败 } while(avcodec_receive_frame(...) >= 0) { // 正常处理帧 } ``` 6. **Qt集成要点**: - 将解码后的QImage通过信号发送到GUI线程 ```cpp // 在类声明中添加信号 signals: void frameReady(const QImage &frame); // 在界面类中连接信号 connect(decoderThread, &Decoder::frameReady, this, &PlayerWindow::updateFrame); ``` 编译注意事项: ```qmake # 在.pro文件中添加FFmpeg链接 LIBS += -lavformat -lavcodec -lswscale -lavutil INCLUDEPATH += /path/to/ffmpeg/headers ``` 版本兼容性提示: - FFmpeg ≥ 4.0 需要此写法 - 如果仍需支持旧版,可添加版本宏: ```cpp #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) av_register_all(); #endif ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值