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

相关推荐
我科绝伦(Huanhuan Zhou)10 小时前
Oracle AWR管理与快照操作完整指南
数据库·oracle·ffmpeg
梵尔纳多10 小时前
ffmpeg 使用滤镜实现播放倍速
c++·qt·ffmpeg
无敌最俊朗@2 天前
音视频播放的核心处理流程
ffmpeg
mortimer3 天前
搞懂FFmpeg中2个桀骜不驯的参数:CRF 与 Preset
ffmpeg·音视频开发·视频编码
2401_841495644 天前
Windows 系统中ffmpeg安装问题的彻底解决
windows·python·ffmpeg·bug·语音识别·下载·安装步骤
八月的雨季 最後的冰吻4 天前
FFmpeg --15-视频解码: AVIO内存输入模式分析
ffmpeg·音视频
aqi004 天前
FFmpeg开发笔记(八十八)基于Compose的国产电视直播开源框架MyTV
android·ffmpeg·音视频·直播·流媒体
present12274 天前
一段音频/视频分离成人声与伴奏,Windows + Anaconda 快速跑通 Spleeter(离线可用)
windows·职场和发展·ffmpeg·音视频·娱乐·媒体
fxshy5 天前
python使用ffmpeg对视频进行转码
python·ffmpeg·音视频
zhangzhangkeji5 天前
FFMPEG - 6:合并、提取音视频;截取、连接音视频,
ffmpeg·音视频