mirror of
https://github.com/moonlight-stream/moonlight-qt
synced 2025-01-09 09:48:43 +00:00
Rewrite FFmpeg decoder selection logic to dynamically discover compatible decoders
This avoids having to maintain a hardcoded list of all known H.264/HEVC/AV1 decoders and ensures new/unknown decoders will just work automatically (assuming we have a renderer that can process their output pixel format).
This commit is contained in:
parent
fc5e1ed5d8
commit
cd4a6f3e17
2 changed files with 144 additions and 124 deletions
|
@ -47,12 +47,8 @@
|
|||
|
||||
#define FAILED_DECODES_RESET_THRESHOLD 20
|
||||
|
||||
typedef struct {
|
||||
const char* codec;
|
||||
int capabilities;
|
||||
} codec_info_t;
|
||||
|
||||
static const QList<codec_info_t> k_NonHwaccelH264Codecs = {
|
||||
static const QMap<QString, int> k_NonHwaccelCodecInfo = {
|
||||
// H.264
|
||||
#ifdef HAVE_MMAL
|
||||
{"h264_mmal", 0},
|
||||
#endif
|
||||
|
@ -68,17 +64,15 @@ static const QList<codec_info_t> k_NonHwaccelH264Codecs = {
|
|||
{"h264_v4l2m2m", 0},
|
||||
#endif
|
||||
{"h264_omx", 0},
|
||||
};
|
||||
|
||||
static const QList<codec_info_t> 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<codec_info_t> 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;
|
||||
capabilities = k_NonHwaccelCodecInfo.value(m_VideoDecoderCtx->codec->name, 0);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Found capabilities for non-hwaccel H.264 decoder: %s -> %d",
|
||||
"Using capabilities table for 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -879,14 +858,15 @@ 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -901,6 +881,7 @@ bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decod
|
|||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder->pix_fmts == NULL) {
|
||||
// Supported output pixel formats are unknown. We'll just try DRM/SDL and hope it can cope.
|
||||
|
@ -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,30 +1020,25 @@ 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) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
@ -1077,31 +1053,47 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||
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)) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
|
||||
// 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)
|
||||
|
@ -1120,17 +1112,44 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(); })) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue