From 278d5c5a342aeeea7f48ac6872b38a17feaae030 Mon Sep 17 00:00:00 2001 From: liberodark Date: Sat, 6 Dec 2025 18:04:43 +0100 Subject: [PATCH] [FFmpeg]: Remove internal FFCodec hack and use public API --- src/video_core/host1x/codecs/decoder.cpp | 4 + src/video_core/host1x/codecs/decoder.h | 0 src/video_core/host1x/ffmpeg/ffmpeg.cpp | 329 +++++------------------ src/video_core/host1x/ffmpeg/ffmpeg.h | 14 +- src/video_core/host1x/nvdec.cpp | 4 - src/video_core/host1x/nvdec.h | 5 - 6 files changed, 74 insertions(+), 282 deletions(-) mode change 100755 => 100644 src/video_core/host1x/codecs/decoder.cpp mode change 100755 => 100644 src/video_core/host1x/codecs/decoder.h diff --git a/src/video_core/host1x/codecs/decoder.cpp b/src/video_core/host1x/codecs/decoder.cpp old mode 100755 new mode 100644 index fb346ea8c..5b5e6aff0 --- a/src/video_core/host1x/codecs/decoder.cpp +++ b/src/video_core/host1x/codecs/decoder.cpp @@ -35,6 +35,10 @@ void Decoder::Decode() { // Receive output frames from decoder. auto frame = decode_api.ReceiveFrame(); + if (!frame) { + return; + } + if (IsInterlaced()) { auto [luma_top, luma_bottom, chroma_top, chroma_bottom] = GetInterlacedOffsets(); auto frame_copy = frame; diff --git a/src/video_core/host1x/codecs/decoder.h b/src/video_core/host1x/codecs/decoder.h old mode 100755 new mode 100644 diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp index 6655399c6..9e5248e25 100644 --- a/src/video_core/host1x/ffmpeg/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp @@ -23,28 +23,45 @@ namespace { constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12; constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P; constexpr std::array PreferredGpuDecoders = { +#if defined(_WIN32) AV_HWDEVICE_TYPE_CUDA, -#ifdef _WIN32 AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2, +#elif defined(__FreeBSD__) + AV_HWDEVICE_TYPE_VDPAU, #elif defined(__unix__) + AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_VDPAU, #endif - // last resort for Linux Flatpak (w/ NVIDIA) AV_HWDEVICE_TYPE_VULKAN, }; AVPixelFormat GetGpuFormat(AVCodecContext* codec_context, const AVPixelFormat* pix_fmts) { + const auto desc = av_pix_fmt_desc_get(codec_context->pix_fmt); + if (desc && !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { + for (int i = 0;; i++) { + const AVCodecHWConfig* config = avcodec_get_hw_config(codec_context->codec, i); + if (!config) { + break; + } + + for (const auto type : PreferredGpuDecoders) { + if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type) { + codec_context->pix_fmt = config->pix_fmt; + } + } + } + } + for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { if (*p == codec_context->pix_fmt) { return codec_context->pix_fmt; } } - LOG_INFO(HW_GPU, "Could not find compatible GPU AV format, falling back to CPU"); + LOG_INFO(HW_GPU, "Could not find supported GPU pixel format, falling back to CPU decoder"); av_buffer_unref(&codec_context->hw_device_ctx); - codec_context->pix_fmt = PreferredCpuFormat; return codec_context->pix_fmt; } @@ -55,7 +72,7 @@ std::string AVError(int errnum) { return errbuf; } -} // namespace +} Packet::Packet(std::span data) { m_packet = av_packet_alloc(); @@ -78,15 +95,15 @@ Frame::~Frame() { Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) { const AVCodecID av_codec = [&] { switch (codec) { - case Tegra::Host1x::NvdecCommon::VideoCodec::H264: - return AV_CODEC_ID_H264; - case Tegra::Host1x::NvdecCommon::VideoCodec::VP8: - return AV_CODEC_ID_VP8; - case Tegra::Host1x::NvdecCommon::VideoCodec::VP9: - return AV_CODEC_ID_VP9; - default: - UNIMPLEMENTED_MSG("Unknown codec {}", codec); - return AV_CODEC_ID_NONE; + case Tegra::Host1x::NvdecCommon::VideoCodec::H264: + return AV_CODEC_ID_H264; + case Tegra::Host1x::NvdecCommon::VideoCodec::VP8: + return AV_CODEC_ID_VP8; + case Tegra::Host1x::NvdecCommon::VideoCodec::VP9: + return AV_CODEC_ID_VP9; + default: + UNIMPLEMENTED_MSG("Unknown codec {}", codec); + return AV_CODEC_ID_NONE; } }(); @@ -97,12 +114,11 @@ bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceTyp for (int i = 0;; i++) { const AVCodecHWConfig* config = avcodec_get_hw_config(m_codec, i); if (!config) { - LOG_DEBUG(HW_GPU, "{} decoder does not support device type {}", m_codec->name, - av_hwdevice_get_type_name(type)); + LOG_DEBUG(HW_GPU, "{} decoder does not support device type {}", m_codec->name, av_hwdevice_get_type_name(type)); break; } - if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 && - config->device_type == type) { + + if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type) { LOG_INFO(HW_GPU, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); *out_pix_fmt = config->pix_fmt; return true; @@ -130,8 +146,7 @@ HardwareContext::~HardwareContext() { av_buffer_unref(&m_gpu_decoder); } -bool HardwareContext::InitializeForDecoder(DecoderContext& decoder_context, - const Decoder& decoder) { +bool HardwareContext::InitializeForDecoder(DecoderContext& decoder_context, const Decoder& decoder) { const auto supported_types = GetSupportedDeviceTypes(); for (const auto type : PreferredGpuDecoders) { AVPixelFormat hw_pix_fmt; @@ -151,17 +166,14 @@ bool HardwareContext::InitializeForDecoder(DecoderContext& decoder_context, } } - LOG_INFO(HW_GPU, "Hardware decoding is disabled due to implementation issues, using CPU."); return false; } bool HardwareContext::InitializeWithType(AVHWDeviceType type) { av_buffer_unref(&m_gpu_decoder); - if (const int ret = av_hwdevice_ctx_create(&m_gpu_decoder, type, nullptr, nullptr, 0); - ret < 0) { - LOG_DEBUG(HW_GPU, "av_hwdevice_ctx_create({}) failed: {}", av_hwdevice_get_type_name(type), - AVError(ret)); + if (const int ret = av_hwdevice_ctx_create(&m_gpu_decoder, type, nullptr, nullptr, 0); ret < 0) { + LOG_DEBUG(HW_GPU, "av_hwdevice_ctx_create({}) failed: {}", av_hwdevice_get_type_name(type), AVError(ret)); return false; } @@ -198,8 +210,7 @@ DecoderContext::~DecoderContext() { avcodec_free_context(&m_codec_context); } -void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context, - AVPixelFormat hw_pix_fmt) { +void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt) { m_codec_context->hw_device_ctx = av_buffer_ref(context.GetBufferRef()); m_codec_context->get_format = GetGpuFormat; m_codec_context->pix_fmt = hw_pix_fmt; @@ -212,192 +223,14 @@ bool DecoderContext::OpenContext(const Decoder& decoder) { } if (!m_codec_context->hw_device_ctx) { - LOG_INFO(HW_GPU, "Using FFmpeg software decoding"); + LOG_INFO(HW_GPU, "Using FFmpeg CPU decoder"); } return true; } -// Nasty but allows linux builds to pass. -// Requires double checks when FFMPEG gets updated. -// Hopefully a future FFMPEG update will all and expose a solution in the public API. -namespace { - -typedef struct FFCodecDefault { - const char* key; - const char* value; -} FFCodecDefault; - -typedef struct FFCodec { - /** - * The public AVCodec. See codec.h for it. - */ - AVCodec p; - - /** - * Internal codec capabilities FF_CODEC_CAP_*. - */ - unsigned caps_internal : 29; - - /** - * This field determines the type of the codec (decoder/encoder) - * and also the exact callback cb implemented by the codec. - * cb_type uses enum FFCodecType values. - */ - unsigned cb_type : 3; - - int priv_data_size; - /** - * @name Frame-level threading support functions - * @{ - */ - /** - * Copy necessary context variables from a previous thread context to the current one. - * If not defined, the next thread will start automatically; otherwise, the codec - * must call ff_thread_finish_setup(). - * - * dst and src will (rarely) point to the same context, in which case memcpy should be skipped. - */ - int (*update_thread_context)(struct AVCodecContext* dst, const struct AVCodecContext* src); - - /** - * Copy variables back to the user-facing context - */ - int (*update_thread_context_for_user)(struct AVCodecContext* dst, - const struct AVCodecContext* src); - /** @} */ - - /** - * Private codec-specific defaults. - */ - const FFCodecDefault* defaults; - - /** - * Initialize codec static data, called from av_codec_iterate(). - * - * This is not intended for time consuming operations as it is - * run for every codec regardless of that codec being used. - */ - void (*init_static_data)(struct FFCodec* codec); - - int (*init)(struct AVCodecContext*); - - union { - /** - * Decode to an AVFrame. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_DECODE. - * - * @param avctx codec context - * @param[out] frame AVFrame for output - * @param[out] got_frame_ptr decoder sets to 0 or 1 to indicate that - * a non-empty frame was returned in frame. - * @param[in] avpkt AVPacket containing the data to be decoded - * @return amount of bytes read from the packet on success, - * negative error code on failure - */ - int (*decode)(struct AVCodecContext* avctx, struct AVFrame* frame, int* got_frame_ptr, - struct AVPacket* avpkt); - /** - * Decode subtitle data to an AVSubtitle. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_DECODE_SUB. - * - * Apart from that this is like the decode callback. - */ - int (*decode_sub)(struct AVCodecContext* avctx, struct AVSubtitle* sub, int* got_frame_ptr, - const struct AVPacket* avpkt); - /** - * Decode API with decoupled packet/frame dataflow. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_RECEIVE_FRAME. - * - * This function is called to get one output frame. It should call - * ff_decode_get_packet() to obtain input data. - */ - int (*receive_frame)(struct AVCodecContext* avctx, struct AVFrame* frame); - /** - * Encode data to an AVPacket. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_ENCODE - * - * @param avctx codec context - * @param[out] avpkt output AVPacket - * @param[in] frame AVFrame containing the input to be encoded - * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a - * non-empty packet was returned in avpkt. - * @return 0 on success, negative error code on failure - */ - int (*encode)(struct AVCodecContext* avctx, struct AVPacket* avpkt, - const struct AVFrame* frame, int* got_packet_ptr); - /** - * Encode subtitles to a raw buffer. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_ENCODE_SUB. - */ - int (*encode_sub)(struct AVCodecContext* avctx, uint8_t* buf, int buf_size, - const struct AVSubtitle* sub); - /** - * Encode API with decoupled frame/packet dataflow. - * cb is in this state if cb_type is FF_CODEC_CB_TYPE_RECEIVE_PACKET. - * - * This function is called to get one output packet. - * It should call ff_encode_get_frame() to obtain input data. - */ - int (*receive_packet)(struct AVCodecContext* avctx, struct AVPacket* avpkt); - } cb; - - int (*close)(struct AVCodecContext*); - - /** - * Flush buffers. - * Will be called when seeking - */ - void (*flush)(struct AVCodecContext*); - - /** - * Decoding only, a comma-separated list of bitstream filters to apply to - * packets before decoding. - */ - const char* bsfs; - - /** - * Array of pointers to hardware configurations supported by the codec, - * or NULL if no hardware supported. The array is terminated by a NULL - * pointer. - * - * The user can only access this field via avcodec_get_hw_config(). - */ - const struct AVCodecHWConfigInternal* const* hw_configs; - - /** - * List of supported codec_tags, terminated by FF_CODEC_TAGS_END. - */ - const uint32_t* codec_tags; -} FFCodec; - -static av_always_inline const FFCodec* ffcodec(const AVCodec* codec) { - return (const FFCodec*)codec; -} - -} // namespace - bool DecoderContext::SendPacket(const Packet& packet) { - m_temp_frame = std::make_shared(); - m_got_frame = 0; - -// Android can randomly crash when calling decode directly, so skip. -// TODO update ffmpeg and hope that fixes it. -#ifndef ANDROID - if (!m_codec_context->hw_device_ctx && m_codec_context->codec_id == AV_CODEC_ID_H264) { - m_decode_order = true; - auto* codec{ffcodec(m_decoder.GetCodec())}; - if (const int ret = codec->cb.decode(m_codec_context, m_temp_frame->GetFrame(), - &m_got_frame, packet.GetPacket()); - ret < 0) { - LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", AVError(ret)); - return false; - } - return true; - } -#endif - - if (const int ret = avcodec_send_packet(m_codec_context, packet.GetPacket()); ret < 0) { + if (const int ret = avcodec_send_packet(m_codec_context, packet.GetPacket()); ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) { LOG_ERROR(HW_GPU, "avcodec_send_packet error: {}", AVError(ret)); return false; } @@ -406,69 +239,31 @@ bool DecoderContext::SendPacket(const Packet& packet) { } std::shared_ptr DecoderContext::ReceiveFrame() { - // Android can randomly crash when calling decode directly, so skip. - // TODO update ffmpeg and hope that fixes it. -#ifndef ANDROID - if (!m_codec_context->hw_device_ctx && m_codec_context->codec_id == AV_CODEC_ID_H264) { - m_decode_order = true; - auto* codec{ffcodec(m_decoder.GetCodec())}; - int ret{0}; - - if (m_got_frame == 0) { - Packet packet{{}}; - auto* pkt = packet.GetPacket(); - pkt->data = nullptr; - pkt->size = 0; - ret = codec->cb.decode(m_codec_context, m_temp_frame->GetFrame(), &m_got_frame, pkt); - m_codec_context->has_b_frames = 0; + auto ReceiveImpl = [&](AVFrame* frame) -> int { + const int ret = avcodec_receive_frame(m_codec_context, frame); + if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) { + LOG_ERROR(HW_GPU, "avcodec_receive_frame error: {}", AVError(ret)); } + return ret; + }; - if (m_got_frame == 0 || ret < 0) { - LOG_ERROR(Service_NVDRV, "Failed to receive a frame! error {}", ret); - return {}; - } - } else -#endif - { - - const auto ReceiveImpl = [&](AVFrame* frame) { - if (const int ret = avcodec_receive_frame(m_codec_context, frame); ret < 0) { - LOG_ERROR(HW_GPU, "avcodec_receive_frame error: {}", AVError(ret)); - return false; - } - - return true; - }; - - if (m_codec_context->hw_device_ctx) { - // If we have a hardware context, make a separate frame here to receive the - // hardware result before sending it to the output. - Frame intermediate_frame; - - if (!ReceiveImpl(intermediate_frame.GetFrame())) { - return {}; - } - - m_temp_frame->SetFormat(PreferredGpuFormat); - if (const int ret = av_hwframe_transfer_data(m_temp_frame->GetFrame(), - intermediate_frame.GetFrame(), 0); - ret < 0) { - LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret)); - return {}; - } - } else { - // Otherwise, decode the frame as normal. - if (!ReceiveImpl(m_temp_frame->GetFrame())) { - return {}; - } - } + std::shared_ptr intermediate_frame = std::make_shared(); + if (ReceiveImpl(intermediate_frame->GetFrame()) < 0) { + return {}; } -#if defined(FF_API_INTERLACED_FRAME) || LIBAVUTIL_VERSION_MAJOR >= 59 - m_temp_frame->GetFrame()->interlaced_frame = - (m_temp_frame->GetFrame()->flags & AV_FRAME_FLAG_INTERLACED) != 0; -#endif - return std::move(m_temp_frame); + m_final_frame = std::make_shared(); + if (m_codec_context->hw_device_ctx) { + m_final_frame->SetFormat(PreferredGpuFormat); + if (const int ret = av_hwframe_transfer_data(m_final_frame->GetFrame(), intermediate_frame->GetFrame(), 0); ret < 0) { + LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret)); + return {}; + } + } else { + m_final_frame = std::move(intermediate_frame); + } + + return std::move(m_final_frame); } void DecodeApi::Reset() { @@ -507,4 +302,4 @@ std::shared_ptr DecodeApi::ReceiveFrame() { return m_decoder_context->ReceiveFrame(); } -} // namespace FFmpeg +} diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.h b/src/video_core/host1x/ffmpeg/ffmpeg.h index bd0ad97ad..c2bae2c8e 100644 --- a/src/video_core/host1x/ffmpeg/ffmpeg.h +++ b/src/video_core/host1x/ffmpeg/ffmpeg.h @@ -20,10 +20,9 @@ extern "C" { #endif #include -#include -#ifndef ANDROID #include -#endif +#include +#include #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop @@ -106,7 +105,11 @@ public: } bool IsInterlaced() const { - return m_frame->interlaced_frame != 0; +#if defined(FF_API_INTERLACED_FRAME) || LIBAVUTIL_VERSION_MAJOR >= 59 + return m_frame->flags & AV_FRAME_FLAG_INTERLACED; +#else + return m_frame->interlaced_frame; +#endif } bool IsHardwareDecoded() const { @@ -188,8 +191,7 @@ public: private: const Decoder& m_decoder; AVCodecContext* m_codec_context{}; - s32 m_got_frame{}; - std::shared_ptr m_temp_frame{}; + std::shared_ptr m_final_frame{}; bool m_decode_order{}; }; diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp index 741a7d5c1..2d337858f 100644 --- a/src/video_core/host1x/nvdec.cpp +++ b/src/video_core/host1x/nvdec.cpp @@ -34,10 +34,6 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) { CreateDecoder(static_cast(argument)); break; case NVDEC_REG_INDEX(execute): { - if (wait_needed) { - std::this_thread::sleep_for(std::chrono::milliseconds(32)); - wait_needed = false; - } Execute(); } break; } diff --git a/src/video_core/host1x/nvdec.h b/src/video_core/host1x/nvdec.h index 565c65f66..55aeac894 100644 --- a/src/video_core/host1x/nvdec.h +++ b/src/video_core/host1x/nvdec.h @@ -28,10 +28,6 @@ public: return syncpoint; } - void SetWait() { - wait_needed = true; - } - private: /// Create the decoder when the codec id is set void CreateDecoder(NvdecCommon::VideoCodec codec); @@ -45,7 +41,6 @@ private: NvdecCommon::NvdecRegisters regs{}; std::unique_ptr decoder; - bool wait_needed{false}; }; } // namespace Host1x