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

相关推荐
!chen3 小时前
Oracle Deep Data Security (Deep Sec) 初体验
数据库·oracle·ffmpeg
wyw00001 天前
FFmpeg实现带颜色MXF转mp4
python·ffmpeg
aqi001 天前
FFmpeg开发笔记(一百零二)国产的音视频移动开源工具FFmpegAndroid
android·ffmpeg·kotlin·音视频·直播·流媒体
ltlovezh2 天前
FFmpeg 是怎么“猜”出文件格式的?源码级拆解 Demuxer 自动识别机制
ffmpeg
Mike_6662 天前
摩尔线程AB100安装torch环境
人工智能·深度学习·ffmpeg·aarch64·摩尔线程·musa
aqi002 天前
FFmpeg开发笔记(一百零一)跨平台的开源音视频移动框架MobileFFmpeg
android·ffmpeg·音视频·直播·流媒体
starvapour3 天前
ffmpeg基于glob匹配将文件夹中图像按文件名连接成mp4
ffmpeg
blevoice4 天前
JL杰理AC696N开发板上调试蓝牙音质优化:开启AAC高清音频支持
单片机·ffmpeg·音视频·aac·ac6966b蓝牙音响方案·杰理智能音箱开发·杰理ac6965e蓝牙音频开发
誰能久伴不乏4 天前
从底层看透音视频架构:FFmpeg 实时视频推流深度解析
linux·c++·tcp/ip·ffmpeg
阳光开朗男孩5 天前
FFmpeg从入门到精通-1.2.ffmpeg编码支持与定制
ffmpeg