diff options
Diffstat (limited to 'src/codec/ffmpeg/decoder.c')
| -rw-r--r-- | src/codec/ffmpeg/decoder.c | 143 |
1 files changed, 87 insertions, 56 deletions
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c index 7d50bca..3da0027 100644 --- a/src/codec/ffmpeg/decoder.c +++ b/src/codec/ffmpeg/decoder.c @@ -4,8 +4,11 @@ #include <libavutil/cpu.h> #include "decoder.h" +#include "common.h" #ifdef CAMU_FF_DECODER_HWACCEL +// If hwaccel throughput is worse then expected on linux, test with +// your GPU set to "highest clocks"/power_dpm_force_performance_level = high. #include "../../render/renderer.h" static const char *hwdevices[] = { #ifdef CAMU_HAVE_VULKAN_DECODE @@ -23,31 +26,14 @@ static const char *hwdevices[] = { }; #endif -static bool alloc_codec_context_internal(struct camu_ff_decoder *av, const AVCodec *codec, AVCodecParameters *codecpar) +static void log_codec_name(const AVCodec *codec, const char *hwdevice_name) { - if (!(av->codec_context = avcodec_alloc_context3(codec))) { - log_error("Failed to alloc codec context."); - return false; - } - - if (avcodec_parameters_to_context(av->codec_context, codecpar) < 0) { - log_error("Failed to copy codec parameters."); - return false; + const char *name = codec->long_name ? codec->long_name : codec->name; + if (hwdevice_name) { + log_info("Codec: %s (via %s hwaccel).", name, hwdevice_name); + } else { + log_info("Codec: %s.", name); } - - return true; -} - -static bool open_avcodec_internal(struct camu_ff_decoder *av, const AVCodec *codec) -{ - AVDictionary *opts = NULL; - s32 ret = avcodec_open2(av->codec_context, codec, &opts); - if (ret < 0) { - log_error("Failed to open codec %s (%s).", codec->name, av_err2str(ret)); - return false; - } - av_dict_free(&opts); - return true; } #ifdef CAMU_FF_DECODER_HWACCEL @@ -93,39 +79,47 @@ static s32 init_hwframe_context(struct camu_ff_decoder *av, AVCodecContext *cont return ret; } +static bool fallback_to_sw_codec(struct camu_ff_decoder *av, const AVCodec *codec) +{ + log_warn("Attempting software decoding fallback..."); + // Remake AVCodecContext as a software decoder. + AVCodecParameters *codecpar = av->codecpar; + av->errored_hw_context = av->codec_context; + av->codec_context = camu_ff_alloc_codec_context(codec, codecpar); + if (!av->codec_context) { + return false; + } + // Note about thread_type choice in ff_decoder_init(). + av->codec_context->thread_type = FF_THREAD_FRAME; + av->codec_context->thread_count = av->thread_count; + if (!camu_ff_open_avcodec(av->codec_context, codec, NULL)) { + return false; + } + log_codec_name(codec, NULL); + log_info("Using software fallback with %i threads for decoder.", av->thread_count); + return true; +} + static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPixelFormat *pix_fmts) { struct camu_ff_decoder *av = (struct camu_ff_decoder *)context->opaque; - const AVCodec *codec = av->codec_context->codec; - const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type); - const enum AVPixelFormat *fmt = pix_fmts; for (; *fmt != AV_PIX_FMT_NONE; fmt++) { if (*fmt == av->hw_pix_fmt) { if (av->use_frames_context) { init_hwframe_context(av, av->codec_context, av->hw_context); } - log_info("Using %s hardware decoding.", hwdevice_name); - goto out; + return *fmt; } } + const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type); log_error("Failed to get %s HW surface format.", hwdevice_name); - // Remake AVCodecContext as a software decoder. - AVCodecParameters *codecpar = av->codecpar; - av->errored_hw_context = av->codec_context; - alloc_codec_context_internal(av, codec, codecpar); - // Note about thread_type choice in ff_decoder_init(). - av->codec_context->thread_type = FF_THREAD_FRAME; - av->codec_context->thread_count = av->thread_count; - if (open_avcodec_internal(av, codec)) { - log_info("Software fallback, using %i threads for decoder.", av->thread_count); - } + fallback_to_sw_codec(av, av->sw_codec); -out: - return *fmt; + return AV_PIX_FMT_NONE; } static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *context) @@ -137,14 +131,17 @@ static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *con if (av->hw_device_type == AV_HWDEVICE_TYPE_D3D11VA) { log_info("av_hwdevice_ctx_create() returns."); } + const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type); if (ret < 0) { - log_error("Failed to create specified HW device."); + log_error("Failed to create %s HW device.", hwdevice_name); return ret; } context->hw_device_ctx = av_buffer_ref(av->hw_context); +#ifdef CAMU_HUGE_VIDEO_BUFFER // Note that context->extra_hw_frames has the ability to cause corruption. - context->extra_hw_frames = 12; + context->extra_hw_frames = 48; +#endif return ret; } @@ -188,8 +185,12 @@ static bool get_hwdevice_config(struct camu_ff_decoder *av, const AVCodec *codec return true; } } - const char *hwdevice_name = av_hwdevice_get_type_name(hw_device_type); - log_warn("No hw_config for device type %s (type: %d).", hwdevice_name, hw_device_type); + if (hw_device_type == AV_HWDEVICE_TYPE_NONE) { + log_warn("No hw_config for codec %s.", codec->name); + } else { + const char *hwdevice_name = av_hwdevice_get_type_name(hw_device_type); + log_warn("No hw_config for device type %s.", hwdevice_name); + } return false; } @@ -243,10 +244,21 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend log_error("Failed to find decoder."); return false; } +#ifdef CAMU_FF_DECODER_HWACCEL + av->sw_codec = codec; + // @TODO: How do you correctly probe avcodec_get_hw_config() if find_decoder() + // always returns libdav1d first? + if (al_strscmp(codec->name, "libdav1d") == 0) { + if (!(codec = avcodec_find_decoder_by_name("av1"))) { + codec = av->sw_codec; + } + } +#endif bool is_video = codecpar->codec_type == AVMEDIA_TYPE_VIDEO && stream->duration > 0; #ifdef CAMU_FF_DECODER_HWACCEL + bool hw_codec_selected = false; if (is_video) { collect_supported_hwaccels(av); av->hw_device_type = AV_HWDEVICE_TYPE_NONE; @@ -254,21 +266,22 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend for (u32 i = 0; i < ARRAY_SIZE(hwdevices); i++) { size_t length = sizeof(hwdevices[i]) - 1; av->hw_device_type = hw_device_supported_by_name(av, hwdevices[i], length); - if (!get_hwdevice_config(av, codec, av->hw_device_type)) { - av->hw_device_type = AV_HWDEVICE_TYPE_NONE; - } else { + if (get_hwdevice_config(av, codec, av->hw_device_type)) { break; + } else { + av->hw_device_type = AV_HWDEVICE_TYPE_NONE; } } if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) { const AVCodec *hw_codec; al_array_foreach(av->supported_hw_codecs, i, hw_codec) { if (hw_codec->id == codecpar->codec_id) { - // @TODO: hw_codec's without a hwdevice (v4l2m2m, cuvid, amf). + // @TODO: hw_codecs without a hwdevice (v4l2m2m, cuvid, amf). size_t length = al_strlen(hw_codec->wrapper_name); av->hw_device_type = hw_device_supported_by_name(av, hw_codec->wrapper_name, length); if (get_hwdevice_config(av, hw_codec, av->hw_device_type)) { codec = hw_codec; + hw_codec_selected = true; break; } av->hw_device_type = AV_HWDEVICE_TYPE_NONE; @@ -281,7 +294,8 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend } #endif - if (!alloc_codec_context_internal(av, codec, codecpar)) { + av->codec_context = camu_ff_alloc_codec_context(codec, codecpar); + if (!av->codec_context) { return false; } @@ -302,14 +316,18 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend #endif if (is_video) { - av->thread_count = MIN(8, MAX(1, av_cpu_count() / 2)); +#ifdef CAMU_HUGE_VIDEO_BUFFER + av->thread_count = av_cpu_count() * 2; +#else + av->thread_count = MIN(12, MAX(1, av_cpu_count())); +#endif #ifdef CAMU_FF_DECODER_HWACCEL - if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) { + if (!(hw_codec_selected || av->hw_device_type != AV_HWDEVICE_TYPE_NONE)) { #endif // FF_THREAD_FRAME or FF_THREAD_SLICE. // Both modes can get choked on certain videos, higher thread_count with // FRAME seems like the safest default. - // @TODO: Check ffmpeg source, SLICE + FRAME? + // @TODO: Check ffmpeg source, is SLICE + FRAME real? // - https://ffmpeg.org/ffmpeg-codecs.html#Codec-Options av->codec_context->thread_type = FF_THREAD_FRAME; av->codec_context->thread_count = av->thread_count; @@ -322,13 +340,26 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend //av->codec_context->flags |= AV_CODEC_FLAG_BITEXACT; //av->codec_context->flags2 |= AV_CODEC_FLAG2_FAST; - if (!open_avcodec_internal(av, codec)) { + if (!camu_ff_open_avcodec(av->codec_context, codec, NULL)) { +#ifdef CAMU_FF_DECODER_HWACCEL + if (hw_codec_selected && fallback_to_sw_codec(av, av->sw_codec)) { + codec = av->sw_codec; + hw_codec_selected = false; + } else { + return false; + } +#else return false; +#endif } - log_info("Codec: %s (%s) %lldkbps.", codec->name, - codec->long_name ? codec->long_name : codec->name, - (av->codec_context->bit_rate > 0) ? av->codec_context->bit_rate / 1000 : 0); + const char *hwdevice_name = NULL; +#ifdef CAMU_FF_DECODER_HWACCEL + if (av->hw_device_type != AV_HWDEVICE_TYPE_NONE) { + hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type); + } +#endif + log_codec_name(codec, hwdevice_name); av->callback = callback; av->userdata = userdata; |