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
帧进行转色,原因如下:
硬件解码帧的特点
-
m_ptAVframe
是硬件帧:- 数据存储在GPU显存中
- 格式是硬件特定的(如DXVA2、CUDA、VAAPI等)
- 不能直接被CPU访问和处理
-
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
,因为:
- 内存空间不同:硬件帧在GPU显存,软件帧在系统内存
- 访问权限:CPU无法直接访问GPU显存中的数据
- 数据格式:硬件帧可能是特定于硬件的编码格式,需要转换为标准像素格式
- API限制:libyuv等图像处理库只能处理系统内存中的数据
av_hwframe_transfer_data
的作用就是建立这个"桥梁",将数据从GPU传输到CPU可访问的内存中。