nvdec: Implement GPU accelerated decoding for all platforms

Supplements the VAAPI intel gpu decoder by implementing the D3D11VA decoder for Windows, and CUVID/VDPAU for Nvidia and AMD on drivers linux respectively.
This commit is contained in:
ameerj 2021-08-07 15:12:15 -04:00
parent 756d76d971
commit 659039ca6d
2 changed files with 91 additions and 69 deletions

View file

@ -16,44 +16,17 @@ extern "C" {
} }
namespace Tegra { namespace Tegra {
#if defined(LIBVA_FOUND)
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
namespace { namespace {
constexpr std::array<const char*, 2> VAAPI_DRIVERS = { AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
"i915",
"amdgpu",
};
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
if (*p == AV_PIX_FMT_VAAPI) { if (*p == av_codec_ctx->pix_fmt) {
return AV_PIX_FMT_VAAPI; return av_codec_ctx->pix_fmt;
} }
} }
LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
return *pix_fmts; return AV_PIX_FMT_NONE;
}
bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
AVDictionary* hwdevice_options = nullptr;
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
for (const auto& driver : VAAPI_DRIVERS) {
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
nullptr, hwdevice_options, 0);
if (hwdevice_error >= 0) {
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
av_dict_free(&hwdevice_options);
return true;
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
av_dict_free(&hwdevice_options);
return false;
} }
} // namespace } // namespace
#endif
void AVFrameDeleter(AVFrame* ptr) { void AVFrameDeleter(AVFrame* ptr) {
av_frame_free(&ptr); av_frame_free(&ptr);
@ -69,26 +42,75 @@ Codec::~Codec() {
} }
// Free libav memory // Free libav memory
avcodec_send_packet(av_codec_ctx, nullptr); avcodec_send_packet(av_codec_ctx, nullptr);
AVFrame* av_frame = av_frame_alloc(); AVFramePtr av_frame{av_frame_alloc(), AVFrameDeleter};
avcodec_receive_frame(av_codec_ctx, av_frame); avcodec_receive_frame(av_codec_ctx, av_frame.get());
avcodec_flush_buffers(av_codec_ctx); avcodec_flush_buffers(av_codec_ctx);
av_frame_free(&av_frame);
avcodec_close(av_codec_ctx); avcodec_close(av_codec_ctx);
av_buffer_unref(&av_hw_device); av_buffer_unref(&av_gpu_decoder);
} }
void Codec::InitializeHwdec() { bool Codec::CreateGpuAvDevice() {
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
#if defined(LIBVA_FOUND) #if defined(LIBVA_FOUND)
if (CreateVaapiHwdevice(&av_hw_device)) { static constexpr std::array<const char*, 3> VAAPI_DRIVERS = {
const auto hw_device_ctx = av_buffer_ref(av_hw_device); "i915",
ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); "iHD",
av_codec_ctx->hw_device_ctx = hw_device_ctx; "amdgpu",
av_codec_ctx->get_format = GetHwFormat; };
AVDictionary* hwdevice_options = nullptr;
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
for (const auto& driver : VAAPI_DRIVERS) {
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
const int hwdevice_error = av_hwdevice_ctx_create(&av_gpu_decoder, AV_HWDEVICE_TYPE_VAAPI,
nullptr, hwdevice_options, 0);
if (hwdevice_error >= 0) {
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
av_dict_free(&hwdevice_options);
av_codec_ctx->pix_fmt = AV_PIX_FMT_VAAPI;
return true;
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
av_dict_free(&hwdevice_options);
#endif
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
static constexpr std::array GPU_DECODER_TYPES{
AV_HWDEVICE_TYPE_CUDA,
#ifdef _WIN32
AV_HWDEVICE_TYPE_D3D11VA,
#else
AV_HWDEVICE_TYPE_VDPAU,
#endif
};
for (const auto& type : GPU_DECODER_TYPES) {
av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
for (int i = 0;; i++) {
const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
if (!config) {
LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
av_codec->name, av_hwdevice_get_type_name(type));
break;
}
if (config->methods & HW_CONFIG_METHOD && config->device_type == type) {
av_codec_ctx->pix_fmt = config->pix_fmt;
LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
return true;
}
}
}
return false;
}
void Codec::InitializeGpuDecoder() {
if (!CreateGpuAvDevice()) {
av_buffer_unref(&av_gpu_decoder);
return; return;
} }
#endif auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
// TODO more GPU accelerated decoders ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
av_codec_ctx->hw_device_ctx = hw_device_ctx;
av_codec_ctx->get_format = GetGpuFormat;
using_gpu_decode = true;
} }
void Codec::Initialize() { void Codec::Initialize() {
@ -107,7 +129,8 @@ void Codec::Initialize() {
av_codec = avcodec_find_decoder(codec); av_codec = avcodec_find_decoder(codec);
av_codec_ctx = avcodec_alloc_context3(av_codec); av_codec_ctx = avcodec_alloc_context3(av_codec);
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
InitializeHwdec();
InitializeGpuDecoder();
if (!av_codec_ctx->hw_device_ctx) { if (!av_codec_ctx->hw_device_ctx) {
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
} }
@ -115,7 +138,7 @@ void Codec::Initialize() {
if (av_error < 0) { if (av_error < 0) {
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
avcodec_close(av_codec_ctx); avcodec_close(av_codec_ctx);
av_buffer_unref(&av_hw_device); av_buffer_unref(&av_gpu_decoder);
return; return;
} }
initialized = true; initialized = true;
@ -153,38 +176,33 @@ void Codec::Decode() {
if (vp9_hidden_frame) { if (vp9_hidden_frame) {
return; return;
} }
AVFrame* hw_frame = av_frame_alloc(); AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
AVFrame* sw_frame = hw_frame; AVFramePtr final_frame{nullptr, AVFrameDeleter};
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed"); ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) { if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
av_frame_free(&hw_frame);
return; return;
} }
if (!hw_frame->width || !hw_frame->height) { if (initial_frame->width == 0 || initial_frame->height == 0) {
LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
av_frame_free(&hw_frame);
return; return;
} }
#if defined(LIBVA_FOUND) if (using_gpu_decode) {
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
if (hw_frame->format == AV_PIX_FMT_VAAPI) { ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
sw_frame = av_frame_alloc();
ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
// because Intel drivers crash unless using AV_PIX_FMT_NV12 // because Intel drivers crash unless using AV_PIX_FMT_NV12
sw_frame->format = AV_PIX_FMT_NV12; final_frame->format = AV_PIX_FMT_NV12;
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0); const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret); ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
av_frame_free(&hw_frame); } else {
final_frame = std::move(initial_frame);
} }
#endif if (final_frame->format != AV_PIX_FMT_YUV420P && final_frame->format != AV_PIX_FMT_NV12) {
if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) { UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
av_frame_free(&sw_frame);
return; return;
} }
av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); av_frames.push(std::move(final_frame));
if (av_frames.size() > 10) { if (av_frames.size() > 10) {
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
av_frames.pop(); av_frames.pop();

View file

@ -50,18 +50,22 @@ public:
/// Returns the value of current_codec /// Returns the value of current_codec
[[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const; [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
/// Return name of the current codec /// Return name of the current codec
[[nodiscard]] std::string_view GetCurrentCodecName() const; [[nodiscard]] std::string_view GetCurrentCodecName() const;
private: private:
void InitializeHwdec(); void InitializeGpuDecoder();
bool CreateGpuAvDevice();
bool initialized{}; bool initialized{};
bool using_gpu_decode{};
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
AVCodec* av_codec{nullptr}; AVCodec* av_codec{nullptr};
AVBufferRef* av_hw_device{nullptr};
AVCodecContext* av_codec_ctx{nullptr}; AVCodecContext* av_codec_ctx{nullptr};
AVBufferRef* av_gpu_decoder{nullptr};
GPU& gpu; GPU& gpu;
const NvdecCommon::NvdecRegisters& state; const NvdecCommon::NvdecRegisters& state;