From 1f1c80941733ae8c9aca579436e1485fdf94887b Mon Sep 17 00:00:00 2001 From: Daniel Campos Ramos Date: Mon, 21 Sep 2026 19:28:20 -0300 Subject: [PATCH 1/5] mp_image: read stereo 3D layout from AV_FRAME_DATA_STEREO3D MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only source of the stereo 3D layout was the Matroska StereoMode element read by demux_mkv. libavcodec attaches the layout signaled in the bitstream, such as the H.264 and HEVC frame packing arrangement SEI, or by the container, such as the MP4 st3d box, to the decoded frame as AV_FRAME_DATA_STEREO3D. Map it onto mp_image_params.stereo3d. Frames coming back from libavfilter keep mpv's own value restored from opaque_ref. Co-authored-by: Kacper Michajłow --- video/csputils.h | 12 ++++++++++-- video/mp_image.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/video/csputils.h b/video/csputils.h index 36e017b58568e..4ed18f1ab244f 100644 --- a/video/csputils.h +++ b/video/csputils.h @@ -49,13 +49,21 @@ extern const struct m_opt_choice_alternatives mp_csp_light_names[]; // The numeric values (except -1) match the Matroska StereoMode element value. enum mp_stereo3d_mode { MP_STEREO3D_INVALID = -1, - /* only modes explicitly referenced in the code are listed */ MP_STEREO3D_MONO = 0, MP_STEREO3D_SBS2L = 1, MP_STEREO3D_AB2R = 2, MP_STEREO3D_AB2L = 3, + MP_STEREO3D_CHECKR = 4, + MP_STEREO3D_CHECKL = 5, + MP_STEREO3D_IRR = 6, + MP_STEREO3D_IRL = 7, + MP_STEREO3D_ICR = 8, + MP_STEREO3D_ICL = 9, + MP_STEREO3D_ARCC = 10, MP_STEREO3D_SBS2R = 11, - /* no explicit enum entries for most valid values */ + MP_STEREO3D_AGMC = 12, + MP_STEREO3D_AL = 13, + MP_STEREO3D_AR = 14, MP_STEREO3D_COUNT = 15, // 14 is last valid mode }; diff --git a/video/mp_image.c b/video/mp_image.c index c0887c119be5b..5a5a84f69873a 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1079,6 +1080,29 @@ void mp_image_params_guess_csp(struct mp_image_params *params) } } +static enum mp_stereo3d_mode stereo3d_from_av(const AVStereo3D *s3d) +{ + bool inv = s3d->flags & AV_STEREO3D_FLAG_INVERT; + switch (s3d->type) { + case AV_STEREO3D_SIDEBYSIDE: + case AV_STEREO3D_SIDEBYSIDE_QUINCUNX: + return inv ? MP_STEREO3D_SBS2R : MP_STEREO3D_SBS2L; + case AV_STEREO3D_TOPBOTTOM: + return inv ? MP_STEREO3D_AB2R : MP_STEREO3D_AB2L; + case AV_STEREO3D_CHECKERBOARD: + return inv ? MP_STEREO3D_CHECKR : MP_STEREO3D_CHECKL; + case AV_STEREO3D_LINES: + return inv ? MP_STEREO3D_IRR : MP_STEREO3D_IRL; + case AV_STEREO3D_COLUMNS: + return inv ? MP_STEREO3D_ICR : MP_STEREO3D_ICL; + case AV_STEREO3D_FRAMESEQUENCE: + return inv ? MP_STEREO3D_AR : MP_STEREO3D_AL; + default: + // AV_STEREO3D_2D and types mpv has no mode for. + return MP_STEREO3D_MONO; + } +} + // Create a new mp_image reference to av_frame. struct mp_image *mp_image_from_av_frame(struct AVFrame *src) { @@ -1131,6 +1155,10 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) dst->params.chroma_location = pl_chroma_from_av(src->chroma_location); + sd = av_frame_get_side_data(src, AV_FRAME_DATA_STEREO3D); + if (sd) + dst->params.stereo3d = stereo3d_from_av((const AVStereo3D *)sd->data); + if (src->opaque_ref) { struct mp_image_params *p = (void *)src->opaque_ref->data; dst->params.stereo3d = p->stereo3d; From e2e62db109eedb42c627116e67fe73173546ffa4 Mon Sep 17 00:00:00 2001 From: Daniel Campos Ramos Date: Mon, 21 Sep 2026 19:29:06 -0300 Subject: [PATCH 2/5] f_decoder_wrapper: prefer bitstream stereo 3D layout over container tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix_image_params() unconditionally overwrote the layout with the container tag, so the value mp_image_from_av_frame() reads from AV_FRAME_DATA_STEREO3D never reached the output. Fall back to the tag only when the bitstream signals nothing, the same rule rotation already follows. Fixes: #18489 Co-authored-by: Kacper Michajłow --- filters/f_decoder_wrapper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c index 2b76b26b14bb7..2397c855fcae4 100644 --- a/filters/f_decoder_wrapper.c +++ b/filters/f_decoder_wrapper.c @@ -678,7 +678,8 @@ static void fix_image_params(struct priv *p, if (m.p_w <= 0 || m.p_h <= 0) m.p_w = m.p_h = 1; - m.stereo3d = p->codec->stereo_mode; + if (!m.stereo3d) + m.stereo3d = p->codec->stereo_mode; if (!mp_rect_equals(&p->codec->crop, &(struct mp_rect){0})) { struct mp_rect crop = p->codec->crop; From e951860b2705f00f4d258c1792cd5d2931e1320a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Wed, 23 Sep 2026 15:33:36 +0200 Subject: [PATCH 3/5] vf_format: make stereo-in=mono override the source mode set_params() treated 0 as unset, but 0 is mono, so stereo-in=mono was a no-op and the only way to drop a stereo 3D mode was stereo-in=no, which wrote the invalid value -1 into the image params. Use -1 as the unset default like rotate does, so no keeps the source mode and mono overrides it. --- DOCS/interface-changes/vf-format-stereo-in.txt | 1 + DOCS/man/vf.rst | 3 ++- video/csputils.c | 2 +- video/filter/vf_format.c | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 DOCS/interface-changes/vf-format-stereo-in.txt diff --git a/DOCS/interface-changes/vf-format-stereo-in.txt b/DOCS/interface-changes/vf-format-stereo-in.txt new file mode 100644 index 0000000000000..e1dae20eff173 --- /dev/null +++ b/DOCS/interface-changes/vf-format-stereo-in.txt @@ -0,0 +1 @@ +change `--vf=format:stereo-in=no` to keep the source stereo 3D mode, `--vf=format:stereo-in=mono` now overrides it with 2D diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 086295d40ca5d..ea560c8fd4d84 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -361,7 +361,8 @@ Available mpv-only filters are: ```` Set the stereo mode the video is assumed to be encoded in. Use ``--vf=format:stereo-in=help`` to list all available modes. Check with - the ``stereo3d`` filter documentation to see what the names mean. + the ``stereo3d`` filter documentation to see what the names mean. The + default ``no`` keeps the mode signaled by the source. ```` Set the rotation the video is assumed to be encoded with in degrees. diff --git a/video/csputils.c b/video/csputils.c index bd942ce4c790c..a91e0221bd8c5 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -135,7 +135,7 @@ const struct m_opt_choice_alternatives pl_alpha_names[] = { // The numeric index matches the Matroska StereoMode value. If you add entries // that don't match Matroska, make sure demux_mkv.c rejects them properly. const struct m_opt_choice_alternatives mp_stereo3d_names[] = { - {"no", -1}, // disable/invalid + {"no", -1}, // unset, vf_format keeps the source mode {"mono", 0}, {"sbs2l", 1}, // "side_by_side_left" {"ab2r", 2}, // "top_bottom_right" diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index a2464a733c47c..3eebe7bc43cc2 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -100,7 +100,7 @@ static void set_params(struct vf_format_opts *p, struct mp_image_params *out, out->light = p->light; if (p->chroma_location) out->chroma_location = p->chroma_location; - if (p->stereo_in) + if (p->stereo_in >= 0) out->stereo3d = p->stereo_in; if (p->rotate >= 0) out->rotate = p->rotate; @@ -304,6 +304,7 @@ const struct mp_user_filter_entry vf_format = { .name = "format", .priv_size = sizeof(OPT_BASE_STRUCT), .priv_defaults = &(const OPT_BASE_STRUCT){ + .stereo_in = -1, .rotate = -1, .dovi = true, .enhancement_layer = true, From c0df83342ec992afe024313e4ce8281721ad8f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Wed, 23 Sep 2026 15:33:36 +0200 Subject: [PATCH 4/5] csputils: update stale stereo3d name comments mpv stopped inserting vf_stereo3d itself in a5610b2a0d, the names only need to stay recognizable next to the stereo3d filter documentation that DOCS/man/vf.rst points to. The filter also accepts icl and icr as input formats. --- video/csputils.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/video/csputils.c b/video/csputils.c index a91e0221bd8c5..58c008a943080 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -130,7 +130,7 @@ const struct m_opt_choice_alternatives pl_alpha_names[] = { {0} }; -// The short name _must_ match with what vf_stereo3d accepts (if supported). +// The short names follow the input format names of the vf_stereo3d. // The long name in comments is closer to the Matroska spec (StereoMode element). // The numeric index matches the Matroska StereoMode value. If you add entries // that don't match Matroska, make sure demux_mkv.c rejects them properly. @@ -144,8 +144,8 @@ const struct m_opt_choice_alternatives mp_stereo3d_names[] = { {"checkl", 5}, // "checkboard_left" (unsupported by vf_stereo3d) {"irr", 6}, // "row_interleaved_right" {"irl", 7}, // "row_interleaved_left" - {"icr", 8}, // "column_interleaved_right" (unsupported by vf_stereo3d) - {"icl", 9}, // "column_interleaved_left" (unsupported by vf_stereo3d) + {"icr", 8}, // "column_interleaved_right" + {"icl", 9}, // "column_interleaved_left" {"arcc", 10}, // "anaglyph_cyan_red" (Matroska: unclear which mode) {"sbs2r", 11}, // "side_by_side_right" {"agmc", 12}, // "anaglyph_green_magenta" (Matroska: unclear which mode) From b66ba127fdf6703258f2d9d505f0fd0d22d843ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Wed, 23 Sep 2026 15:34:16 +0200 Subject: [PATCH 5/5] vo_gpu: remove unused OSD change check The last caller went away with the rpi code in 343a5fd345. It also regenerated the OSD with stereo mode 0, so any new user would have reset the per eye OSD layout on stereo 3D content. Drop the change tracking state that only existed for it. --- video/out/gpu/osd.c | 21 --------------------- video/out/gpu/osd.h | 2 -- video/out/gpu/video.c | 6 ------ video/out/gpu/video.h | 2 -- 4 files changed, 31 deletions(-) diff --git a/video/out/gpu/osd.c b/video/out/gpu/osd.c index d6788f8c0e3d9..883dccd7eb229 100644 --- a/video/out/gpu/osd.c +++ b/video/out/gpu/osd.c @@ -53,7 +53,6 @@ struct mpgl_osd_part { struct ra_tex *texture; int w, h; int num_subparts; - int prev_num_subparts; struct sub_bitmap *subparts; int num_vertices; struct vertex *vertices; @@ -66,7 +65,6 @@ struct mpgl_osd { struct mpgl_osd_part *parts[MAX_OSD_PARTS]; const struct ra_format *fmt_table[SUBBITMAP_COUNT]; bool formats[SUBBITMAP_COUNT]; - bool change_flag; // for reporting to API user only // temporary int stereo_mode; struct mp_osd_res osd_res; @@ -81,7 +79,6 @@ struct mpgl_osd *mpgl_osd_init(struct ra *ra, struct mp_log *log, .log = log, .osd = osd, .ra = ra, - .change_flag = true, .scratch = talloc_zero_size(ctx, 1), }; @@ -194,7 +191,6 @@ static void gen_osd_cb(void *pctx, struct sub_bitmaps *imgs) ok = false; osd->change_id = imgs->change_id; - ctx->change_flag = true; } osd->num_subparts = ok ? imgs->num_parts : 0; @@ -325,15 +321,6 @@ void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts, osd_draw(ctx->osd, ctx->osd_res, pts, draw_flags, ctx->formats, gen_osd_cb, ctx); ctx->stereo_mode = stereo_mode; - - // Parts going away does not necessarily result in gen_osd_cb() being called - // (not even with num_parts==0), so check this separately. - for (int n = 0; n < MAX_OSD_PARTS; n++) { - struct mpgl_osd_part *part = ctx->parts[n]; - if (part->num_subparts != part->prev_num_subparts) - ctx->change_flag = true; - part->prev_num_subparts = part->num_subparts; - } } // See osd_resize() for remarks. This function is an optional optimization too. @@ -342,11 +329,3 @@ void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mod set_res(ctx, res, stereo_mode); osd_resize(ctx->osd, ctx->osd_res); } - -bool mpgl_osd_check_change(struct mpgl_osd *ctx, struct mp_osd_res *res, - double pts) -{ - ctx->change_flag = false; - mpgl_osd_generate(ctx, *res, pts, 0, 0); - return ctx->change_flag; -} diff --git a/video/out/gpu/osd.h b/video/out/gpu/osd.h index 1b05e254f8146..84139ee8c3989 100644 --- a/video/out/gpu/osd.h +++ b/video/out/gpu/osd.h @@ -19,7 +19,5 @@ bool mpgl_osd_draw_prepare(struct mpgl_osd *ctx, int index, struct gl_shader_cache *sc); void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index, struct gl_shader_cache *sc, const struct ra_fbo *fbo); -bool mpgl_osd_check_change(struct mpgl_osd *ctx, struct mp_osd_res *res, - double pts); #endif diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index b114846ba4fe9..11c4f2a4a6d86 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -3794,12 +3794,6 @@ void gl_video_set_osd_pts(struct gl_video *p, double pts) p->osd_pts = pts; } -bool gl_video_check_osd_change(struct gl_video *p, struct mp_osd_res *res, - double pts) -{ - return p->osd ? mpgl_osd_check_change(p->osd, res, pts) : false; -} - void gl_video_resize(struct gl_video *p, struct mp_rect *src, struct mp_rect *dst, struct mp_osd_res *osd) diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h index a5b03adfbf7bc..d6ead086076ca 100644 --- a/video/out/gpu/video.h +++ b/video/out/gpu/video.h @@ -212,8 +212,6 @@ void gl_video_set_fb_depth(struct gl_video *p, int fb_depth); void gl_video_perfdata(struct gl_video *p, struct voctrl_performance_data *out); void gl_video_set_clear_color(struct gl_video *p, struct m_color color); void gl_video_set_osd_pts(struct gl_video *p, double pts); -bool gl_video_check_osd_change(struct gl_video *p, struct mp_osd_res *osd, - double pts); void gl_video_screenshot(struct gl_video *p, struct vo_frame *frame, struct voctrl_screenshot *args);