我有一个简单的C ++应用程序,它使用FFmpeg 3.2来接收H264 RTP流。为了节省CPU,我正在使用编解码器h264_cuvid进行解码部分。我的FFmpeg 3.2编译时启用了hw加速。事实上,如果我执行命令:
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
吗?
(非常感谢代码片段)
答案 0 :(得分:1)
您必须使用vf_scale_npp
执行此操作。您可以根据需要使用nppscale_deinterleave
或nppscale_resize
。
两者都有相同的输入参数,AVFilterContext应使用nppscale_init
进行初始化,NPPScaleStageContext采用输入/输出像素格式,以及两个AVFrame当然是你的输入和输出框架。
有关详细信息,您可以看到npplib\nppscale定义,它将执行自ffmpeg 3.1以来的CUDA加速格式转换和缩放。
无论如何,我建议直接使用NVIDIA Video Codec SDK来实现此目的。
答案 1 :(得分:1)
我不是一名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来完成它。
答案 2 :(得分:1)
这就是我对最新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)中,另一种是硬件,存储在图形卡内存中。
要检索硬件框架并将其放入可读的可转换(带有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()函数,但是您需要解码至少一帧。
希望这会有所帮助!