avcodec_send_frame对单个AVFrame是否线程安全?多线程编码异常求助
问题解决:多线程共享AVFrame编码时avcodec_send_frame错误及段错误
核心原因分析
- AVFrame非线程安全:直接在两个线程中操作同一个
AVFrame实例,AVFrame内部的引用计数、数据缓冲区操作无线程保护,并发读写会引发内存损坏,导致段错误或AVERROR_INVALIDDATA(-22)。 - 编码器上下文频繁创建销毁:每次快照都新建
AVCodecContext,不仅效率低下,还可能因资源初始化不完整或竞争导致编码失败。 - 同步逻辑不严谨:现有信号量和屏障的同步未完全隔离共享帧的访问,帧数据可能在编码过程中被其他线程修改。
- 帧格式与编码器不匹配:若
pCdcCtxInp的像素格式不是MJPEG编码器支持的类型(如YUV420P为兼容格式,其他格式可能不支持),会直接触发-22错误。
具体修复方案
1. 为每个线程创建独立AVFrame副本
绝对禁止直接共享同一个AVFrame实例,每个线程处理前通过av_frame_ref创建副本,处理完成后调用av_frame_unref释放:
// 复制共享帧到本地副本 AVFrame *snap_frame = av_frame_alloc(); if (!snap_frame) { // 内存错误处理 return -ENOMEM; } if (av_frame_ref(snap_frame, stEnc.pFrm) < 0) { av_frame_free(&snap_frame); return -1; } // 使用副本执行编码操作 if ((rt = avcodec_send_frame(pCdcCtx, snap_frame))) PrintErrorAV("snapshot encode send", rt); // ...后续编码逻辑 // 编码完成后释放副本 av_frame_unref(snap_frame); av_frame_free(&snap_frame);
2. 复用MJPEG编码器上下文
将编码器上下文的初始化移到快照函数外部,仅初始化一次,避免重复创建销毁:
// 模块级全局编码器上下文 static AVCodecContext *g_snap_cdc_ctx = NULL; // 程序启动时调用一次,初始化快照编码器 int InitSnapshotEncoder() { const AVCodec *pCdc = avcodec_find_encoder(AV_CODEC_ID_MJPEG); if (!pCdc) { fprintf(stderr, "ERROR find snapshot encoder\n"); return -1; } g_snap_cdc_ctx = avcodec_alloc_context3(pCdc); if (!g_snap_cdc_ctx) return -ENOMEM; g_snap_cdc_ctx->time_base = DEFAULT_TIME_BASE; g_snap_cdc_ctx->pix_fmt = pCdcCtxInp->pix_fmt; g_snap_cdc_ctx->height = pCdcCtxInp->height; g_snap_cdc_ctx->width = pCdcCtxInp->width; return avcodec_open2(g_snap_cdc_ctx, pCdc, NULL); } // 程序退出时销毁编码器上下文 void DeinitSnapshotEncoder() { if (g_snap_cdc_ctx) { avcodec_free_context(&g_snap_cdc_ctx); } }
之后在SnapshotEncode中直接使用g_snap_cdc_ctx即可,无需重复创建。
3. 完善线程同步逻辑
用互斥锁替代信号量,更适合临界区保护,确保同一时间只有一个线程访问共享帧:
// 全局互斥锁,保护共享帧stEnc.pFrm static pthread_mutex_t g_frame_mutex = PTHREAD_MUTEX_INITIALIZER; // 在快照函数中获取锁并复制帧 pthread_mutex_lock(&g_frame_mutex); AVFrame *snap_frame = av_frame_alloc(); if (!snap_frame || av_frame_ref(snap_frame, stEnc.pFrm) < 0) { pthread_mutex_unlock(&g_frame_mutex); return -1; } pthread_mutex_unlock(&g_frame_mutex);
4. 验证帧格式与编码器兼容性
检查pCdcCtxInp->pix_fmt是否为MJPEG编码器支持的格式(如YUV420P、YUVJ420P),若不兼容则提前转换:
// 示例:将NV12格式转换为YUV420P if (pCdcCtxInp->pix_fmt != AV_PIX_FMT_YUV420P) { struct SwsContext *sws_ctx = sws_getContext(pCdcCtxInp->width, pCdcCtxInp->height, pCdcCtxInp->pix_fmt, pCdcCtxInp->width, pCdcCtxInp->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); // 使用sws_scale完成格式转换,得到兼容帧后再编码 }
修改后的SnapshotEncode示例代码
// 假设已提前初始化g_snap_cdc_ctx和g_frame_mutex int SnapshotEncode(int (*callback)(const char * topic, const uint8_t * buf, int lbuf), const char * topic) { int rt = 0; AVFrame *snap_frame = NULL; AVPacket *pPkt = av_packet_alloc(); if (!pPkt) { PrintErrorAV("allocate snapshot AVPacket", (rt = AVERROR(ENOMEM))); return rt; } // 获取锁并复制共享帧 pthread_mutex_lock(&g_frame_mutex); snap_frame = av_frame_alloc(); if (!snap_frame || (rt = av_frame_ref(snap_frame, stEnc.pFrm)) < 0) { pthread_mutex_unlock(&g_frame_mutex); PrintErrorAV("copy snapshot frame", rt); goto END; } pthread_mutex_unlock(&g_frame_mutex); // 执行编码操作 if ((rt = avcodec_send_frame(g_snap_cdc_ctx, snap_frame))) { PrintErrorAV("snapshot encode send", rt); } else if ((rt = avcodec_receive_packet(g_snap_cdc_ctx, pPkt))) { PrintErrorAV("snapshot encode receive", rt); } if (rt == 0) { callback(topic, pPkt->data, pPkt->size); } END: if (snap_frame) { av_frame_unref(snap_frame); av_frame_free(&snap_frame); } av_packet_unref(pPkt); av_packet_free(&pPkt); return rt; }
内容的提问来源于stack exchange,提问作者Александр А
相关产品推荐
相关产品推荐

