You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者Александр А

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:25:18