diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index d67245ba..012a62fe 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -47,12 +47,8 @@ #define FAILED_DECODES_RESET_THRESHOLD 20 -typedef struct { - const char* codec; - int capabilities; -} codec_info_t; - -static const QList k_NonHwaccelH264Codecs = { +static const QMap k_NonHwaccelCodecInfo = { + // H.264 #ifdef HAVE_MMAL {"h264_mmal", 0}, #endif @@ -68,17 +64,15 @@ static const QList k_NonHwaccelH264Codecs = { {"h264_v4l2m2m", 0}, #endif {"h264_omx", 0}, -}; -static const QList k_NonHwaccelHEVCCodecs = { + // HEVC {"hevc_rkmpp", 0}, {"hevc_nvv4l2", CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC}, {"hevc_nvmpi", 0}, {"hevc_v4l2m2m", 0}, {"hevc_omx", 0}, -}; -static const QList k_NonHwaccelAV1Codecs = { + // AV1 }; bool FFmpegVideoDecoder::isHardwareAccelerated() @@ -131,26 +125,11 @@ int FFmpegVideoDecoder::getDecoderCapabilities() // We have a non-hwaccel hardware decoder. This will always // be using SDLRenderer/DrmRenderer so we will pick decoder // capabilities based on the decoder name. - for (const codec_info_t& codecInfo : k_NonHwaccelH264Codecs) { - if (strcmp(m_VideoDecoderCtx->codec->name, codecInfo.codec) == 0) { - capabilities = codecInfo.capabilities; - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Found capabilities for non-hwaccel H.264 decoder: %s -> %d", - m_VideoDecoderCtx->codec->name, - capabilities); - break; - } - } - for (const codec_info_t& codecInfo : k_NonHwaccelHEVCCodecs) { - if (strcmp(m_VideoDecoderCtx->codec->name, codecInfo.codec) == 0) { - capabilities = codecInfo.capabilities; - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Found capabilities for non-hwaccel HEVC decoder: %s -> %d", - m_VideoDecoderCtx->codec->name, - capabilities); - break; - } - } + capabilities = k_NonHwaccelCodecInfo.value(m_VideoDecoderCtx->codec->name, 0); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Using capabilities table for decoder: %s -> %d", + m_VideoDecoderCtx->codec->name, + capabilities); } } @@ -879,26 +858,28 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, } \ } -bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decoderName, - PDECODER_PARAMETERS params) +bool FFmpegVideoDecoder::tryInitializeRendererForUnknownDecoder(const AVCodec* decoder, + PDECODER_PARAMETERS params, + bool tryHwAccel) { - const AVCodec* decoder = avcodec_find_decoder_by_name(decoderName); - if (decoder == nullptr) { + if (!decoder) { return false; } - // This might be a hwaccel decoder, so try any hw configs first - for (int i = 0;; i++) { - const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); - if (!config) { - // No remaining hwaccel options - break; - } + if (tryHwAccel) { + // This might be a hwaccel decoder, so try any hw configs first + for (int i = 0;; i++) { + const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); + if (!config) { + // No remaining hwaccel options + break; + } - // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, params, config, - [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { - return true; + // Initialize the hardware codec and submit a test frame if the renderer needs it + if (tryInitializeRenderer(decoder, params, config, + [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { + return true; + } } } @@ -923,7 +904,7 @@ bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decod #ifdef HAVE_MMAL // HACK: Avoid using YUV420P on h264_mmal. It can cause a deadlock inside the MMAL libraries. // Even if it didn't completely deadlock us, the performance would likely be atrocious. - if (strcmp(decoderName, "h264_mmal") == 0) { + if (strcmp(decoder->name, "h264_mmal") == 0) { for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) { TRY_PREFERRED_PIXEL_FORMAT(MmalRenderer); } @@ -990,7 +971,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) QString h264DecoderHint = qgetenv("H264_DECODER_HINT"); if (!h264DecoderHint.isEmpty() && (params->videoFormat & VIDEO_FORMAT_MASK_H264)) { QByteArray decoderString = h264DecoderHint.toLocal8Bit(); - if (tryInitializeRendererForDecoderByName(decoderString.constData(), params)) { + if (tryInitializeRendererForUnknownDecoder(avcodec_find_decoder_by_name(decoderString.constData()), params, true)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Using custom H.264 decoder (H264_DECODER_HINT): %s", decoderString.constData()); @@ -1007,7 +988,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) QString hevcDecoderHint = qgetenv("HEVC_DECODER_HINT"); if (!hevcDecoderHint.isEmpty() && (params->videoFormat & VIDEO_FORMAT_MASK_H265)) { QByteArray decoderString = hevcDecoderHint.toLocal8Bit(); - if (tryInitializeRendererForDecoderByName(decoderString.constData(), params)) { + if (tryInitializeRendererForUnknownDecoder(avcodec_find_decoder_by_name(decoderString.constData()), params, true)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Using custom HEVC decoder (HEVC_DECODER_HINT): %s", decoderString.constData()); @@ -1024,7 +1005,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) QString av1DecoderHint = qgetenv("AV1_DECODER_HINT"); if (!av1DecoderHint.isEmpty() && (params->videoFormat & VIDEO_FORMAT_MASK_AV1)) { QByteArray decoderString = av1DecoderHint.toLocal8Bit(); - if (tryInitializeRendererForDecoderByName(decoderString.constData(), params)) { + if (tryInitializeRendererForUnknownDecoder(avcodec_find_decoder_by_name(decoderString.constData()), params, true)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Using custom AV1 decoder (AV1_DECODER_HINT): %s", decoderString.constData()); @@ -1039,98 +1020,136 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) } const AVCodec* decoder; - - if (params->videoFormat & VIDEO_FORMAT_MASK_H264) { - decoder = avcodec_find_decoder(AV_CODEC_ID_H264); - } - else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) { - decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC); - } - else if (params->videoFormat & VIDEO_FORMAT_MASK_AV1) { - decoder = avcodec_find_decoder(AV_CODEC_ID_AV1); - } - else { - Q_ASSERT(false); - decoder = nullptr; - } - - if (!decoder) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Unable to find decoder for format: %x", - params->videoFormat); - return false; - } + void* codecIterator; // Look for a hardware decoder first unless software-only if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) { - // Look for the first matching hwaccel hardware decoder (pass 0) - for (int i = 0;; i++) { - const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); - if (!config) { - // No remaining hwaccel options - break; + // Iterate through tier-1 hwaccel decoders + codecIterator = NULL; + while ((decoder = av_codec_iterate(&codecIterator))) { + // Skip codecs that aren't decoders + if (!av_codec_is_decoder(decoder)) { + continue; } - // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, params, config, - [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { + // Skip decoders that don't match our codec + if (((params->videoFormat & VIDEO_FORMAT_MASK_H264) && decoder->id != AV_CODEC_ID_H264) || + ((params->videoFormat & VIDEO_FORMAT_MASK_H265) && decoder->id != AV_CODEC_ID_HEVC) || + ((params->videoFormat & VIDEO_FORMAT_MASK_AV1) && decoder->id != AV_CODEC_ID_AV1)) { + continue; + } + + // Look for the first matching hwaccel hardware decoder (pass 0) + for (int i = 0;; i++) { + const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); + if (!config) { + // No remaining hwaccel options + break; + } + + // Initialize the hardware codec and submit a test frame if the renderer needs it + if (tryInitializeRenderer(decoder, params, config, + [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { + return true; + } + } + } + + // Iterate through non-hwaccel hardware decoders + codecIterator = NULL; + while ((decoder = av_codec_iterate(&codecIterator))) { + // Skip codecs that aren't decoders + if (!av_codec_is_decoder(decoder)) { + continue; + } + + // Skip decoders that don't match our codec + if (((params->videoFormat & VIDEO_FORMAT_MASK_H264) && decoder->id != AV_CODEC_ID_H264) || + ((params->videoFormat & VIDEO_FORMAT_MASK_H265) && decoder->id != AV_CODEC_ID_HEVC) || + ((params->videoFormat & VIDEO_FORMAT_MASK_AV1) && decoder->id != AV_CODEC_ID_AV1)) { + continue; + } + + // Skip hwaccel and software/hybrid decoders + if (avcodec_get_hw_config(decoder, 0) || !(decoder->capabilities & AV_CODEC_CAP_HARDWARE)) { + continue; + } + + // Try this decoder + if (tryInitializeRendererForUnknownDecoder(decoder, params, false)) { return true; } } - // Continue with special non-hwaccel hardware decoders - if (params->videoFormat & VIDEO_FORMAT_MASK_H264) { - for (const codec_info_t& codecInfo : k_NonHwaccelH264Codecs) { - if (tryInitializeRendererForDecoderByName(codecInfo.codec, params)) { - return true; - } - } - } - else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) { - for (const codec_info_t& codecInfo : k_NonHwaccelHEVCCodecs) { - if (tryInitializeRendererForDecoderByName(codecInfo.codec, params)) { - return true; - } - } - } - else if (params->videoFormat & VIDEO_FORMAT_MASK_AV1) { - for (const codec_info_t& codecInfo : k_NonHwaccelAV1Codecs) { - if (tryInitializeRendererForDecoderByName(codecInfo.codec, params)) { - return true; - } - } - } - else { - Q_ASSERT(false); - } - - // Look for the first matching hwaccel hardware decoder (pass 1) - // This picks up "second-tier" hwaccels like CUDA. - for (int i = 0;; i++) { - const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); - if (!config) { - // No remaining hwaccel options - break; + // Iterate through tier-2 hwaccel decoders + codecIterator = NULL; + while ((decoder = av_codec_iterate(&codecIterator))) { + // Skip codecs that aren't decoders + if (!av_codec_is_decoder(decoder)) { + continue; } - // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, params, config, - [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 1); })) { - return true; + // Skip decoders that don't match our codec + if (((params->videoFormat & VIDEO_FORMAT_MASK_H264) && decoder->id != AV_CODEC_ID_H264) || + ((params->videoFormat & VIDEO_FORMAT_MASK_H265) && decoder->id != AV_CODEC_ID_HEVC) || + ((params->videoFormat & VIDEO_FORMAT_MASK_AV1) && decoder->id != AV_CODEC_ID_AV1)) { + continue; + } + + // Look for the first matching hwaccel hardware decoder (pass 1) + // This picks up "second-tier" hwaccels like CUDA. + for (int i = 0;; i++) { + const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); + if (!config) { + // No remaining hwaccel options + break; + } + + // Initialize the hardware codec and submit a test frame if the renderer needs it + if (tryInitializeRenderer(decoder, params, config, + [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 1); })) { + return true; + } } } } - // Fallback to software if no matching hardware decoder was found - // and if software fallback is allowed + // Iterate through all software decoders if allowed if (params->vds != StreamingPreferences::VDS_FORCE_HARDWARE) { - if (tryInitializeRenderer(decoder, params, nullptr, - []() -> IFFmpegRenderer* { return new SdlRenderer(); })) { - return true; + codecIterator = NULL; + while ((decoder = av_codec_iterate(&codecIterator))) { + // Skip codecs that aren't decoders + if (!av_codec_is_decoder(decoder)) { + continue; + } + + // Skip decoders that don't match our codec + if (((params->videoFormat & VIDEO_FORMAT_MASK_H264) && decoder->id != AV_CODEC_ID_H264) || + ((params->videoFormat & VIDEO_FORMAT_MASK_H265) && decoder->id != AV_CODEC_ID_HEVC) || + ((params->videoFormat & VIDEO_FORMAT_MASK_AV1) && decoder->id != AV_CODEC_ID_AV1)) { + continue; + } + + // Skip hardware decoders + // + // NB: We can't skip hwaccel decoders here because they can be both + // hardware and software depending on whether an hwaccel is supplied. + // Instead, we tell tryInitializeRendererForUnknownDecoder() not to + // try hwaccel for this decoder. + if (decoder->capabilities & AV_CODEC_CAP_HARDWARE) { + continue; + } + + // Try this decoder without hwaccel + if (tryInitializeRendererForUnknownDecoder(decoder, params, false)) { + return true; + } } } - // No decoder worked + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Unable to find working decoder for format: %x", + params->videoFormat); return false; } diff --git a/app/streaming/video/ffmpeg.h b/app/streaming/video/ffmpeg.h index ff9b8f14..0f99e8c1 100644 --- a/app/streaming/video/ffmpeg.h +++ b/app/streaming/video/ffmpeg.h @@ -40,8 +40,9 @@ private: bool createFrontendRenderer(PDECODER_PARAMETERS params, bool useAlternateFrontend); - bool tryInitializeRendererForDecoderByName(const char* decoderName, - PDECODER_PARAMETERS params); + bool tryInitializeRendererForUnknownDecoder(const AVCodec* decoder, + PDECODER_PARAMETERS params, + bool tryHwAccel); bool tryInitializeRenderer(const AVCodec* decoder, PDECODER_PARAMETERS params,