diff options
Diffstat (limited to '')
| -rw-r--r-- | src/video_core/command_classes/codecs/codec.cpp | 154 | ||||
| -rw-r--r-- | src/video_core/command_classes/codecs/codec.h | 8 |
2 files changed, 92 insertions, 70 deletions
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp index f798a0053..e4ee63e31 100644 --- a/src/video_core/command_classes/codecs/codec.cpp +++ b/src/video_core/command_classes/codecs/codec.cpp | |||
| @@ -16,44 +16,17 @@ extern "C" { | |||
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | namespace Tegra { | 18 | namespace Tegra { |
| 19 | #if defined(LIBVA_FOUND) | ||
| 20 | // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license | ||
| 21 | namespace { | 19 | namespace { |
| 22 | constexpr std::array<const char*, 2> VAAPI_DRIVERS = { | 20 | AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) { |
| 23 | "i915", | ||
| 24 | "amdgpu", | ||
| 25 | }; | ||
| 26 | |||
| 27 | AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) { | ||
| 28 | for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { | 21 | for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { |
| 29 | if (*p == AV_PIX_FMT_VAAPI) { | 22 | if (*p == av_codec_ctx->pix_fmt) { |
| 30 | return AV_PIX_FMT_VAAPI; | 23 | return av_codec_ctx->pix_fmt; |
| 31 | } | 24 | } |
| 32 | } | 25 | } |
| 33 | LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); | 26 | LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); |
| 34 | return *pix_fmts; | 27 | return AV_PIX_FMT_NONE; |
| 35 | } | ||
| 36 | |||
| 37 | bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) { | ||
| 38 | AVDictionary* hwdevice_options = nullptr; | ||
| 39 | av_dict_set(&hwdevice_options, "connection_type", "drm", 0); | ||
| 40 | for (const auto& driver : VAAPI_DRIVERS) { | ||
| 41 | av_dict_set(&hwdevice_options, "kernel_driver", driver, 0); | ||
| 42 | const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI, | ||
| 43 | nullptr, hwdevice_options, 0); | ||
| 44 | if (hwdevice_error >= 0) { | ||
| 45 | LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver); | ||
| 46 | av_dict_free(&hwdevice_options); | ||
| 47 | return true; | ||
| 48 | } | ||
| 49 | LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error); | ||
| 50 | } | ||
| 51 | LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers"); | ||
| 52 | av_dict_free(&hwdevice_options); | ||
| 53 | return false; | ||
| 54 | } | 28 | } |
| 55 | } // namespace | 29 | } // namespace |
| 56 | #endif | ||
| 57 | 30 | ||
| 58 | void AVFrameDeleter(AVFrame* ptr) { | 31 | void AVFrameDeleter(AVFrame* ptr) { |
| 59 | av_frame_free(&ptr); | 32 | av_frame_free(&ptr); |
| @@ -69,26 +42,75 @@ Codec::~Codec() { | |||
| 69 | } | 42 | } |
| 70 | // Free libav memory | 43 | // Free libav memory |
| 71 | avcodec_send_packet(av_codec_ctx, nullptr); | 44 | avcodec_send_packet(av_codec_ctx, nullptr); |
| 72 | AVFrame* av_frame = av_frame_alloc(); | 45 | AVFramePtr av_frame{av_frame_alloc(), AVFrameDeleter}; |
| 73 | avcodec_receive_frame(av_codec_ctx, av_frame); | 46 | avcodec_receive_frame(av_codec_ctx, av_frame.get()); |
| 74 | avcodec_flush_buffers(av_codec_ctx); | 47 | avcodec_flush_buffers(av_codec_ctx); |
| 75 | av_frame_free(&av_frame); | ||
| 76 | avcodec_close(av_codec_ctx); | 48 | avcodec_close(av_codec_ctx); |
| 77 | av_buffer_unref(&av_hw_device); | 49 | av_buffer_unref(&av_gpu_decoder); |
| 78 | } | 50 | } |
| 79 | 51 | ||
| 80 | void Codec::InitializeHwdec() { | 52 | bool Codec::CreateGpuAvDevice() { |
| 81 | // Prioritize integrated GPU to mitigate bandwidth bottlenecks | ||
| 82 | #if defined(LIBVA_FOUND) | 53 | #if defined(LIBVA_FOUND) |
| 83 | if (CreateVaapiHwdevice(&av_hw_device)) { | 54 | static constexpr std::array<const char*, 3> VAAPI_DRIVERS = { |
| 84 | const auto hw_device_ctx = av_buffer_ref(av_hw_device); | 55 | "i915", |
| 85 | ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); | 56 | "iHD", |
| 86 | av_codec_ctx->hw_device_ctx = hw_device_ctx; | 57 | "amdgpu", |
| 87 | av_codec_ctx->get_format = GetHwFormat; | 58 | }; |
| 88 | return; | 59 | AVDictionary* hwdevice_options = nullptr; |
| 60 | av_dict_set(&hwdevice_options, "connection_type", "drm", 0); | ||
| 61 | for (const auto& driver : VAAPI_DRIVERS) { | ||
| 62 | av_dict_set(&hwdevice_options, "kernel_driver", driver, 0); | ||
| 63 | const int hwdevice_error = av_hwdevice_ctx_create(&av_gpu_decoder, AV_HWDEVICE_TYPE_VAAPI, | ||
| 64 | nullptr, hwdevice_options, 0); | ||
| 65 | if (hwdevice_error >= 0) { | ||
| 66 | LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver); | ||
| 67 | av_dict_free(&hwdevice_options); | ||
| 68 | av_codec_ctx->pix_fmt = AV_PIX_FMT_VAAPI; | ||
| 69 | return true; | ||
| 70 | } | ||
| 71 | LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error); | ||
| 89 | } | 72 | } |
| 73 | LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers"); | ||
| 74 | av_dict_free(&hwdevice_options); | ||
| 75 | #endif | ||
| 76 | static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX; | ||
| 77 | static constexpr std::array GPU_DECODER_TYPES{ | ||
| 78 | AV_HWDEVICE_TYPE_CUDA, | ||
| 79 | #ifdef _WIN32 | ||
| 80 | AV_HWDEVICE_TYPE_D3D11VA, | ||
| 81 | #else | ||
| 82 | AV_HWDEVICE_TYPE_VDPAU, | ||
| 90 | #endif | 83 | #endif |
| 91 | // TODO more GPU accelerated decoders | 84 | }; |
| 85 | for (const auto& type : GPU_DECODER_TYPES) { | ||
| 86 | av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0); | ||
| 87 | for (int i = 0;; i++) { | ||
| 88 | const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i); | ||
| 89 | if (!config) { | ||
| 90 | LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.", | ||
| 91 | av_codec->name, av_hwdevice_get_type_name(type)); | ||
| 92 | break; | ||
| 93 | } | ||
| 94 | if (config->methods & HW_CONFIG_METHOD && config->device_type == type) { | ||
| 95 | av_codec_ctx->pix_fmt = config->pix_fmt; | ||
| 96 | LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); | ||
| 97 | return true; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | |||
| 104 | void Codec::InitializeGpuDecoder() { | ||
| 105 | if (!CreateGpuAvDevice()) { | ||
| 106 | av_buffer_unref(&av_gpu_decoder); | ||
| 107 | return; | ||
| 108 | } | ||
| 109 | auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder); | ||
| 110 | ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); | ||
| 111 | av_codec_ctx->hw_device_ctx = hw_device_ctx; | ||
| 112 | av_codec_ctx->get_format = GetGpuFormat; | ||
| 113 | using_gpu_decode = true; | ||
| 92 | } | 114 | } |
| 93 | 115 | ||
| 94 | void Codec::Initialize() { | 116 | void Codec::Initialize() { |
| @@ -107,7 +129,8 @@ void Codec::Initialize() { | |||
| 107 | av_codec = avcodec_find_decoder(codec); | 129 | av_codec = avcodec_find_decoder(codec); |
| 108 | av_codec_ctx = avcodec_alloc_context3(av_codec); | 130 | av_codec_ctx = avcodec_alloc_context3(av_codec); |
| 109 | av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); | 131 | av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); |
| 110 | InitializeHwdec(); | 132 | |
| 133 | InitializeGpuDecoder(); | ||
| 111 | if (!av_codec_ctx->hw_device_ctx) { | 134 | if (!av_codec_ctx->hw_device_ctx) { |
| 112 | LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); | 135 | LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); |
| 113 | } | 136 | } |
| @@ -115,7 +138,7 @@ void Codec::Initialize() { | |||
| 115 | if (av_error < 0) { | 138 | if (av_error < 0) { |
| 116 | LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); | 139 | LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); |
| 117 | avcodec_close(av_codec_ctx); | 140 | avcodec_close(av_codec_ctx); |
| 118 | av_buffer_unref(&av_hw_device); | 141 | av_buffer_unref(&av_gpu_decoder); |
| 119 | return; | 142 | return; |
| 120 | } | 143 | } |
| 121 | initialized = true; | 144 | initialized = true; |
| @@ -153,38 +176,33 @@ void Codec::Decode() { | |||
| 153 | if (vp9_hidden_frame) { | 176 | if (vp9_hidden_frame) { |
| 154 | return; | 177 | return; |
| 155 | } | 178 | } |
| 156 | AVFrame* hw_frame = av_frame_alloc(); | 179 | AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter}; |
| 157 | AVFrame* sw_frame = hw_frame; | 180 | AVFramePtr final_frame{nullptr, AVFrameDeleter}; |
| 158 | ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed"); | 181 | ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed"); |
| 159 | if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) { | 182 | if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) { |
| 160 | LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); | 183 | LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); |
| 161 | av_frame_free(&hw_frame); | ||
| 162 | return; | 184 | return; |
| 163 | } | 185 | } |
| 164 | if (!hw_frame->width || !hw_frame->height) { | 186 | if (initial_frame->width == 0 || initial_frame->height == 0) { |
| 165 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); | 187 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); |
| 166 | av_frame_free(&hw_frame); | ||
| 167 | return; | 188 | return; |
| 168 | } | 189 | } |
| 169 | #if defined(LIBVA_FOUND) | 190 | if (using_gpu_decode) { |
| 170 | // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license | 191 | final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; |
| 171 | if (hw_frame->format == AV_PIX_FMT_VAAPI) { | 192 | ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); |
| 172 | sw_frame = av_frame_alloc(); | ||
| 173 | ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed"); | ||
| 174 | // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp | 193 | // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp |
| 175 | // because Intel drivers crash unless using AV_PIX_FMT_NV12 | 194 | // because Intel drivers crash unless using AV_PIX_FMT_NV12 |
| 176 | sw_frame->format = AV_PIX_FMT_NV12; | 195 | final_frame->format = AV_PIX_FMT_NV12; |
| 177 | const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0); | 196 | const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0); |
| 178 | ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret); | 197 | ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret); |
| 179 | av_frame_free(&hw_frame); | 198 | } else { |
| 180 | } | 199 | final_frame = std::move(initial_frame); |
| 181 | #endif | 200 | } |
| 182 | if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) { | 201 | if (final_frame->format != AV_PIX_FMT_YUV420P && final_frame->format != AV_PIX_FMT_NV12) { |
| 183 | UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format); | 202 | UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); |
| 184 | av_frame_free(&sw_frame); | ||
| 185 | return; | 203 | return; |
| 186 | } | 204 | } |
| 187 | av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); | 205 | av_frames.push(std::move(final_frame)); |
| 188 | if (av_frames.size() > 10) { | 206 | if (av_frames.size() > 10) { |
| 189 | LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); | 207 | LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); |
| 190 | av_frames.pop(); | 208 | av_frames.pop(); |
diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h index 71936203f..abfe59221 100644 --- a/src/video_core/command_classes/codecs/codec.h +++ b/src/video_core/command_classes/codecs/codec.h | |||
| @@ -50,18 +50,22 @@ public: | |||
| 50 | 50 | ||
| 51 | /// Returns the value of current_codec | 51 | /// Returns the value of current_codec |
| 52 | [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const; | 52 | [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const; |
| 53 | |||
| 53 | /// Return name of the current codec | 54 | /// Return name of the current codec |
| 54 | [[nodiscard]] std::string_view GetCurrentCodecName() const; | 55 | [[nodiscard]] std::string_view GetCurrentCodecName() const; |
| 55 | 56 | ||
| 56 | private: | 57 | private: |
| 57 | void InitializeHwdec(); | 58 | void InitializeGpuDecoder(); |
| 59 | |||
| 60 | bool CreateGpuAvDevice(); | ||
| 58 | 61 | ||
| 59 | bool initialized{}; | 62 | bool initialized{}; |
| 63 | bool using_gpu_decode{}; | ||
| 60 | NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; | 64 | NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; |
| 61 | 65 | ||
| 62 | AVCodec* av_codec{nullptr}; | 66 | AVCodec* av_codec{nullptr}; |
| 63 | AVBufferRef* av_hw_device{nullptr}; | ||
| 64 | AVCodecContext* av_codec_ctx{nullptr}; | 67 | AVCodecContext* av_codec_ctx{nullptr}; |
| 68 | AVBufferRef* av_gpu_decoder{nullptr}; | ||
| 65 | 69 | ||
| 66 | GPU& gpu; | 70 | GPU& gpu; |
| 67 | const NvdecCommon::NvdecRegisters& state; | 71 | const NvdecCommon::NvdecRegisters& state; |