【FFmpeg】HW解码器输出 硬件帧 or 软件帧

cpp 复制代码
ret = avcodec_send_packet(avctx, packet);
if (ret < 0)
{
    MError("Error during decoding: %d(%s)\n", ret, av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, ret));
    return ret;
}

while (1)
{
    ret = avcodec_receive_frame(avctx, m_ptAVframe);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
    {
        return 0;
    }
    else if (ret < 0)
    {
        MError("Error while decoding: %d(%s)\n", ret, av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, ret));
        goto fail;
    }

    if (m_ptAVframe->format == m_enhw_pix_fmt)
    {
        /* retrieve data from GPU to CPU */
        if ((ret = av_hwframe_transfer_data(m_ptAVsw_frame, m_ptAVframe, 0)) < 0)
        {
            MError("Error transferring the data to system memory: %d(%s)\n", ret, av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, ret));
            goto fail;
        }
        tmp_frame = m_ptAVsw_frame;
    }
    else
    {
        tmp_frame = m_ptAVframe;
    }

    // 直接使用tmp_frame->data进行NV12到I420转换
    if (AV_PIX_FMT_NV12 == (AVPixelFormat)tmp_frame->format)
    {
        // 直接使用tmp_frame的数据指针进行转换
        ret = libyuv_NV12ToI420(
            tmp_frame->data[0],                    // Y平面
            tmp_frame->linesize[0],               // Y行跨度
            tmp_frame->data[1],                    // UV平面
            tmp_frame->linesize[1],               // UV行跨度
            pBufOut,                              // 输出的I420数据
            dwBufoutLen                           // 输出缓冲区大小
        );
        
        if (ret < 0)
        {
            MError("NV12 to I420 conversion failed: %d\n", ret);
            goto fail;
        }

        m_dwOutWidth = tmp_frame->width;
        m_dwOutHeight = tmp_frame->height;
        m_dwCurBufFrmLen = // 计算I420数据大小: width * height * 1.5
            tmp_frame->width * tmp_frame->height + 
            (tmp_frame->width / 2) * (tmp_frame->height / 2) * 2;
    }
    else
    {
        // 其他格式处理...
        size = av_image_get_buffer_size((AVPixelFormat)tmp_frame->format, 
                                       tmp_frame->width, tmp_frame->height, 1);
        if (size <= FFMPEG_YUV_BUFFER_SIZE && NULL != pBufOut && dwBufoutLen)
        {
            ret = av_image_copy_to_buffer(pBufOut, size,
                (const uint8_t * const *)tmp_frame->data,
                (const int *)tmp_frame->linesize, (AVPixelFormat)tmp_frame->format,
                tmp_frame->width, tmp_frame->height, 1);
            if (ret < 0)
            {
                MError("Can not copy image to buffer: %d(%s)\n", ret, av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, ret));
                goto fail;
            }
            m_dwCurBufFrmLen = size;
        }
    }
}

问题:上述硬解代码中,必须使用 av_hwframe_transfer_data 接口输出的 m_ptAVsw_frame帧来转色吗?能不能直接使用 m_ptAVframe 帧?

答案是:不能直接使用 m_ptAVframe 帧进行转色,原因如下:

硬件解码帧的特点

  1. m_ptAVframe 是硬件帧

    • 数据存储在GPU显存中
    • 格式是硬件特定的(如DXVA2、CUDA、VAAPI等)
    • 不能直接被CPU访问和处理
  2. m_ptAVsw_frame 是软件帧

    • 数据存储在系统内存中
    • 格式是标准的像素格式(如NV12、YUV420P等)
    • 可以被CPU直接访问和处理

为什么必须使用 av_hwframe_transfer_data

cpp 复制代码
if (m_ptAVframe->format == m_enhw_pix_fmt)
{
    /* 这一步是必需的 - 将数据从GPU传输到CPU */
    if ((ret = av_hwframe_transfer_data(m_ptAVsw_frame, m_ptAVframe, 0)) < 0)
    {
        MError("Error transferring the data to system memory\n");
        goto fail;
    }
    tmp_frame = m_ptAVsw_frame;  // 使用传输后的软件帧
}

如果直接使用硬件帧会怎样

cpp 复制代码
// 错误的做法 - 直接使用硬件帧
if (AV_PIX_FMT_NV12 == (AVPixelFormat)m_ptAVframe->format)  // 这个判断可能就不成立
{
    // 这里m_ptAVframe->data指向的是GPU显存
    // libyuv无法直接访问,会导致访问违规或错误数据
    libyuv_NV12ToI420(m_ptAVframe->data[0], ...);
}

硬件解码数据流

复制代码
GPU显存 (不可直接访问)       系统内存 (可直接访问)        输出缓冲区
     ↓                           ↓                       ↓
m_ptAVframe ──av_hwframe_transfer_data──→ m_ptAVsw_frame ──libyuv_NV12ToI420──→ pBufOut
(硬件帧)                          (软件帧)                   (I420数据)
     ↓                           ↓                       ↓
  DXVA2                        NV12                    I420
 CUDA等                       YUV420P                 等格式

验证方法

你可以添加调试信息来确认:

cpp 复制代码
printf("硬件帧格式: %d (%s)\n", m_ptAVframe->format, av_get_pix_fmt_name((AVPixelFormat)m_ptAVframe->format));
printf("硬件帧数据指针: data[0]=%p, data[1]=%p\n", m_ptAVframe->data[0], m_ptAVframe->data[1]);

if (m_ptAVframe->format == m_enhw_pix_fmt)
{
    if ((ret = av_hwframe_transfer_data(m_ptAVsw_frame, m_ptAVframe, 0)) < 0)
    {
        // 错误处理
    }
    
    printf("软件帧格式: %d (%s)\n", m_ptAVsw_frame->format, av_get_pix_fmt_name((AVPixelFormat)m_ptAVsw_frame->format));
    printf("软件帧数据指针: data[0]=%p, data[1]=%p\n", m_ptAVsw_frame->data[0], m_ptAVsw_frame->data[1]);
    
    tmp_frame = m_ptAVsw_frame;
}

总结

必须使用 av_hwframe_transfer_data,因为:

  1. 内存空间不同:硬件帧在GPU显存,软件帧在系统内存
  2. 访问权限:CPU无法直接访问GPU显存中的数据
  3. 数据格式:硬件帧可能是特定于硬件的编码格式,需要转换为标准像素格式
  4. API限制:libyuv等图像处理库只能处理系统内存中的数据

av_hwframe_transfer_data 的作用就是建立这个"桥梁",将数据从GPU传输到CPU可访问的内存中。

相关推荐
skyjilygao14 小时前
n8n整合ffmpeg
ffmpeg·视频编辑·n8n
别动哪条鱼18 小时前
SDL 函数对各对象缓冲区的影响
网络·数据结构·ffmpeg
daidaidaiyu1 天前
FFmpeg 关键的结构体
c++·ffmpeg
扶尔魔ocy2 天前
【QT window】ffmpeg实现录音功能之无损格式--PCM
ffmpeg·pcm
止礼2 天前
FFmpeg8.0.1 源代码的深入分析
ffmpeg
小曾同学.com2 天前
音视频中的“透传”与“DTS音频”
ffmpeg·音视频·透传·dts
vivo互联网技术2 天前
数字人动画云端渲染方案
前端·ffmpeg·puppeteer·web3d
止礼2 天前
FFmpeg8.0.1 编解码流程
ffmpeg
qs70162 天前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg