Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
games/moonlight-qt: compatibility with ffmpeg 9.0
To:
OpenBSD ports <ports@openbsd.org>
Date:
Thu, 13 Aug 2026 00:57:56 +0200

Download raw body.

Thread
  • Kirill A. Korinsky:

    games/moonlight-qt: compatibility with ffmpeg 9.0

ports@,

I'd like to backport two commits:

- https://github.com/moonlight-stream/moonlight-qt/commit/13880353d8741ae331bf13433c5ad80071d5f44f
- https://github.com/moonlight-stream/moonlight-qt/commit/6ce02616f04fb83cd7c5d1492b03367f2220ac46

which brings support of ffmpeg-9.0 to games/moonlight-qt

Build tested against ffmpeg-9.0 and ffmpeg-8.1.2

Ok?

Index: Makefile
===================================================================
RCS file: /home/cvs/ports/games/moonlight-qt/Makefile,v
diff -u -p -r1.12 Makefile
--- Makefile	18 Feb 2026 18:25:17 -0000	1.12
+++ Makefile	12 Aug 2026 22:45:00 -0000
@@ -5,7 +5,7 @@ PKGNAME =		moonlight-qt-${V}
 
 DISTNAME =		MoonlightSrc-${V}
 SITES =			https://github.com/moonlight-stream/moonlight-qt/releases/download/v${V}/
-REVISION =		1
+REVISION =		2
 
 CATEGORIES =		games
 
Index: patches/patch-app_streaming_video_ffmpeg-renderers_plvk_cpp
===================================================================
RCS file: patches/patch-app_streaming_video_ffmpeg-renderers_plvk_cpp
diff -N patches/patch-app_streaming_video_ffmpeg-renderers_plvk_cpp
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ patches/patch-app_streaming_video_ffmpeg-renderers_plvk_cpp	12 Aug 2026 22:46:11 -0000
@@ -0,0 +1,166 @@
+https://github.com/moonlight-stream/moonlight-qt/commit/6ce02616f04fb83cd7c5d1492b03367f2220ac46
+
+Index: app/streaming/video/ffmpeg-renderers/plvk.cpp
+--- app/streaming/video/ffmpeg-renderers/plvk.cpp.orig
++++ app/streaming/video/ffmpeg-renderers/plvk.cpp
+@@ -14,8 +14,9 @@
+ #include <vector>
+ #include <set>
+ 
+-#ifndef VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME
++#ifndef VK_KHR_video_decode_av1
+ #define VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME "VK_KHR_video_decode_av1"
++#define VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR ((VkVideoCodecOperationFlagBitsKHR)0x00000004)
+ #endif
+ 
+ // Keep these in sync with hwcontext_vulkan.c
+@@ -317,7 +318,7 @@ bool PlVkRenderer::tryInitializeDevice(VkPhysicalDevic
+     vkParams.device = device;
+     vkParams.opt_extensions = k_OptionalDeviceExtensions;
+     vkParams.num_opt_extensions = SDL_arraysize(k_OptionalDeviceExtensions);
+-    vkParams.extra_queues = VK_QUEUE_VIDEO_DECODE_BIT_KHR;
++    vkParams.extra_queues = m_HwAccelBackend ? VK_QUEUE_FLAG_BITS_MAX_ENUM : 0;
+     m_Vulkan = pl_vulkan_create(m_Log, &vkParams);
+     if (m_Vulkan == nullptr) {
+         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+@@ -399,7 +400,7 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS para
+ 
+     // Lookup all Vulkan functions we require
+     POPULATE_FUNCTION(vkDestroySurfaceKHR);
+-    POPULATE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties);
++    POPULATE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties2);
+     POPULATE_FUNCTION(vkGetPhysicalDeviceSurfacePresentModesKHR);
+     POPULATE_FUNCTION(vkGetPhysicalDeviceSurfaceFormatsKHR);
+     POPULATE_FUNCTION(vkEnumeratePhysicalDevices);
+@@ -504,24 +505,13 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS para
+         vkDeviceContext->nb_enabled_inst_extensions = m_PlVkInstance->num_extensions;
+         vkDeviceContext->enabled_dev_extensions = m_Vulkan->extensions;
+         vkDeviceContext->nb_enabled_dev_extensions = m_Vulkan->num_extensions;
+-        vkDeviceContext->queue_family_index = m_Vulkan->queue_graphics.index;
+-        vkDeviceContext->nb_graphics_queues = m_Vulkan->queue_graphics.count;
+-        vkDeviceContext->queue_family_tx_index = m_Vulkan->queue_transfer.index;
+-        vkDeviceContext->nb_tx_queues = m_Vulkan->queue_transfer.count;
+-        vkDeviceContext->queue_family_comp_index = m_Vulkan->queue_compute.index;
+-        vkDeviceContext->nb_comp_queues = m_Vulkan->queue_compute.count;
+ #if LIBAVUTIL_VERSION_INT > AV_VERSION_INT(58, 9, 100)
+         vkDeviceContext->lock_queue = lockQueue;
+         vkDeviceContext->unlock_queue = unlockQueue;
+ #endif
+ 
+-        static_assert(sizeof(vkDeviceContext->queue_family_decode_index) == sizeof(uint32_t), "sizeof(int) != sizeof(uint32_t)");
+-        static_assert(sizeof(vkDeviceContext->nb_decode_queues) == sizeof(uint32_t), "sizeof(int) != sizeof(uint32_t)");
+-        if (!getQueue(VK_QUEUE_VIDEO_DECODE_BIT_KHR, (uint32_t*)&vkDeviceContext->queue_family_decode_index, (uint32_t*)&vkDeviceContext->nb_decode_queues)) {
+-            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
+-                        "Vulkan video decoding is not supported by the Vulkan device");
+-            return false;
+-        }
++        // Populate the device queues for decoding this video format
++        populateQueues(params->videoFormat);
+ 
+         int err = av_hwdevice_ctx_init(m_HwDeviceCtx);
+         if (err < 0) {
+@@ -581,23 +571,86 @@ bool PlVkRenderer::mapAvFrameToPlacebo(const AVFrame *
+     return true;
+ }
+ 
+-bool PlVkRenderer::getQueue(VkQueueFlags requiredFlags, uint32_t *queueIndex, uint32_t *queueCount)
++bool PlVkRenderer::populateQueues(int videoFormat)
+ {
++    auto vkDeviceContext = (AVVulkanDeviceContext*)((AVHWDeviceContext *)m_HwDeviceCtx->data)->hwctx;
++
+     uint32_t queueFamilyCount = 0;
+-    fn_vkGetPhysicalDeviceQueueFamilyProperties(m_Vulkan->phys_device, &queueFamilyCount, nullptr);
++    fn_vkGetPhysicalDeviceQueueFamilyProperties2(m_Vulkan->phys_device, &queueFamilyCount, nullptr);
+ 
+-    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+-    fn_vkGetPhysicalDeviceQueueFamilyProperties(m_Vulkan->phys_device, &queueFamilyCount, queueFamilies.data());
++    std::vector<VkQueueFamilyProperties2> queueFamilies(queueFamilyCount);
++    std::vector<VkQueueFamilyVideoPropertiesKHR> queueFamilyVideoProps(queueFamilyCount);
++    for (uint32_t i = 0; i < queueFamilyCount; i++) {
++        queueFamilyVideoProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR;
++        queueFamilies[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
++        queueFamilies[i].pNext = &queueFamilyVideoProps[i];
++    }
+ 
++    fn_vkGetPhysicalDeviceQueueFamilyProperties2(m_Vulkan->phys_device, &queueFamilyCount, queueFamilies.data());
++
++#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 34, 100)
++    Q_UNUSED(videoFormat);
++
+     for (uint32_t i = 0; i < queueFamilyCount; i++) {
+-        if ((queueFamilies[i].queueFlags & requiredFlags) == requiredFlags) {
+-            *queueIndex = i;
+-            *queueCount = queueFamilies[i].queueCount;
+-            return true;
++        vkDeviceContext->qf[i].idx = i;
++        vkDeviceContext->qf[i].num = queueFamilies[i].queueFamilyProperties.queueCount;
++        vkDeviceContext->qf[i].flags = (VkQueueFlagBits)queueFamilies[i].queueFamilyProperties.queueFlags;
++        vkDeviceContext->qf[i].video_caps = (VkVideoCodecOperationFlagBitsKHR)queueFamilyVideoProps[i].videoCodecOperations;
++    }
++    vkDeviceContext->nb_qf = queueFamilyCount;
++#else
++    vkDeviceContext->queue_family_index = m_Vulkan->queue_graphics.index;
++    vkDeviceContext->nb_graphics_queues = m_Vulkan->queue_graphics.count;
++    vkDeviceContext->queue_family_tx_index = m_Vulkan->queue_transfer.index;
++    vkDeviceContext->nb_tx_queues = m_Vulkan->queue_transfer.count;
++    vkDeviceContext->queue_family_comp_index = m_Vulkan->queue_compute.index;
++    vkDeviceContext->nb_comp_queues = m_Vulkan->queue_compute.count;
++
++    // Select a video decode queue that is capable of decoding our chosen format
++    for (uint32_t i = 0; i < queueFamilyCount; i++) {
++        if (queueFamilies[i].queueFamilyProperties.queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR) {
++            if (videoFormat & VIDEO_FORMAT_MASK_H264) {
++                if (queueFamilyVideoProps[i].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR) {
++                    vkDeviceContext->queue_family_decode_index = i;
++                    vkDeviceContext->nb_decode_queues = queueFamilies[i].queueFamilyProperties.queueCount;
++                    break;
++                }
++            }
++            else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
++                if (queueFamilyVideoProps[i].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR) {
++                    vkDeviceContext->queue_family_decode_index = i;
++                    vkDeviceContext->nb_decode_queues = queueFamilies[i].queueFamilyProperties.queueCount;
++                    break;
++                }
++            }
++            else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
++#if LIBAVCODEC_VERSION_MAJOR >= 61
++                // VK_KHR_video_decode_av1 added VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR to check for AV1
++                // decoding support on this queue. Since FFmpeg 6.1 used the older Mesa-specific AV1 extension,
++                // we'll just assume all video decode queues on this device support AV1 (since we checked that
++                // the physical device supports it earlier.
++                if (queueFamilyVideoProps[i].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR)
++#endif
++                {
++                    vkDeviceContext->queue_family_decode_index = i;
++                    vkDeviceContext->nb_decode_queues = queueFamilies[i].queueFamilyProperties.queueCount;
++                    break;
++                }
++            }
++            else {
++                SDL_assert(false);
++            }
+         }
+     }
+ 
+-    return false;
++    if (vkDeviceContext->queue_family_decode_index < 0) {
++        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
++                     "Unable to find compatible video decode queue!");
++        return false;
++    }
++#endif
++
++    return true;
+ }
+ 
+ bool PlVkRenderer::isPresentModeSupportedByPhysicalDevice(VkPhysicalDevice device, VkPresentModeKHR presentMode)
+@@ -637,7 +690,7 @@ bool PlVkRenderer::isColorSpaceSupportedByPhysicalDevi
+ bool PlVkRenderer::isSurfacePresentationSupportedByPhysicalDevice(VkPhysicalDevice device)
+ {
+     uint32_t queueFamilyCount = 0;
+-    fn_vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
++    fn_vkGetPhysicalDeviceQueueFamilyProperties2(device, &queueFamilyCount, nullptr);
+ 
+     for (uint32_t i = 0; i < queueFamilyCount; i++) {
+         VkBool32 supported = VK_FALSE;
Index: patches/patch-app_streaming_video_ffmpeg-renderers_plvk_h
===================================================================
RCS file: patches/patch-app_streaming_video_ffmpeg-renderers_plvk_h
diff -N patches/patch-app_streaming_video_ffmpeg-renderers_plvk_h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ patches/patch-app_streaming_video_ffmpeg-renderers_plvk_h	12 Aug 2026 22:46:14 -0000
@@ -0,0 +1,23 @@
+https://github.com/moonlight-stream/moonlight-qt/commit/6ce02616f04fb83cd7c5d1492b03367f2220ac46
+
+Index: app/streaming/video/ffmpeg-renderers/plvk.h
+--- app/streaming/video/ffmpeg-renderers/plvk.h.orig
++++ app/streaming/video/ffmpeg-renderers/plvk.h
+@@ -37,7 +37,7 @@ class PlVkRenderer : public IFFmpegRenderer { (private
+     static void overlayUploadComplete(void* opaque);
+ 
+     bool mapAvFrameToPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
+-    bool getQueue(VkQueueFlags requiredFlags, uint32_t* queueIndex, uint32_t* queueCount);
++    bool populateQueues(int videoFormat);
+     bool chooseVulkanDevice(PDECODER_PARAMETERS params, bool hdrOutputRequired);
+     bool tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDeviceProperties* deviceProps,
+                              PDECODER_PARAMETERS decoderParams, bool hdrOutputRequired);
+@@ -95,7 +95,7 @@ class PlVkRenderer : public IFFmpegRenderer { (private
+ 
+     // Vulkan functions we call directly
+     PFN_vkDestroySurfaceKHR fn_vkDestroySurfaceKHR = nullptr;
+-    PFN_vkGetPhysicalDeviceQueueFamilyProperties fn_vkGetPhysicalDeviceQueueFamilyProperties = nullptr;
++    PFN_vkGetPhysicalDeviceQueueFamilyProperties2 fn_vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr;
+     PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fn_vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr;
+     PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fn_vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
+     PFN_vkEnumeratePhysicalDevices fn_vkEnumeratePhysicalDevices = nullptr;
Index: patches/patch-app_streaming_video_ffmpeg_cpp
===================================================================
RCS file: patches/patch-app_streaming_video_ffmpeg_cpp
diff -N patches/patch-app_streaming_video_ffmpeg_cpp
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ patches/patch-app_streaming_video_ffmpeg_cpp	12 Aug 2026 22:45:37 -0000
@@ -0,0 +1,152 @@
+https://github.com/moonlight-stream/moonlight-qt/commit/13880353d8741ae331bf13433c5ad80071d5f44f
+
+Index: app/streaming/video/ffmpeg.cpp
+--- app/streaming/video/ffmpeg.cpp.orig
++++ app/streaming/video/ffmpeg.cpp
+@@ -1076,15 +1076,15 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const A
+ #define TRY_PREFERRED_PIXEL_FORMAT(RENDERER_TYPE) \
+     { \
+         RENDERER_TYPE renderer; \
+-        if (renderer.getPreferredPixelFormat(params->videoFormat) == decoder->pix_fmts[i]) { \
++        if (renderer.getPreferredPixelFormat(params->videoFormat) == decoder_pix_fmts[i]) { \
+             SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
+                         "Trying " #RENDERER_TYPE " for codec %s due to preferred pixel format: 0x%x", \
+-                        decoder->name, decoder->pix_fmts[i]); \
+-            if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, nullptr, \
++                        decoder->name, decoder_pix_fmts[i]); \
++            if (tryInitializeRenderer(decoder, decoder_pix_fmts[i], params, nullptr, nullptr, \
+                                       []() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \
+                 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
+                             "Chose " #RENDERER_TYPE " for codec %s due to preferred pixel format: 0x%x", \
+-                            decoder->name, decoder->pix_fmts[i]); \
++                            decoder->name, decoder_pix_fmts[i]); \
+                 return true; \
+             } \
+         } \
+@@ -1093,16 +1093,16 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const A
+ #define TRY_SUPPORTED_NON_PREFERRED_PIXEL_FORMAT(RENDERER_TYPE) \
+     { \
+         RENDERER_TYPE renderer; \
+-        if (decoder->pix_fmts[i] != renderer.getPreferredPixelFormat(params->videoFormat) && \
+-            renderer.isPixelFormatSupported(params->videoFormat, decoder->pix_fmts[i])) { \
++        if (decoder_pix_fmts[i] != renderer.getPreferredPixelFormat(params->videoFormat) && \
++            renderer.isPixelFormatSupported(params->videoFormat, decoder_pix_fmts[i])) { \
+             SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
+                         "Trying " #RENDERER_TYPE " for codec %s due to compatible pixel format: 0x%x", \
+-                        decoder->name, decoder->pix_fmts[i]); \
+-            if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, nullptr, \
++                        decoder->name, decoder_pix_fmts[i]); \
++            if (tryInitializeRenderer(decoder, decoder_pix_fmts[i], params, nullptr, nullptr, \
+                                       []() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \
+                 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
+                             "Chose " #RENDERER_TYPE " for codec %s due to compatible pixel format: 0x%x", \
+-                            decoder->name, decoder->pix_fmts[i]); \
++                            decoder->name, decoder_pix_fmts[i]); \
+                 return true; \
+             } \
+         } \
+@@ -1116,6 +1116,16 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+         return false;
+     }
+ 
++    const AVPixelFormat* decoder_pix_fmts;
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100)
++    if (avcodec_get_supported_config(nullptr, decoder, AV_CODEC_CONFIG_PIX_FORMAT, 0,
++                                     (const void**)&decoder_pix_fmts, nullptr) < 0) {
++        decoder_pix_fmts = nullptr;
++    }
++#else
++    decoder_pix_fmts = decoder->pix_fmts;
++#endif
++
+     // This might be a hwaccel decoder, so try any hw configs first
+     if (tryHwAccel) {
+         for (int pass = 0; pass <= MAX_DECODER_PASS; pass++) {
+@@ -1141,7 +1151,7 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+         }
+     }
+ 
+-    if (decoder->pix_fmts == NULL) {
++    if (decoder_pix_fmts == NULL) {
+         // Supported output pixel formats are unknown. We'll just try DRM/SDL and hope it can cope.
+ 
+ #if defined(HAVE_DRM) && defined(GL_IS_SLOW)
+@@ -1177,11 +1187,11 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+     // Even if it didn't completely deadlock us, the performance would likely be atrocious.
+     if (strcmp(decoder->name, "h264_mmal") == 0) {
+ #ifdef HAVE_MMAL
+-        for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++        for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+             TRY_PREFERRED_PIXEL_FORMAT(MmalRenderer);
+         }
+ 
+-        for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++        for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+             TRY_SUPPORTED_NON_PREFERRED_PIXEL_FORMAT(MmalRenderer);
+         }
+ #endif
+@@ -1191,7 +1201,7 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+     }
+ 
+     // Check if any of our decoders prefer any of the pixel formats first
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+ #ifdef HAVE_DRM
+         TRY_PREFERRED_PIXEL_FORMAT(DrmRenderer);
+ #endif
+@@ -1204,7 +1214,7 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+     }
+ 
+     // Nothing prefers any of them. Let's see if anyone will tolerate one.
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+ #ifdef HAVE_DRM
+         TRY_SUPPORTED_NON_PREFERRED_PIXEL_FORMAT(DrmRenderer);
+ #endif
+@@ -1219,10 +1229,10 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+ #if defined(HAVE_LIBPLACEBO_VULKAN) && defined(VULKAN_IS_SLOW)
+     // If we got here with VULKAN_IS_SLOW, DrmRenderer didn't work,
+     // so we have to resort to PlVkRenderer.
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+         TRY_PREFERRED_PIXEL_FORMAT(PlVkRenderer);
+     }
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+         TRY_SUPPORTED_NON_PREFERRED_PIXEL_FORMAT(PlVkRenderer);
+     }
+ #endif
+@@ -1230,10 +1240,10 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnkno
+ #ifdef GL_IS_SLOW
+     // If we got here with GL_IS_SLOW, DrmRenderer didn't work, so we have
+     // to resort to SdlRenderer.
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+         TRY_PREFERRED_PIXEL_FORMAT(SdlRenderer);
+     }
+-    for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++    for (int i = 0; decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+         TRY_SUPPORTED_NON_PREFERRED_PIXEL_FORMAT(SdlRenderer);
+     }
+ #endif
+@@ -1369,9 +1379,18 @@ bool FFmpegVideoDecoder::tryInitializeNonHwAccelDecode
+ 
+         // Skip decoders without zero-copy output formats if requested
+         if (requireZeroCopyFormat) {
++            const AVPixelFormat* decoder_pix_fmts;
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100)
++            if (avcodec_get_supported_config(nullptr, decoder, AV_CODEC_CONFIG_PIX_FORMAT, 0,
++                                             (const void**)&decoder_pix_fmts, nullptr) < 0) {
++                decoder_pix_fmts = nullptr;
++            }
++#else
++            decoder_pix_fmts = decoder->pix_fmts;
++#endif
+             bool foundZeroCopyFormat = false;
+-            for (int i = 0; decoder->pix_fmts && decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+-                if (isZeroCopyFormat(decoder->pix_fmts[i])) {
++            for (int i = 0; decoder_pix_fmts && decoder_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
++                if (isZeroCopyFormat(decoder_pix_fmts[i])) {
+                     foundZeroCopyFormat = true;
+                     break;
+                 }


-- 
wbr, Kirill