如何将像素格式为 AV_PIX_FMT_CUDA 的 FFmpeg AVFrame 转换为像素格式为 AV_PIX_FMT_RGB 的新 AVFrame
Posted
技术标签:
【中文标题】如何将像素格式为 AV_PIX_FMT_CUDA 的 FFmpeg AVFrame 转换为像素格式为 AV_PIX_FMT_RGB 的新 AVFrame【英文标题】:How can I convert an FFmpeg AVFrame with pixel format AV_PIX_FMT_CUDA to a new AVFrame with pixel format AV_PIX_FMT_RGB 【发布时间】:2018-04-13 10:38:19 【问题描述】:我有一个简单的 C++ 应用程序,它使用 FFmpeg 3.2 接收 H264 RTP 流。为了节省 CPU,我使用编解码器 h264_cuvid 进行解码部分。我的 FFmpeg 3.2 是在启用硬件加速的情况下编译的。事实上,如果我执行命令:
ffmpeg -hwaccels
我明白了
cuvid
这意味着我的 FFmpeg 设置可以与我的 NVIDIA 卡“对话”。
函数avcodec_decode_video2
提供给我的帧具有像素格式AV_PIX_FMT_CUDA
。我需要使用AV_PIX_FMT_RGB
将这些帧转换为新帧。不幸的是,我无法使用众所周知的函数sws_getContext
和sws_scale
进行转换,因为不支持像素格式AV_PIX_FMT_CUDA
。如果我尝试使用 swscale,我会收到错误消息:
“不支持cuda作为输入像素格式”
你知道如何将 FFmpeg AVFrame
从 AV_PIX_FMT_CUDA
转换为 AV_PIX_FMT_RGB
吗?
(非常感谢您的代码)
【问题讨论】:
如果你只需要用 cuvid 解码,我想没有必要处理 AV_PIX_FMT_CUDA。虽然没有这方面的例子,但qsvdec.c
和hw_decode.c
官方例子可能是一个很好的参考。并且解码器应该在这里返回 nv12 格式到主机内存。
【参考方案1】:
您必须使用vf_scale_npp
来执行此操作。您可以根据需要使用nppscale_deinterleave
或nppscale_resize
。
两者都有相同的输入参数,AVFilterContext 应该用nppscale_init
初始化,NPPScaleStageContext 采用你的输入/输出像素格式和两个AVFrames,当然是你的输入和输出帧。
有关更多信息,您可以查看 npplib\nppscale 定义,该定义将从 ffmpeg 3.1 开始执行 CUDA 加速格式转换和缩放。
无论如何,我建议为此直接使用NVIDIA Video Codec SDK。
【讨论】:
嗨哈米德。非常感谢您的回答。我要研究那个 vf_scale_npp。函数 static int nppscale_deinterleave (AVFilterContext *ctx, NPPScaleStageContext * stage, AVFrame *out, AVFrame *in); static int nppscale_resize (AVFilterContext *ctx, NPPScaleStageContext * stage, AVFrame *out, AVFrame *in);看起来真的很有希望。我会尽快给我反馈。再次感谢 嗨哈米德。我试过了,但没有成功。我从函数“nppscale_init”获得成功,但从“nppscale_deinterleave”获得失败。从这最后我得到错误代码:[in @ 0x7fff69b97820] NPP deinterleave 错误:-8 显然问题出在我的“in”AVFrame 中。但是什么?你知道它是什么意思吗?您还建议直接使用 NVIDIA Video Codec SDK 进行此类转换。我愿意使用它。 FFmpeg 在这里缺少文档和好的示例。您是否有一段代码,可能是一个函数,它接收从 cuvid 获得的 AVFrame 并在 AV_PIX_FMT_RGB 中返回一个新的? 好的,没问题。无论如何,我已经进行了更深入的调查。我看到“nppscale_deinterleave”调用了“nppiYCbCr420_8u_P2P3R”函数,它返回错误代码-8。该错误代码是 NPP_NULL_POINTER_ERROR = -8 我已经检查过我没有将任何 NULL 传递给“nppscale_deinterleave”,但错误仍然存在。我怀疑我必须在我的软件中“带来”更多该 vf_scale_npp 的代码。【参考方案2】:我不是 ffmpeg 专家,但我遇到了类似的问题并设法解决了它。我从 cuvid(mjpeg_cuvid 解码器)得到AV_PIX_FMT_NV12
,并想要AV_PIX_FMT_CUDA
进行cuda 处理。
我发现在解码帧之前设置像素格式是可行的。
pCodecCtx->pix_fmt = AV_PIX_FMT_CUDA; // change format here
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// do something with pFrame->data[0] (Y) and pFrame->data[1] (UV)
您可以使用 pix_fmts 检查您的解码器支持哪些像素格式:
AVCodec *pCodec = avcodec_find_decoder_by_name("mjpeg_cuvid");
for (int i = 0; pCodec->pix_fmts[i] != AV_PIX_FMT_NONE; i++)
std::cout << pCodec->pix_fmts[i] << std::endl;
我确信有更好的方法可以做到这一点,但我随后使用 this 列表将整数像素格式 ID 映射到人类可读的像素格式。
如果这不起作用,您可以执行 cudaMemcpy 将像素从设备传输到主机:
cudaMemcpy(pLocalBuf pFrame->data[0], size, cudaMemcpyDeviceToHost);
从 YUV 到 RGB/RGBA 的转换可以通过多种方式完成。 This example 使用 libavdevice API。
【讨论】:
【参考方案3】:这是我对最新FFMPeg 4.1版本的硬件解码的理解。以下是我研究源码后得出的结论。
首先我建议从 hw_decode 示例中启发自己:
https://github.com/FFmpeg/FFmpeg/blob/release/4.1/doc/examples/hw_decode.c
使用新 API,当您使用 avcodec_send_packet() 向编码器发送数据包时,然后使用 avcodec_receive_frame() 检索解码后的帧。
AVFrame
有两种不同的类型:软件 一种,存储在“CPU”内存(又称 RAM)中,以及 硬件,一种是存储在显卡内存中。
从硬件获取 AVFrame
要检索硬件帧并将其转换为可读的、可转换的(使用 swscaler)AVFrame
,需要使用av_hwframe_transfer_data() 从图形卡中检索数据。然后看检索帧的像素格式,使用nVidia解码时通常是NV12格式。
// According to the API, if the format of the AVFrame is set before calling
// av_hwframe_transfer_data(), the graphic card will try to automatically convert
// to the desired format. (with some limitation, see below)
m_swFrame->format = AV_PIX_FMT_NV12;
// retrieve data from GPU to CPU
err = av_hwframe_transfer_data(
m_swFrame, // The frame that will contain the usable data.
m_decodedFrame, // Frame returned by avcodec_receive_frame()
0);
const char* gpu_pixfmt = av_get_pix_fmt_name((AVPixelFormat)m_decodedFrame->format);
const char* cpu_pixfmt = av_get_pix_fmt_name((AVPixelFormat)m_swFrame->format);
列出支持的“软件”像素格式
如果您要选择像素格式,请注意此处并非所有 AVPixelFormat 都受支持。 AVHWFramesConstraints是你的朋友:
AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
int err = av_hwdevice_ctx_create(&hwDeviceCtx, type, nullptr, nullptr, 0);
if (err < 0)
// Err
AVHWFramesConstraints* hw_frames_const = av_hwdevice_get_hwframe_constraints(hwDeviceCtx, nullptr);
if (hw_frames_const == nullptr)
// Err
// Check if we can convert the pixel format to a readable format.
AVPixelFormat found = AV_PIX_FMT_NONE;
for (AVPixelFormat* p = hw_frames_const->valid_sw_formats;
*p != AV_PIX_FMT_NONE; p++)
// Check if we can convert to the desired format.
if (sws_isSupportedInput(*p))
// Ok! This format can be used with swscale!
found = *p;
break;
// Don't forget to free the constraint object.
av_hwframe_constraints_free(&hw_frames_const);
// Attach your hw device to your codec context if you want to use hw decoding.
// Check AVCodecContext.hw_device_ctx!
最后,一个更快的方法可能是av_hwframe_transfer_get_formats() 函数,但您至少需要解码一帧。
希望这会有所帮助!
【讨论】:
以上是关于如何将像素格式为 AV_PIX_FMT_CUDA 的 FFmpeg AVFrame 转换为像素格式为 AV_PIX_FMT_RGB 的新 AVFrame的主要内容,如果未能解决你的问题,请参考以下文章
如何将 numpy 数组转换为标准 TensorFlow 格式?