diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /video/decode | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'video/decode')
-rw-r--r-- | video/decode/vd_lavc.c | 1457 |
1 files changed, 1457 insertions, 0 deletions
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c new file mode 100644 index 0000000..b971d26 --- /dev/null +++ b/video/decode/vd_lavc.c @@ -0,0 +1,1457 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <float.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <stdbool.h> + +#include <libavcodec/avcodec.h> +#include <libavformat/version.h> +#include <libavutil/common.h> +#include <libavutil/hwcontext.h> +#include <libavutil/opt.h> +#include <libavutil/intreadwrite.h> +#include <libavutil/pixdesc.h> + +#include "mpv_talloc.h" +#include "common/global.h" +#include "common/msg.h" +#include "options/m_config.h" +#include "options/options.h" +#include "osdep/threads.h" +#include "misc/bstr.h" +#include "common/av_common.h" +#include "common/codecs.h" + +#include "video/fmt-conversion.h" + +#include "filters/f_decoder_wrapper.h" +#include "filters/filter_internal.h" +#include "video/hwdec.h" +#include "video/img_format.h" +#include "video/mp_image.h" +#include "video/mp_image_pool.h" +#include "demux/demux.h" +#include "demux/stheader.h" +#include "demux/packet.h" +#include "video/csputils.h" +#include "video/sws_utils.h" +#include "video/out/vo.h" + +#include "options/m_option.h" + +static void init_avctx(struct mp_filter *vd); +static void uninit_avctx(struct mp_filter *vd); + +static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags); +static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx, + const enum AVPixelFormat *pix_fmt); +static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt, + struct bstr name); + +#define HWDEC_DELAY_QUEUE_COUNT 2 + +#define OPT_BASE_STRUCT struct vd_lavc_params + +struct vd_lavc_params { + bool fast; + int film_grain; + bool show_all; + int skip_loop_filter; + int skip_idct; + int skip_frame; + int framedrop; + int threads; + bool bitexact; + bool old_x264; + bool apply_cropping; + bool check_hw_profile; + int software_fallback; + char **avopts; + int dr; + char **hwdec_api; + char *hwdec_codecs; + int hwdec_image_format; + int hwdec_extra_frames; +}; + +static const struct m_opt_choice_alternatives discard_names[] = { + {"none", AVDISCARD_NONE}, + {"default", AVDISCARD_DEFAULT}, + {"nonref", AVDISCARD_NONREF}, + {"bidir", AVDISCARD_BIDIR}, + {"nonkey", AVDISCARD_NONKEY}, + {"all", AVDISCARD_ALL}, + {0} +}; +#define OPT_DISCARD(field) OPT_CHOICE_C(field, discard_names) + +const struct m_sub_options vd_lavc_conf = { + .opts = (const m_option_t[]){ + {"vd-lavc-fast", OPT_BOOL(fast)}, + {"vd-lavc-film-grain", OPT_CHOICE(film_grain, + {"auto", -1}, {"cpu", 0}, {"gpu", 1})}, + {"vd-lavc-show-all", OPT_BOOL(show_all)}, + {"vd-lavc-skiploopfilter", OPT_DISCARD(skip_loop_filter)}, + {"vd-lavc-skipidct", OPT_DISCARD(skip_idct)}, + {"vd-lavc-skipframe", OPT_DISCARD(skip_frame)}, + {"vd-lavc-framedrop", OPT_DISCARD(framedrop)}, + {"vd-lavc-threads", OPT_INT(threads), M_RANGE(0, DBL_MAX)}, + {"vd-lavc-bitexact", OPT_BOOL(bitexact)}, + {"vd-lavc-assume-old-x264", OPT_BOOL(old_x264)}, + {"vd-lavc-check-hw-profile", OPT_BOOL(check_hw_profile)}, + {"vd-lavc-software-fallback", OPT_CHOICE(software_fallback, + {"no", INT_MAX}, {"yes", 1}), M_RANGE(1, INT_MAX)}, + {"vd-lavc-o", OPT_KEYVALUELIST(avopts)}, + {"vd-lavc-dr", OPT_CHOICE(dr, + {"auto", -1}, {"no", 0}, {"yes", 1})}, + {"vd-apply-cropping", OPT_BOOL(apply_cropping)}, + {"hwdec", OPT_STRINGLIST(hwdec_api), + .help = hwdec_opt_help, + .flags = M_OPT_OPTIONAL_PARAM | UPDATE_HWDEC}, + {"hwdec-codecs", OPT_STRING(hwdec_codecs)}, + {"hwdec-image-format", OPT_IMAGEFORMAT(hwdec_image_format)}, + {"hwdec-extra-frames", OPT_INT(hwdec_extra_frames), M_RANGE(0, 256)}, + {0} + }, + .size = sizeof(struct vd_lavc_params), + .defaults = &(const struct vd_lavc_params){ + .film_grain = -1 /*auto*/, + .check_hw_profile = true, + .software_fallback = 3, + .skip_loop_filter = AVDISCARD_DEFAULT, + .skip_idct = AVDISCARD_DEFAULT, + .skip_frame = AVDISCARD_DEFAULT, + .framedrop = AVDISCARD_NONREF, + .dr = -1, + .hwdec_api = (char *[]){"no", NULL,}, + .hwdec_codecs = "h264,vc1,hevc,vp8,vp9,av1,prores", + // Maximum number of surfaces the player wants to buffer. This number + // might require adjustment depending on whatever the player does; + // for example, if vo_gpu increases the number of reference surfaces for + // interpolation, this value has to be increased too. + .hwdec_extra_frames = 6, + .apply_cropping = true, + }, +}; + +struct hwdec_info { + char name[64]; + char method_name[24]; // non-unique name describing the hwdec method + const AVCodec *codec; // implemented by this codec + enum AVHWDeviceType lavc_device; // if not NONE, get a hwdevice + bool copying; // if true, outputs sw frames, or copy to sw ourselves + enum AVPixelFormat pix_fmt; // if not NONE, select in get_format + bool use_hw_frames; // set AVCodecContext.hw_frames_ctx + bool use_hw_device; // set AVCodecContext.hw_device_ctx + unsigned int flags; // HWDEC_FLAG_* + + // for internal sorting + int auto_pos; + int rank; +}; + +typedef struct lavc_ctx { + struct mp_log *log; + struct m_config_cache *opts_cache; + struct vd_lavc_params *opts; + struct mp_codec_params *codec; + AVCodecContext *avctx; + AVFrame *pic; + AVPacket *avpkt; + bool use_hwdec; + struct hwdec_info hwdec; // valid only if use_hwdec==true + bstr *attempted_hwdecs; + int num_attempted_hwdecs; + AVRational codec_timebase; + enum AVDiscard skip_frame; + bool flushing; + struct lavc_state state; + const char *decoder; + bool hwdec_failed; + bool hwdec_notified; + bool force_eof; + + bool intra_only; + int framedrop_flags; + + bool hw_probing; + struct demux_packet **sent_packets; + int num_sent_packets; + + struct demux_packet **requeue_packets; + int num_requeue_packets; + + struct mp_image **delay_queue; + int num_delay_queue; + int max_delay_queue; + + // From VO + struct vo *vo; + struct mp_hwdec_devices *hwdec_devs; + + // Wrapped AVHWDeviceContext* used for decoding. + AVBufferRef *hwdec_dev; + + bool hwdec_request_reinit; + int hwdec_fail_count; + + struct mp_image_pool *hwdec_swpool; + + AVBufferRef *cached_hw_frames_ctx; + + // --- The following fields are protected by dr_lock. + mp_mutex dr_lock; + bool dr_failed; + struct mp_image_pool *dr_pool; + int dr_imgfmt, dr_w, dr_h, dr_stride_align; + + struct mp_decoder public; +} vd_ffmpeg_ctx; + +enum { + HWDEC_FLAG_AUTO = (1 << 0), // prioritize in autoprobe order + HWDEC_FLAG_WHITELIST = (1 << 1), // whitelist for auto-safe +}; + +struct autoprobe_info { + const char *method_name; + unsigned int flags; // HWDEC_FLAG_* +}; + +// Things not included in this list will be tried last, in random order. +const struct autoprobe_info hwdec_autoprobe_info[] = { + {"d3d11va", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"dxva2", HWDEC_FLAG_AUTO}, + {"d3d11va-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"dxva2-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"nvdec", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"nvdec-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"vaapi", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"vaapi-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"vdpau", HWDEC_FLAG_AUTO}, + {"vdpau-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"drm", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"drm-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"mmal", HWDEC_FLAG_AUTO}, + {"mmal-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"mediacodec", HWDEC_FLAG_AUTO}, + {"mediacodec-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"videotoolbox", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {"videotoolbox-copy", HWDEC_FLAG_AUTO | HWDEC_FLAG_WHITELIST}, + {0} +}; + +static int hwdec_compare(const void *p1, const void *p2) +{ + struct hwdec_info *h1 = (void *)p1; + struct hwdec_info *h2 = (void *)p2; + + if (h1 == h2) + return 0; + + // Strictly put non-preferred hwdecs to the end of the list. + if ((h1->auto_pos == INT_MAX) != (h2->auto_pos == INT_MAX)) + return h1->auto_pos == INT_MAX ? 1 : -1; + // List non-copying entries first, so --hwdec=auto takes them. + if (h1->copying != h2->copying) + return h1->copying ? 1 : -1; + // Order by autoprobe preference order. + if (h1->auto_pos != h2->auto_pos) + return h1->auto_pos > h2->auto_pos ? 1 : -1; + // Put hwdecs without hw_device_ctx last + if ((!!h1->lavc_device) != (!!h2->lavc_device)) + return h1->lavc_device ? -1 : 1; + // Fallback sort order to make sorting stable. + return h1->rank > h2->rank ? 1 :-1; +} + +// (This takes care of some bookkeeping too, like setting info.name) +static void add_hwdec_item(struct hwdec_info **infos, int *num_infos, + struct hwdec_info info) +{ + if (info.copying) + mp_snprintf_cat(info.method_name, sizeof(info.method_name), "-copy"); + + // (Including the codec name in case this is a wrapper looks pretty dumb, + // but better not have them clash with hwaccels and others.) + snprintf(info.name, sizeof(info.name), "%s-%s", + info.codec->name, info.method_name); + + info.rank = *num_infos; + info.auto_pos = INT_MAX; + + for (int x = 0; hwdec_autoprobe_info[x].method_name; x++) { + const struct autoprobe_info *entry = &hwdec_autoprobe_info[x]; + if (strcmp(entry->method_name, info.method_name) == 0) { + info.flags |= entry->flags; + if (info.flags & HWDEC_FLAG_AUTO) + info.auto_pos = x; + } + } + + MP_TARRAY_APPEND(NULL, *infos, *num_infos, info); +} + +static void add_all_hwdec_methods(struct hwdec_info **infos, int *num_infos) +{ + const AVCodec *codec = NULL; + void *iter = NULL; + while (1) { + codec = av_codec_iterate(&iter); + if (!codec) + break; + if (codec->type != AVMEDIA_TYPE_VIDEO || !av_codec_is_decoder(codec)) + continue; + + struct hwdec_info info_template = { + .pix_fmt = AV_PIX_FMT_NONE, + .codec = codec, + }; + + const char *wrapper = NULL; + if (codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID)) + wrapper = codec->wrapper_name; + + // A decoder can provide multiple methods. In particular, hwaccels + // provide various methods (e.g. native h264 with vaapi & d3d11), but + // even wrapper decoders could provide multiple methods. + bool found_any = false; + for (int n = 0; ; n++) { + const AVCodecHWConfig *cfg = avcodec_get_hw_config(codec, n); + if (!cfg) + break; + + if ((cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) || + (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) + { + struct hwdec_info info = info_template; + info.lavc_device = cfg->device_type; + info.pix_fmt = cfg->pix_fmt; + + const char *name = av_hwdevice_get_type_name(cfg->device_type); + assert(name); // API violation by libavcodec + + // nvdec hwaccels and the cuvid full decoder clash with their + // naming, so fix it here; we also prefer nvdec for the hwaccel. + if (strcmp(name, "cuda") == 0 && !wrapper) + name = "nvdec"; + + snprintf(info.method_name, sizeof(info.method_name), "%s", name); + + // Usually we want to prefer using hw_frames_ctx for true + // hwaccels only, but we actually don't have any way to detect + // those, so always use hw_frames_ctx if offered. + if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) { + info.use_hw_frames = true; + } else { + info.use_hw_device = true; + } + + // Direct variant. + add_hwdec_item(infos, num_infos, info); + + // Copy variant. + info.copying = true; + if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) { + info.use_hw_frames = false; + info.use_hw_device = true; + } + add_hwdec_item(infos, num_infos, info); + + found_any = true; + } else if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_INTERNAL) { + struct hwdec_info info = info_template; + info.pix_fmt = cfg->pix_fmt; + + const char *name = wrapper; + if (!name) + name = av_get_pix_fmt_name(info.pix_fmt); + assert(name); // API violation by libavcodec + + snprintf(info.method_name, sizeof(info.method_name), "%s", name); + + // Direct variant. + add_hwdec_item(infos, num_infos, info); + + // Copy variant. + info.copying = true; + info.pix_fmt = AV_PIX_FMT_NONE; // trust it can do sw output + add_hwdec_item(infos, num_infos, info); + + found_any = true; + } + } + + if (!found_any && wrapper) { + // We _know_ there's something supported here, usually outputting + // sw surfaces. E.g. mediacodec (before hw_device_ctx support). + + struct hwdec_info info = info_template; + info.copying = true; // probably + + snprintf(info.method_name, sizeof(info.method_name), "%s", wrapper); + add_hwdec_item(infos, num_infos, info); + } + } + + qsort(*infos, *num_infos, sizeof(struct hwdec_info), hwdec_compare); +} + +static bool hwdec_codec_allowed(struct mp_filter *vd, const char *codec) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + bstr s = bstr0(ctx->opts->hwdec_codecs); + while (s.len) { + bstr item; + bstr_split_tok(s, ",", &item, &s); + if (bstr_equals0(item, "all") || bstr_equals0(item, codec)) + return true; + } + return false; +} + +static AVBufferRef *hwdec_create_dev(struct mp_filter *vd, + struct hwdec_info *hwdec, + bool autoprobe) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + assert(hwdec->lavc_device); + + if (hwdec->copying) { + const struct hwcontext_fns *fns = + hwdec_get_hwcontext_fns(hwdec->lavc_device); + if (fns && fns->create_dev) { + struct hwcontext_create_dev_params params = { + .probing = autoprobe, + }; + return fns->create_dev(vd->global, vd->log, ¶ms); + } else { + AVBufferRef* ref = NULL; + av_hwdevice_ctx_create(&ref, hwdec->lavc_device, NULL, NULL, 0); + return ref; + } + } else if (ctx->hwdec_devs) { + int imgfmt = pixfmt2imgfmt(hwdec->pix_fmt); + struct hwdec_imgfmt_request params = { + .imgfmt = imgfmt, + .probing = autoprobe, + }; + hwdec_devices_request_for_img_fmt(ctx->hwdec_devs, ¶ms); + + const struct mp_hwdec_ctx *hw_ctx = + hwdec_devices_get_by_imgfmt(ctx->hwdec_devs, imgfmt); + + if (hw_ctx && hw_ctx->av_device_ref) + return av_buffer_ref(hw_ctx->av_device_ref); + } + + return NULL; +} + +// Select if and which hwdec to use. Also makes sure to get the decode device. +static void select_and_set_hwdec(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + const char *codec = ctx->codec->codec; + + m_config_cache_update(ctx->opts_cache); + + struct hwdec_info *hwdecs = NULL; + int num_hwdecs = 0; + add_all_hwdec_methods(&hwdecs, &num_hwdecs); + + char **hwdec_api = ctx->opts->hwdec_api; + for (int i = 0; hwdec_api[i]; i++) { + bstr opt = bstr0(hwdec_api[i]); + + bool hwdec_requested = !bstr_equals0(opt, "no"); + bool hwdec_auto_all = bstr_equals0(opt, "auto") || + bstr_equals0(opt, ""); + bool hwdec_auto_safe = bstr_equals0(opt, "auto-safe") || + bstr_equals0(opt, "auto-copy-safe") || + bstr_equals0(opt, "yes"); + bool hwdec_auto_copy = bstr_equals0(opt, "auto-copy") || + bstr_equals0(opt, "auto-copy-safe"); + bool hwdec_auto = hwdec_auto_all || hwdec_auto_copy || hwdec_auto_safe; + + if (!hwdec_requested) { + MP_VERBOSE(vd, "No hardware decoding requested.\n"); + break; + } else if (!hwdec_codec_allowed(vd, codec)) { + MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not " + "on whitelist.\n", codec); + break; + } else { + bool hwdec_name_supported = false; // relevant only if !hwdec_auto + for (int n = 0; n < num_hwdecs; n++) { + struct hwdec_info *hwdec = &hwdecs[n]; + + if (!hwdec_auto && !(bstr_equals0(opt, hwdec->method_name) || + bstr_equals0(opt, hwdec->name))) + continue; + hwdec_name_supported = true; + + bool already_attempted = false; + for (int j = 0; j < ctx->num_attempted_hwdecs; j++) { + if (bstr_equals0(ctx->attempted_hwdecs[j], hwdec->name)) { + MP_DBG(vd, "Skipping previously attempted hwdec: %s\n", + hwdec->name); + already_attempted = true; + break; + } + } + if (already_attempted) + continue; + + const char *hw_codec = mp_codec_from_av_codec_id(hwdec->codec->id); + if (!hw_codec || strcmp(hw_codec, codec) != 0) + continue; + + if (hwdec_auto_safe && !(hwdec->flags & HWDEC_FLAG_WHITELIST)) + continue; + + MP_VERBOSE(vd, "Looking at hwdec %s...\n", hwdec->name); + + /* + * Past this point, any kind of failure that results in us + * looking for a new hwdec should not lead to use trying this + * hwdec again - so add it to the list, regardless of whether + * initialisation will succeed or not. + */ + MP_TARRAY_APPEND(ctx, ctx->attempted_hwdecs, + ctx->num_attempted_hwdecs, + bstrdup(ctx, bstr0(hwdec->name))); + + if (hwdec_auto_copy && !hwdec->copying) { + MP_VERBOSE(vd, "Not using this for auto-copy.\n"); + continue; + } + + if (hwdec->lavc_device) { + ctx->hwdec_dev = hwdec_create_dev(vd, hwdec, hwdec_auto); + if (!ctx->hwdec_dev) { + MP_VERBOSE(vd, "Could not create device.\n"); + continue; + } + + const struct hwcontext_fns *fns = + hwdec_get_hwcontext_fns(hwdec->lavc_device); + if (fns && fns->is_emulated && fns->is_emulated(ctx->hwdec_dev)) { + if (hwdec_auto) { + MP_VERBOSE(vd, "Not using emulated API.\n"); + av_buffer_unref(&ctx->hwdec_dev); + continue; + } + MP_WARN(vd, "Using emulated hardware decoding API.\n"); + } + } else if (!hwdec->copying) { + // Most likely METHOD_INTERNAL, which often use delay-loaded + // VO support as well. + if (ctx->hwdec_devs) { + struct hwdec_imgfmt_request params = { + .imgfmt = pixfmt2imgfmt(hwdec->pix_fmt), + .probing = hwdec_auto, + }; + hwdec_devices_request_for_img_fmt( + ctx->hwdec_devs, ¶ms); + } + } + + ctx->use_hwdec = true; + ctx->hwdec = *hwdec; + break; + } + if (ctx->use_hwdec) + break; + else if (!hwdec_auto && !hwdec_name_supported) + MP_WARN(vd, "Unsupported hwdec: %.*s\n", BSTR_P(opt)); + } + } + talloc_free(hwdecs); + + + if (ctx->use_hwdec) { + MP_VERBOSE(vd, "Trying hardware decoding via %s.\n", ctx->hwdec.name); + if (strcmp(ctx->decoder, ctx->hwdec.codec->name) != 0) + MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n", + ctx->hwdec.codec->name); + } else { + // If software fallback is disabled and we get here, all hwdec must + // have failed. Tell the ctx to always force an eof. + if (ctx->opts->software_fallback == INT_MAX) { + MP_WARN(ctx, "Software decoding fallback is disabled.\n"); + ctx->force_eof = true; + } else { + MP_VERBOSE(vd, "Using software decoding.\n"); + } + } +} + +static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt, + struct bstr name) +{ + struct hwdec_info *hwdecs = NULL; + int num_hwdecs = 0; + add_all_hwdec_methods(&hwdecs, &num_hwdecs); + + mp_info(log, "Valid values (with alternative full names):\n"); + + for (int n = 0; n < num_hwdecs; n++) { + struct hwdec_info *hwdec = &hwdecs[n]; + + mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name); + } + + talloc_free(hwdecs); + + mp_info(log, " auto (yes '')\n"); + mp_info(log, " no\n"); + mp_info(log, " auto-safe\n"); + mp_info(log, " auto-copy\n"); + mp_info(log, " auto-copy-safe\n"); + + return M_OPT_EXIT; +} + +static void force_fallback(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + uninit_avctx(vd); + int lev = ctx->hwdec_notified ? MSGL_WARN : MSGL_V; + mp_msg(vd->log, lev, "Attempting next decoding method after failure of %.*s.\n", + BSTR_P(ctx->attempted_hwdecs[ctx->num_attempted_hwdecs - 1])); + select_and_set_hwdec(vd); + init_avctx(vd); +} + +static void reinit(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + uninit_avctx(vd); + + /* + * Reset attempted hwdecs so that if the hwdec list is reconfigured + * we attempt all of them from the beginning. The most practical + * reason for this is that ctrl+h toggles between `no` and + * `auto-safe`, and we want to reevaluate from a clean slate each time. + */ + TA_FREEP(&ctx->attempted_hwdecs); + ctx->num_attempted_hwdecs = 0; + ctx->hwdec_notified = false; + + select_and_set_hwdec(vd); + + bool use_hwdec = ctx->use_hwdec; + init_avctx(vd); + if (!ctx->avctx && use_hwdec) { + do { + force_fallback(vd); + } while (!ctx->avctx); + } +} + +static void init_avctx(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + struct vd_lavc_params *lavc_param = ctx->opts; + struct mp_codec_params *c = ctx->codec; + + m_config_cache_update(ctx->opts_cache); + + assert(!ctx->avctx); + + const AVCodec *lavc_codec = NULL; + + if (ctx->use_hwdec) { + lavc_codec = ctx->hwdec.codec; + } else { + lavc_codec = avcodec_find_decoder_by_name(ctx->decoder); + } + if (!lavc_codec) + return; + + const AVCodecDescriptor *desc = avcodec_descriptor_get(lavc_codec->id); + ctx->intra_only = desc && (desc->props & AV_CODEC_PROP_INTRA_ONLY); + + ctx->codec_timebase = mp_get_codec_timebase(ctx->codec); + + // This decoder does not read pkt_timebase correctly yet. + if (strstr(lavc_codec->name, "_mmal")) + ctx->codec_timebase = (AVRational){1, 1000000}; + + ctx->hwdec_failed = false; + ctx->hwdec_request_reinit = false; + ctx->avctx = avcodec_alloc_context3(lavc_codec); + AVCodecContext *avctx = ctx->avctx; + if (!ctx->avctx) + goto error; + avctx->codec_type = AVMEDIA_TYPE_VIDEO; + avctx->codec_id = lavc_codec->id; + avctx->pkt_timebase = ctx->codec_timebase; + + ctx->pic = av_frame_alloc(); + if (!ctx->pic) + goto error; + + ctx->avpkt = av_packet_alloc(); + if (!ctx->avpkt) + goto error; + + if (ctx->use_hwdec) { + avctx->opaque = vd; + avctx->thread_count = 1; + avctx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL; + if (!lavc_param->check_hw_profile) + avctx->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH; + +#ifdef AV_HWACCEL_FLAG_UNSAFE_OUTPUT + /* + * This flag primarily exists for nvdec which has a very limited + * output frame pool, which can get exhausted if consumers don't + * release frames quickly. However, as an implementation + * requirement, we have to copy the frames anyway, so we don't + * need this extra implicit copy. + */ + avctx->hwaccel_flags |= AV_HWACCEL_FLAG_UNSAFE_OUTPUT; +#endif + + if (ctx->hwdec.use_hw_device) { + if (ctx->hwdec_dev) + avctx->hw_device_ctx = av_buffer_ref(ctx->hwdec_dev); + if (!avctx->hw_device_ctx) + goto error; + } + if (ctx->hwdec.use_hw_frames) { + if (!ctx->hwdec_dev) + goto error; + } + + if (ctx->hwdec.pix_fmt != AV_PIX_FMT_NONE) + avctx->get_format = get_format_hwdec; + + // Some APIs benefit from this, for others it's additional bloat. + if (ctx->hwdec.copying) + ctx->max_delay_queue = HWDEC_DELAY_QUEUE_COUNT; + ctx->hw_probing = true; + } else { + mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads); + } + + if (!ctx->use_hwdec && ctx->vo && lavc_param->dr) { + avctx->opaque = vd; + avctx->get_buffer2 = get_buffer2_direct; +#if LIBAVCODEC_VERSION_MAJOR < 60 + AV_NOWARN_DEPRECATED({ + avctx->thread_safe_callbacks = 1; + }); +#endif + } + + avctx->flags |= lavc_param->bitexact ? AV_CODEC_FLAG_BITEXACT : 0; + avctx->flags2 |= lavc_param->fast ? AV_CODEC_FLAG2_FAST : 0; + + if (lavc_param->show_all) + avctx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT; + + avctx->skip_loop_filter = lavc_param->skip_loop_filter; + avctx->skip_idct = lavc_param->skip_idct; + avctx->skip_frame = lavc_param->skip_frame; + avctx->apply_cropping = lavc_param->apply_cropping; + + if (lavc_codec->id == AV_CODEC_ID_H264 && lavc_param->old_x264) + av_opt_set(avctx, "x264_build", "150", AV_OPT_SEARCH_CHILDREN); + +#ifndef AV_CODEC_EXPORT_DATA_FILM_GRAIN + if (ctx->opts->film_grain == 1) + MP_WARN(vd, "GPU film grain requested, but FFmpeg too old to expose " + "film grain parameters. Please update to latest master, " + "or at least to release 4.4.\n"); +#else + switch(ctx->opts->film_grain) { + case 0: /*CPU*/ + // default lavc flags handle film grain within the decoder. + break; + case 1: /*GPU*/ + if (!ctx->vo || + (ctx->vo && !(ctx->vo->driver->caps & VO_CAP_FILM_GRAIN))) { + MP_MSG(vd, ctx->vo ? MSGL_WARN : MSGL_V, + "GPU film grain requested, but VO %s, expect wrong output.\n", + ctx->vo ? + "does not support applying film grain" : + "is not available at decoder initialization to verify support"); + } + + avctx->export_side_data |= AV_CODEC_EXPORT_DATA_FILM_GRAIN; + break; + default: + if (ctx->vo && (ctx->vo->driver->caps & VO_CAP_FILM_GRAIN)) + avctx->export_side_data |= AV_CODEC_EXPORT_DATA_FILM_GRAIN; + + break; + } +#endif + + mp_set_avopts(vd->log, avctx, lavc_param->avopts); + + // Do this after the above avopt handling in case it changes values + ctx->skip_frame = avctx->skip_frame; + + if (mp_set_avctx_codec_headers(avctx, c) < 0) { + MP_ERR(vd, "Could not set codec parameters.\n"); + goto error; + } + + /* open it */ + if (avcodec_open2(avctx, lavc_codec, NULL) < 0) + goto error; + + // Sometimes, the first packet contains information required for correct + // decoding of the rest of the stream. The only currently known case is the + // x264 build number (encoded in a SEI element), needed to enable a + // workaround for broken 4:4:4 streams produced by older x264 versions. + if (lavc_codec->id == AV_CODEC_ID_H264 && c->first_packet) { + mp_set_av_packet(ctx->avpkt, c->first_packet, &ctx->codec_timebase); + avcodec_send_packet(avctx, ctx->avpkt); + avcodec_receive_frame(avctx, ctx->pic); + av_frame_unref(ctx->pic); + avcodec_flush_buffers(ctx->avctx); + } + + return; + +error: + MP_ERR(vd, "Could not open codec.\n"); + uninit_avctx(vd); +} + +static void reset_avctx(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + if (ctx->avctx && avcodec_is_open(ctx->avctx)) + avcodec_flush_buffers(ctx->avctx); + ctx->flushing = false; + ctx->hwdec_request_reinit = false; +} + +static void flush_all(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + for (int n = 0; n < ctx->num_delay_queue; n++) + talloc_free(ctx->delay_queue[n]); + ctx->num_delay_queue = 0; + + for (int n = 0; n < ctx->num_sent_packets; n++) + talloc_free(ctx->sent_packets[n]); + ctx->num_sent_packets = 0; + + for (int n = 0; n < ctx->num_requeue_packets; n++) + talloc_free(ctx->requeue_packets[n]); + ctx->num_requeue_packets = 0; + + reset_avctx(vd); +} + +static void uninit_avctx(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + flush_all(vd); + av_frame_free(&ctx->pic); + mp_free_av_packet(&ctx->avpkt); + av_buffer_unref(&ctx->cached_hw_frames_ctx); + + avcodec_free_context(&ctx->avctx); + + av_buffer_unref(&ctx->hwdec_dev); + + ctx->hwdec_failed = false; + ctx->hwdec_fail_count = 0; + ctx->max_delay_queue = 0; + ctx->hw_probing = false; + ctx->hwdec = (struct hwdec_info){0}; + ctx->use_hwdec = false; +} + +static int init_generic_hwaccel(struct mp_filter *vd, enum AVPixelFormat hw_fmt) +{ + struct lavc_ctx *ctx = vd->priv; + AVBufferRef *new_frames_ctx = NULL; + + if (!ctx->hwdec.use_hw_frames) + return 0; + + if (!ctx->hwdec_dev) { + MP_ERR(ctx, "Missing device context.\n"); + goto error; + } + + if (avcodec_get_hw_frames_parameters(ctx->avctx, + ctx->hwdec_dev, hw_fmt, &new_frames_ctx) < 0) + { + MP_VERBOSE(ctx, "Hardware decoding of this stream is unsupported?\n"); + goto error; + } + + AVHWFramesContext *new_fctx = (void *)new_frames_ctx->data; + + if (ctx->opts->hwdec_image_format) + new_fctx->sw_format = imgfmt2pixfmt(ctx->opts->hwdec_image_format); + + // 1 surface is already included by libavcodec. The field is 0 if the + // hwaccel supports dynamic surface allocation. + if (new_fctx->initial_pool_size) + new_fctx->initial_pool_size += ctx->opts->hwdec_extra_frames - 1; + + const struct hwcontext_fns *fns = + hwdec_get_hwcontext_fns(new_fctx->device_ctx->type); + + if (fns && fns->refine_hwframes) + fns->refine_hwframes(new_frames_ctx); + + // We might be able to reuse a previously allocated frame pool. + if (ctx->cached_hw_frames_ctx) { + AVHWFramesContext *old_fctx = (void *)ctx->cached_hw_frames_ctx->data; + + if (new_fctx->format != old_fctx->format || + new_fctx->sw_format != old_fctx->sw_format || + new_fctx->width != old_fctx->width || + new_fctx->height != old_fctx->height || + new_fctx->initial_pool_size != old_fctx->initial_pool_size) + av_buffer_unref(&ctx->cached_hw_frames_ctx); + } + + if (!ctx->cached_hw_frames_ctx) { + if (av_hwframe_ctx_init(new_frames_ctx) < 0) { + MP_ERR(ctx, "Failed to allocate hw frames.\n"); + goto error; + } + + ctx->cached_hw_frames_ctx = new_frames_ctx; + new_frames_ctx = NULL; + } + + ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->cached_hw_frames_ctx); + if (!ctx->avctx->hw_frames_ctx) + goto error; + + av_buffer_unref(&new_frames_ctx); + return 0; + +error: + av_buffer_unref(&new_frames_ctx); + av_buffer_unref(&ctx->cached_hw_frames_ctx); + return -1; +} + +static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx, + const enum AVPixelFormat *fmt) +{ + struct mp_filter *vd = avctx->opaque; + vd_ffmpeg_ctx *ctx = vd->priv; + + MP_VERBOSE(vd, "Pixel formats supported by decoder:"); + for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) + MP_VERBOSE(vd, " %s", av_get_pix_fmt_name(fmt[i])); + MP_VERBOSE(vd, "\n"); + + const char *profile = avcodec_profile_name(avctx->codec_id, avctx->profile); + MP_VERBOSE(vd, "Codec profile: %s (0x%x)\n", profile ? profile : "unknown", + avctx->profile); + + assert(ctx->use_hwdec); + + ctx->hwdec_request_reinit |= ctx->hwdec_failed; + ctx->hwdec_failed = false; + + enum AVPixelFormat select = AV_PIX_FMT_NONE; + for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) { + if (ctx->hwdec.pix_fmt == fmt[i]) { + if (init_generic_hwaccel(vd, fmt[i]) < 0) + break; + select = fmt[i]; + break; + } + } + + if (select == AV_PIX_FMT_NONE) { + ctx->hwdec_failed = true; + select = avcodec_default_get_format(avctx, fmt); + } + + const char *name = av_get_pix_fmt_name(select); + MP_VERBOSE(vd, "Requesting pixfmt '%s' from decoder.\n", name ? name : "-"); + return select; +} + +static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags) +{ + struct mp_filter *vd = avctx->opaque; + vd_ffmpeg_ctx *p = vd->priv; + + mp_mutex_lock(&p->dr_lock); + + int w = pic->width; + int h = pic->height; + int linesize_align[AV_NUM_DATA_POINTERS] = {0}; + avcodec_align_dimensions2(avctx, &w, &h, linesize_align); + + // We assume that different alignments are just different power-of-2s. + // Thus, a higher alignment always satisfies a lower alignment. + int stride_align = MP_IMAGE_BYTE_ALIGN; + for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) + stride_align = MPMAX(stride_align, linesize_align[n]); + + // Note: texel sizes may be NPOT, so use full lcm instead of max + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pic->format); + if (!(desc->flags & AV_PIX_FMT_FLAG_BITSTREAM)) { + for (int n = 0; n < desc->nb_components; n++) + stride_align = mp_lcm(stride_align, desc->comp[n].step); + } + + int imgfmt = pixfmt2imgfmt(pic->format); + if (!imgfmt) + goto fallback; + + if (p->dr_failed) + goto fallback; + + // (For simplicity, we realloc on any parameter change, instead of trying + // to be clever.) + if (stride_align != p->dr_stride_align || w != p->dr_w || h != p->dr_h || + imgfmt != p->dr_imgfmt) + { + mp_image_pool_clear(p->dr_pool); + p->dr_imgfmt = imgfmt; + p->dr_w = w; + p->dr_h = h; + p->dr_stride_align = stride_align; + MP_DBG(p, "DR parameter change to %dx%d %s align=%d\n", w, h, + mp_imgfmt_to_name(imgfmt), stride_align); + } + + struct mp_image *img = mp_image_pool_get_no_alloc(p->dr_pool, imgfmt, w, h); + if (!img) { + bool host_cached = p->opts->dr == -1; // auto + int dr_flags = host_cached ? VO_DR_FLAG_HOST_CACHED : 0; + MP_DBG(p, "Allocating new%s DR image...\n", host_cached ? " (host-cached)" : ""); + img = vo_get_image(p->vo, imgfmt, w, h, stride_align, dr_flags); + if (!img) { + MP_DBG(p, "...failed..\n"); + goto fallback; + } + + // Now make the mp_image part of the pool. This requires doing magic to + // the image, so just add it to the pool and get it back to avoid + // dealing with magic ourselves. (Normally this never fails.) + mp_image_pool_add(p->dr_pool, img); + img = mp_image_pool_get_no_alloc(p->dr_pool, imgfmt, w, h); + if (!img) + goto fallback; + } + + // get_buffer2 callers seem very unappreciative of overwriting pic with a + // new reference. The AVCodecContext.get_buffer2 comments tell us exactly + // what we should do, so follow that. + for (int n = 0; n < 4; n++) { + pic->data[n] = img->planes[n]; + pic->linesize[n] = img->stride[n]; + pic->buf[n] = img->bufs[n]; + img->bufs[n] = NULL; + } + talloc_free(img); + + mp_mutex_unlock(&p->dr_lock); + + return 0; + +fallback: + if (!p->dr_failed) + MP_VERBOSE(p, "DR failed - disabling.\n"); + p->dr_failed = true; + mp_mutex_unlock(&p->dr_lock); + + return avcodec_default_get_buffer2(avctx, pic, flags); +} + +static void prepare_decoding(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + AVCodecContext *avctx = ctx->avctx; + struct vd_lavc_params *opts = ctx->opts; + + if (!avctx) + return; + + int drop = ctx->framedrop_flags; + if (drop == 1) { + avctx->skip_frame = opts->framedrop; // normal framedrop + } else if (drop == 2) { + avctx->skip_frame = AVDISCARD_NONREF; // hr-seek framedrop + // Can be much more aggressive for true intra codecs. + if (ctx->intra_only) + avctx->skip_frame = AVDISCARD_ALL; + } else { + avctx->skip_frame = ctx->skip_frame; // normal playback + } + + if (ctx->hwdec_request_reinit) + reset_avctx(vd); +} + +static void handle_err(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + struct vd_lavc_params *opts = ctx->opts; + + MP_WARN(vd, "Error while decoding frame%s!\n", + ctx->use_hwdec ? " (hardware decoding)" : ""); + + if (ctx->use_hwdec) { + ctx->hwdec_fail_count += 1; + if (ctx->hwdec_fail_count >= opts->software_fallback) + ctx->hwdec_failed = true; + } +} + +static int send_packet(struct mp_filter *vd, struct demux_packet *pkt) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + AVCodecContext *avctx = ctx->avctx; + + if (ctx->num_requeue_packets && ctx->requeue_packets[0] != pkt) + return AVERROR(EAGAIN); // cannot consume the packet + + if (ctx->hwdec_failed) + return AVERROR(EAGAIN); + + if (!ctx->avctx) + return AVERROR_EOF; + + prepare_decoding(vd); + + if (avctx->skip_frame == AVDISCARD_ALL) + return 0; + + mp_set_av_packet(ctx->avpkt, pkt, &ctx->codec_timebase); + + int ret = avcodec_send_packet(avctx, pkt ? ctx->avpkt : NULL); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return ret; + + if (ctx->hw_probing && ctx->num_sent_packets < 32 && + ctx->opts->software_fallback <= 32) + { + pkt = pkt ? demux_copy_packet(pkt) : NULL; + MP_TARRAY_APPEND(ctx, ctx->sent_packets, ctx->num_sent_packets, pkt); + } + + if (ret < 0) + handle_err(vd); + return ret; +} + +static void send_queued_packet(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + assert(ctx->num_requeue_packets); + + if (send_packet(vd, ctx->requeue_packets[0]) != AVERROR(EAGAIN)) { + talloc_free(ctx->requeue_packets[0]); + MP_TARRAY_REMOVE_AT(ctx->requeue_packets, ctx->num_requeue_packets, 0); + } +} + +// Returns whether decoder is still active (!EOF state). +static int decode_frame(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + AVCodecContext *avctx = ctx->avctx; + + if (!avctx || ctx->force_eof) + return AVERROR_EOF; + + prepare_decoding(vd); + + // Re-send old packets (typically after a hwdec fallback during init). + if (ctx->num_requeue_packets) + send_queued_packet(vd); + + int ret = avcodec_receive_frame(avctx, ctx->pic); + if (ret < 0) { + if (ret == AVERROR_EOF) { + // If flushing was initialized earlier and has ended now, make it + // start over in case we get new packets at some point in the future. + // This must take the delay queue into account, so avctx returns EOF + // until the delay queue has been drained. + if (!ctx->num_delay_queue) + reset_avctx(vd); + } else if (ret == AVERROR(EAGAIN)) { + // just retry after caller writes a packet + } else { + handle_err(vd); + } + return ret; + } + + // If something was decoded successfully, it must return a frame with valid + // data. + assert(ctx->pic->buf[0]); + + struct mp_image *mpi = mp_image_from_av_frame(ctx->pic); + if (!mpi) { + av_frame_unref(ctx->pic); + return ret; + } + + if (mpi->imgfmt == IMGFMT_CUDA && !mpi->planes[0]) { + MP_ERR(vd, "CUDA frame without data. This is a FFmpeg bug.\n"); + talloc_free(mpi); + handle_err(vd); + return AVERROR_BUG; + } + + ctx->hwdec_fail_count = 0; + + mpi->pts = mp_pts_from_av(ctx->pic->pts, &ctx->codec_timebase); + mpi->dts = mp_pts_from_av(ctx->pic->pkt_dts, &ctx->codec_timebase); + + mpi->pkt_duration = +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 30, 100) + mp_pts_from_av(ctx->pic->duration, &ctx->codec_timebase); +#else + mp_pts_from_av(ctx->pic->pkt_duration, &ctx->codec_timebase); +#endif + + av_frame_unref(ctx->pic); + + MP_TARRAY_APPEND(ctx, ctx->delay_queue, ctx->num_delay_queue, mpi); + return ret; +} + +static int receive_frame(struct mp_filter *vd, struct mp_frame *out_frame) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + int ret = decode_frame(vd); + + if (ctx->hwdec_failed) { + // Failed hardware decoding? Try the next one, and eventually software. + struct demux_packet **pkts = ctx->sent_packets; + int num_pkts = ctx->num_sent_packets; + ctx->sent_packets = NULL; + ctx->num_sent_packets = 0; + + /* + * We repeatedly force_fallback until we get an avctx, because there are + * certain hwdecs that are really full decoders, and so if these fail, + * they also fail to give us a valid avctx, and the early return path + * here will simply give up on decoding completely if there is no + * decoder. We should never hit an infinite loop as the hwdec list is + * finite and we will eventually exhaust it and fall back to software + * decoding (and in practice, most hwdecs are hwaccels and so the + * decoder will successfully init even if the hwaccel fails later.) + */ + do { + force_fallback(vd); + } while (!ctx->avctx); + + ctx->requeue_packets = pkts; + ctx->num_requeue_packets = num_pkts; + + return 0; // force retry + } + + if (ret == AVERROR(EAGAIN) && ctx->num_requeue_packets) + return 0; // force retry, so send_queued_packet() gets called + + if (ctx->num_delay_queue <= ctx->max_delay_queue && ret != AVERROR_EOF) + return ret; + + if (!ctx->num_delay_queue) + return ret; + + struct mp_image *res = ctx->delay_queue[0]; + MP_TARRAY_REMOVE_AT(ctx->delay_queue, ctx->num_delay_queue, 0); + + res = res ? mp_img_swap_to_native(res) : NULL; + if (!res) + return AVERROR_UNKNOWN; + + if (ctx->use_hwdec && ctx->hwdec.copying && res->hwctx) { + struct mp_image *sw = mp_image_hw_download(res, ctx->hwdec_swpool); + mp_image_unrefp(&res); + res = sw; + if (!res) { + MP_ERR(vd, "Could not copy back hardware decoded frame.\n"); + ctx->hwdec_fail_count = INT_MAX - 1; // force fallback + handle_err(vd); + return AVERROR_UNKNOWN; + } + } + + if (!ctx->hwdec_notified) { + if (ctx->use_hwdec) { + MP_INFO(vd, "Using hardware decoding (%s).\n", + ctx->hwdec.method_name); + } else { + MP_VERBOSE(vd, "Using software decoding.\n"); + } + ctx->hwdec_notified = true; + } + + if (ctx->hw_probing) { + for (int n = 0; n < ctx->num_sent_packets; n++) + talloc_free(ctx->sent_packets[n]); + ctx->num_sent_packets = 0; + ctx->hw_probing = false; + } + + *out_frame = MAKE_FRAME(MP_FRAME_VIDEO, res); + return 0; +} + +static int control(struct mp_filter *vd, enum dec_ctrl cmd, void *arg) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + switch (cmd) { + case VDCTRL_SET_FRAMEDROP: + ctx->framedrop_flags = *(int *)arg; + return CONTROL_TRUE; + case VDCTRL_CHECK_FORCED_EOF: { + *(bool *)arg = ctx->force_eof; + return CONTROL_TRUE; + } + case VDCTRL_GET_BFRAMES: { + AVCodecContext *avctx = ctx->avctx; + if (!avctx) + break; + if (ctx->use_hwdec && strcmp(ctx->hwdec.method_name, "mmal") == 0) + break; // MMAL has arbitrary buffering, thus unknown + *(int *)arg = avctx->has_b_frames; + return CONTROL_TRUE; + } + case VDCTRL_GET_HWDEC: { + *(char **)arg = ctx->use_hwdec ? ctx->hwdec.method_name : NULL; + return CONTROL_TRUE; + } + case VDCTRL_FORCE_HWDEC_FALLBACK: + if (ctx->use_hwdec) { + force_fallback(vd); + return ctx->avctx ? CONTROL_OK : CONTROL_ERROR; + } + return CONTROL_FALSE; + case VDCTRL_REINIT: + reinit(vd); + return CONTROL_TRUE; + } + return CONTROL_UNKNOWN; +} + +static void process(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + lavc_process(vd, &ctx->state, send_packet, receive_frame); +} + +static void reset(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + flush_all(vd); + + ctx->state = (struct lavc_state){0}; + ctx->framedrop_flags = 0; +} + +static void destroy(struct mp_filter *vd) +{ + vd_ffmpeg_ctx *ctx = vd->priv; + + uninit_avctx(vd); + + mp_mutex_destroy(&ctx->dr_lock); +} + +static const struct mp_filter_info vd_lavc_filter = { + .name = "vd_lavc", + .priv_size = sizeof(vd_ffmpeg_ctx), + .process = process, + .reset = reset, + .destroy = destroy, +}; + +static struct mp_decoder *create(struct mp_filter *parent, + struct mp_codec_params *codec, + const char *decoder) +{ + struct mp_filter *vd = mp_filter_create(parent, &vd_lavc_filter); + if (!vd) + return NULL; + + mp_filter_add_pin(vd, MP_PIN_IN, "in"); + mp_filter_add_pin(vd, MP_PIN_OUT, "out"); + + vd->log = mp_log_new(vd, parent->log, NULL); + + vd_ffmpeg_ctx *ctx = vd->priv; + ctx->log = vd->log; + ctx->opts_cache = m_config_cache_alloc(ctx, vd->global, &vd_lavc_conf); + ctx->opts = ctx->opts_cache->opts; + ctx->codec = codec; + ctx->decoder = talloc_strdup(ctx, decoder); + ctx->hwdec_swpool = mp_image_pool_new(ctx); + ctx->dr_pool = mp_image_pool_new(ctx); + + ctx->public.f = vd; + ctx->public.control = control; + + mp_mutex_init(&ctx->dr_lock); + + // hwdec/DR + struct mp_stream_info *info = mp_filter_find_stream_info(vd); + if (info) { + ctx->hwdec_devs = info->hwdec_devs; + ctx->vo = info->dr_vo; + } + + reinit(vd); + + if (!ctx->avctx) { + talloc_free(vd); + return NULL; + } + return &ctx->public; +} + +static void add_decoders(struct mp_decoder_list *list) +{ + mp_add_lavc_decoders(list, AVMEDIA_TYPE_VIDEO); +} + +const struct mp_decoder_fns vd_lavc = { + .create = create, + .add_decoders = add_decoders, +}; |