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:
		| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user