diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c index 6040d6be3add1..11303e385d63c 100644 --- a/filters/f_auto_filters.c +++ b/filters/f_auto_filters.c @@ -63,12 +63,15 @@ static void deint_process(struct mp_filter *f) bool filter_needed = opts->deinterlace == 1 || (opts->deinterlace == -1 && (p->interlaced_frame || p->sub.filter)); - // If the image format changed, or if we no longer need a filter, - // destroy any existing filter. - if (img->imgfmt != p->prev_imgfmt || (p->sub.filter && !filter_needed)) { + // If the image format changed, destroy any existing filter immediately since + // it may not support the new format. If we no longer need a filter, drain + // and destroy it gracefully. + if (img->imgfmt != p->prev_imgfmt) { + mp_subfilter_destroy(&p->sub); + p->prev_imgfmt = img->imgfmt; + } else if (p->sub.filter && !filter_needed) { if (!mp_subfilter_drain_destroy(&p->sub)) return; - p->prev_imgfmt = img->imgfmt; } // If no filter is needed or if the filter is already inserted and we reach @@ -92,6 +95,7 @@ static void deint_process(struct mp_filter *f) field_parity = "auto"; } + struct mp_stream_info *info = mp_filter_find_stream_info(f); bool has_filter = true; if (img->imgfmt == IMGFMT_VDPAU) { char *args[] = {"deint", "yes", @@ -123,6 +127,11 @@ static void deint_process(struct mp_filter *f) "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args); + } else if (info && info->deinterlace && !IMGFMT_IS_HWACCEL(img->imgfmt)) { + char *args[] = {"interlaced-only", opts->deinterlace == 1 ? "no" : "yes", + "parity", field_parity, NULL}; + p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, + "fieldrate", args); } else { has_filter = false; } diff --git a/filters/f_output_chain.c b/filters/f_output_chain.c index 8ded23177da84..7c9ab99acf55c 100644 --- a/filters/f_output_chain.c +++ b/filters/f_output_chain.c @@ -387,6 +387,7 @@ void mp_output_chain_set_vo(struct mp_output_chain *c, struct vo *vo) p->stream_info.osd = vo ? vo->osd : NULL; p->stream_info.vflip = vo ? vo->driver->caps & VO_CAP_VFLIP : false; p->stream_info.rotate90 = vo ? vo->driver->caps & VO_CAP_ROTATE90 : false; + p->stream_info.deinterlace = vo ? vo->driver->caps & VO_CAP_DEINTERLACE : false; p->stream_info.dr_vo = vo; p->vo = vo; update_output_caps(p); diff --git a/filters/filter.h b/filters/filter.h index 5bf1e48a84958..334c46e64a024 100644 --- a/filters/filter.h +++ b/filters/filter.h @@ -408,6 +408,7 @@ struct mp_stream_info { bool vflip; bool rotate90; bool force_swdec; + bool deinterlace; struct vo *dr_vo; // for calling vo_get_image() }; diff --git a/filters/user_filters.c b/filters/user_filters.c index 7bdda6965369f..49b674111f6ff 100644 --- a/filters/user_filters.c +++ b/filters/user_filters.c @@ -106,6 +106,7 @@ const struct mp_user_filter_entry *vf_list[] = { #if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN &vf_gpu, #endif + &vf_fieldrate, }; static bool get_vf_desc(struct m_obj_desc *dst, int index) diff --git a/filters/user_filters.h b/filters/user_filters.h index aa88649b33dec..8698ba2c1a099 100644 --- a/filters/user_filters.h +++ b/filters/user_filters.h @@ -38,3 +38,4 @@ extern const struct mp_user_filter_entry vf_d3d11vpp; extern const struct mp_user_filter_entry vf_amf_frc; extern const struct mp_user_filter_entry vf_fingerprint; extern const struct mp_user_filter_entry vf_gpu; +extern const struct mp_user_filter_entry vf_fieldrate; diff --git a/meson.build b/meson.build index 1cd2570f0d7ef..1cd90648e64a7 100644 --- a/meson.build +++ b/meson.build @@ -217,6 +217,7 @@ sources = files( 'video/filter/refqueue.c', 'video/filter/vf_format.c', 'video/filter/vf_sub.c', + 'video/filter/vf_fieldrate.c', 'video/fmt-conversion.c', 'video/hwdec.c', 'video/image_loader.c', diff --git a/video/filter/vf_fieldrate.c b/video/filter/vf_fieldrate.c new file mode 100644 index 0000000000000..d2d66b30d5c54 --- /dev/null +++ b/video/filter/vf_fieldrate.c @@ -0,0 +1,107 @@ +#include "filters/filter_internal.h" +#include "filters/user_filters.h" +#include "refqueue.h" + +struct opts { + int field_parity; + bool interlaced_only; +}; + +struct priv { + struct opts *opts; + struct mp_refqueue *queue; +}; + +static void vf_field_process(struct mp_filter *f) +{ + struct priv *p = f->priv; + mp_refqueue_execute_reinit(p->queue); + + if (!mp_refqueue_can_output(p->queue)) + return; + + struct mp_image *in = mp_refqueue_get(p->queue, 0); + struct mp_image *out = mp_image_new_ref(in); + if (!out) { + mp_refqueue_write_out_pin(p->queue, NULL); + return; + } + // This filter does not deinterlace. It only emits one output per field so + // the VO gets called at fieldrate cadence. + if (mp_refqueue_should_deint(p->queue)) { + out->field_tick = mp_refqueue_is_second_field(p->queue) ? + MP_FIELD_TICK_SECOND : MP_FIELD_TICK_FIRST; + out->fields |= MP_IMGFIELD_INTERLACED; + if (mp_refqueue_top_field_first(p->queue)) { + out->fields |= MP_IMGFIELD_TOP_FIRST; + } else { + out->fields &= ~MP_IMGFIELD_TOP_FIRST; + } + } else { + out->field_tick = MP_FIELD_TICK_NONE; + } + mp_refqueue_write_out_pin(p->queue, out); +} + +static void vf_field_reset(struct mp_filter *f) +{ + struct priv *p = f->priv; + mp_refqueue_flush(p->queue); +} + +static void vf_field_destroy(struct mp_filter *f) +{ + struct priv *p = f->priv; + mp_refqueue_flush(p->queue); + talloc_free(p->queue); +} + +static const struct mp_filter_info vf_field_filter = { + .name = "fieldrate", + .process = vf_field_process, + .reset = vf_field_reset, + .destroy = vf_field_destroy, + .priv_size = sizeof(struct priv), +}; + +static struct mp_filter *vf_field_create(struct mp_filter *parent, void *options) +{ + struct mp_filter *f = mp_filter_create(parent, &vf_field_filter); + if (!f) + return NULL; + struct priv *p = f->priv; + p->opts = talloc_steal(p, options); + + mp_filter_add_pin(f, MP_PIN_IN, "in"); + mp_filter_add_pin(f, MP_PIN_OUT, "out"); + + p->queue = mp_refqueue_alloc(f); + + mp_refqueue_set_refs(p->queue, 0, 0); + mp_refqueue_set_mode(p->queue, + MP_MODE_DEINT | + MP_MODE_OUTPUT_FIELDS | + (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); + + return f; +} +#define OPT_BASE_STRUCT struct opts +static const m_option_t vf_opts_fields[] = { + {"interlaced-only", OPT_BOOL(interlaced_only)}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, + {0} +}; + +const struct mp_user_filter_entry vf_fieldrate = { + .desc = { + .description = "Emit one frame per field", + .name = "fieldrate", + .priv_size = sizeof(OPT_BASE_STRUCT), + .options = vf_opts_fields, + }, + .create = vf_field_create, +}; diff --git a/video/mp_image.h b/video/mp_image.h index 5fe523dd64e96..6ad78091e39a5 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -38,6 +38,12 @@ #define MP_IMGFIELD_REPEAT_FIRST 0x04 #define MP_IMGFIELD_INTERLACED 0x20 +enum mp_image_field_tick { + MP_FIELD_TICK_NONE = 0, + MP_FIELD_TICK_FIRST, + MP_FIELD_TICK_SECOND, +}; + // Describes image parameters that usually stay constant. // New fields can be added in the future. Code changing the parameters should // usually copy the whole struct, so that fields added later will be preserved. @@ -97,6 +103,8 @@ typedef struct mp_image { int pict_type; // 0->unknown, 1->I, 2->P, 3->B int fields; + enum mp_image_field_tick field_tick; + /* only inside filter chain */ double pts; /* only after decoder */ diff --git a/video/out/vo.h b/video/out/vo.h index a98b0f9496853..89afa9e6731b9 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -205,6 +205,8 @@ enum { VO_CAP_FRAMEOWNER = 1 << 5, // VO does handle mp_image_params.vflip VO_CAP_VFLIP = 1 << 6, + // VO supports deinterlacing + VO_CAP_DEINTERLACE = 1 << 7, }; enum { diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 308a9fb02f5b6..3731663c34adc 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -147,6 +147,7 @@ struct priv { pl_options pars; struct m_config_cache *opts_cache; struct m_config_cache *next_opts_cache; + struct m_config_cache *filter_opts_cache; struct gl_next_opts *next_opts; struct cache shader_cache, icc_cache; struct mp_csp_equalizer_state *video_eq; @@ -890,6 +891,7 @@ static void update_options(struct vo *vo) pl_options pars = p->pars; bool changed = m_config_cache_update(p->opts_cache); changed = m_config_cache_update(p->next_opts_cache) || changed; + changed = m_config_cache_update(p->filter_opts_cache) || changed; if (changed) update_render_options(vo); @@ -1143,13 +1145,29 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) mpi->priv = fp; fp->vo = vo; + bool use_fields = params.deinterlace_params && + mpi->fields & MP_IMGFIELD_INTERLACED && + !IMGFMT_IS_HWACCEL(mpi->imgfmt); + // vf_fieldrate emits a frame for each field only to make mpv render + // at the second field PTS. But don't actually push it to pl_queue, + // because it already derives it internally from first_field. + if (use_fields && mpi->field_tick == MP_FIELD_TICK_SECOND) { + talloc_free(mpi); + p->last_id = id; + continue; + } + int first_field = !use_fields ? PL_FIELD_NONE : + (mpi->fields & MP_IMGFIELD_TOP_FIRST) ? PL_FIELD_TOP : PL_FIELD_BOTTOM; + pl_queue_push(p->queue, &(struct pl_source_frame) { .pts = mpi->pts, - .duration = can_interpolate ? frame->approx_duration : 0, + .duration = can_interpolate ? frame->approx_duration : + use_fields ? mpi->pkt_duration : 0, .frame_data = mpi, .map = map_frame, .unmap = unmap_frame, .discard = discard_frame, + .first_field = first_field, }); p->last_id = id; @@ -2229,6 +2247,7 @@ static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->opts_cache = m_config_cache_alloc(p, vo->global, &gl_video_conf); + p->filter_opts_cache = m_config_cache_alloc(p, vo->global, &filter_conf); p->next_opts_cache = m_config_cache_alloc(p, vo->global, &gl_next_conf); p->next_opts = p->next_opts_cache->opts; p->video_eq = mp_csp_equalizer_create(p, vo->global); @@ -2559,6 +2578,7 @@ static void update_render_options(struct vo *vo) struct priv *p = vo->priv; pl_options pars = p->pars; const struct gl_video_opts *opts = p->opts_cache->opts; + const struct filter_opts *fopts = p->filter_opts_cache->opts; pars->params.background_color[0] = opts->background_color.r / 255.0; pars->params.background_color[1] = opts->background_color.g / 255.0; pars->params.background_color[2] = opts->background_color.b / 255.0; @@ -2592,6 +2612,9 @@ static void update_render_options(struct vo *vo) pars->params.plane_upscaler = map_scaler(p, SCALER_CSCALE); pars->params.frame_mixer = opts->interpolation ? map_scaler(p, SCALER_TSCALE) : NULL; + pars->params.deinterlace_params = fopts->deinterlace != 0 ? &pars->deinterlace_params : NULL; + pars->deinterlace_params.algo = PL_DEINTERLACE_BWDIF; + // Request as many frames as required from the decoder, depending on the // speed VPS/FPS ratio libplacebo may need more frames. Request frames up to // ratio of 1/2, but only if anti aliasing is enabled. @@ -2709,6 +2732,7 @@ const struct vo_driver video_out_gpu_next = { .caps = VO_CAP_ROTATE90 | VO_CAP_FILM_GRAIN | VO_CAP_VFLIP | + VO_CAP_DEINTERLACE | 0x0, .preinit = preinit, .query_format = query_format,