【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可访问的内存中。

相关推荐
长沙红胖子Qt5 小时前
FFmpeg开发笔记(十三):ffmpeg采集麦克风音频pcm重采样为aac录音为AAC文件
笔记·ffmpeg·音视频
feiyangqingyun5 小时前
全网首发/Qt结合ffmpeg实现rist推拉流/可信赖的互联网流媒体协议/跨平台支持各个系统
qt·ffmpeg·rist推拉流
shenhuxi_yu1 天前
ffmpeg avio使用示例
ffmpeg
aqi002 天前
FFmpeg开发笔记(八十二)使用国产直播服务器smart_rtmpd执行推流操作
ffmpeg·音视频·直播·流媒体
陈增林2 天前
用 PyQt5 + FFmpeg 打造批量视频音频提取器
qt·ffmpeg·音视频
西瓜er3 天前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
QMCY_jason3 天前
ubuntu 24.04 FFmpeg编译 带Nvidia 加速记录
linux·ubuntu·ffmpeg
eqwaak03 天前
动态图表导出与视频生成:精通Matplotlib Animation与FFmpeg
开发语言·python·ffmpeg·音视频·matplotlib
执尺量北斗3 天前
LinkMate 智能会议室系统:基于 Qt / QML / WebRTC / FFmpeg / Whisper / OpenGL 的实时音视频会议平台
qt·ffmpeg·webrtc