Skip to content

stereo3d: detect layout signalled in the video stream (frame packing SEI, st3d) - #18490

Open
danielcamposramos wants to merge 2 commits into
mpv-player:masterfrom
danielcamposramos:stereo3d-frame-packing-detection
Open

danielcamposramos wants to merge 2 commits into
mpv-player:masterfrom
danielcamposramos:stereo3d-frame-packing-detection

Conversation

@danielcamposramos

@danielcamposramos danielcamposramos commented Sep 16, 2026

Copy link
Copy Markdown

Fixes #18489.
What this patches:

  • mpv detects a stereo 3D layout only from the Matroska StereoMode element. A layout signalled in the video stream, the H.264/HEVC frame_packing_arrangement SEI (payload type 45), or the MP4 st3d box, is ignored, and such files play as stereo-in=mono.

libavcodec already decodes that SEI into AVStereo3D frame side data and hands it to mpv. mpv never asks for it.

Reproduction steps:
Three files isolating each signal, built in three commands using ffmpeg:

ffmpeg -f lavfi -i "testsrc2=size=960x1080:rate=24:duration=2" \
       -f lavfi -i "smptebars=size=960x1080:rate=24:duration=2" \
       -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" \
       -c:v libx264 -crf 20 -pix_fmt yuv420p source_sbs.mp4

ffmpeg -i source_sbs.mp4 -c:v copy -an plain.mkv                      # no signal
mkvmerge -o container_only.mkv --stereo-mode 0:1 plain.mkv            # container tag only
ffmpeg -i source_sbs.mp4 -c:v libx264 -crf 20 -pix_fmt yuv420p \
       -x264-params frame-packing=3 sei_only.mp4                      # in-band SEI only
for f in plain.mkv container_only.mkv sei_only.mp4; do
  printf '%-20s ' "$f"
  mpv --no-config --vo=null --frames=1 --no-audio \
      --term-playing-msg='${video-params}' "$f" 2>/dev/null | grep -i stereo-in
done

Before this patch sei_only.mp4 reports stereo-in=mono; after it reports stereo-in=sbs2l.

Why the first frame was not enough:
The in-band signal is attached only to the frames that carry it. Measured files.

Results
Verified on Debian 14, x86_64, libavcodec 63.1.101, libplacebo 7.360.1, built from this tree. Real files are ordinary 3D videos, not synthesized for the test.

No container-tagged file changes behaviour, and files with no signal stay mono.
The inverted case is the one that showed why metadata beats guessing: that file's SEI declares
side by side (inverted), the right eye first type, while its filename says SBS.

Before this change mpv showed it flat; a player guessing from the filename would swap the viewer's eyes. Only the declared layout gets it right.

Scope:
This makes mpv detect the layout. It does not change what mpv does with it, no automatic conversion is added, and --vf=format:stereo-in=... still overrides as before.

Files that already worked are unaffected.

Relation to existing issues:

Why this is becoming more common:

HandBrake merged support for writing this SEI (PR #8100), and FFmpeg has an open request for a lossless injector for it (#24531), so files carrying the in-band signal are expected to keep appearing.

Testing:
Tested, per DOCS/contribute.md. Built from this tree and run against the three control files and a 26-file library of real 3D videos. Happy to adjust the mapping's placement — it could live next to mp_stereo3d_names[]incsputils.c` instead of inline — or to split the fallback differently if you would rather the container tag always win.

Disclosure:
An AI partner helped produce the outcomes. The words here are mine, edited in the browser.

Every result above was executed and measured on my machine, not inferred;
The patch was compiled, the before/after values come from running both binaries against the same files, and the side-data-per-keyframe counts were measured with ffmpeg -vf showinfo.

@danielcamposramos

Copy link
Copy Markdown
Author

Follow-up on scope, before it comes up in review.

The description says no container-tagged file changes behaviour. That holds when the container tag and the in-band signal agree, which is the normal case, but not when they disagree:

mkvmerge -o conflict.mkv --stereo-mode 0:3 sei_only.mp4   # tag: top-bottom, SEI: side-by-side

  before: stereo-in=ab2l    (container tag)
  after:  stereo-in=sbs2l   (in-band signal)

This is deliberate — it mirrors the frame rotation handling a few lines below in fix_image_params(), where the frame's value also takes priority over the container — but it is a behaviour change on a tagged file, so it should be stated rather than implied. If you would prefer the container tag to always win when both are present, that is a one-line reordering and I am happy to make it.

@CounterPillow

Copy link
Copy Markdown
Contributor
mucho texto

@danielcamposramos

Copy link
Copy Markdown
Author
mucho texto

Why are you so rude? Your single accepted PR is larger and contains no technical nothing, it's nothing but bad words on people trying to collaborate because, yes, it's a people dream (humans project into others what they are) to be listed as contributor, not to actually contribute with fixes....

@CounterPillow

Copy link
Copy Markdown
Contributor

Your single accepted PR

$ gol --author="Nicolas F" | wc -l
28

?

The rude part here is you letting clod flood the maintainers with redundant text.

@llyyr

llyyr commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Why are you so rude? Your single accepted PR is larger and contains no technical nothing, it's nothing but bad words on people trying to collaborate because, yes, it's a people dream (humans project into others what they are) to be listed as contributor, not to actually contribute with fixes....

Please read the contribution guidelines. You need to show you're capable of responding to reviews with human-written responses, which the AI-slop written commit messages implies you're not.

@danielcamposramos

Copy link
Copy Markdown
Author

Why are you so rude? Your single accepted PR is larger and contains no technical nothing, it's nothing but bad words on people trying to collaborate because, yes, it's a people dream (humans project into others what they are) to be listed as contributor, not to actually contribute with fixes....

Please read the contribution guidelines. You need to show you're capable of responding to reviews with human-written responses, which the AI-slop written commit messages implies you're not.

AI-slop - define this term, please? What about human slop, like being rude to a genuine contribuition proposal? You are assuming that because I leveraged AI to write the PR is slop? HandBreak actually verifyed my claims before that...

This message might contain english errors because it's not my main language and mainteners prefer wrong english than AI assisted writting.

Comment thread video/mp_image.c
Comment thread video/mp_image.c
Comment thread video/mp_image.c
Comment thread video/mp_image.c
Comment thread video/mp_image.c
Comment thread video/mp_image.c
Comment thread filters/f_decoder_wrapper.c
Comment thread filters/f_decoder_wrapper.c
@danielcamposramos

Copy link
Copy Markdown
Author

How often the frame-packing SEI is actually attached to a frame?
Runnable evidence for the "why the first frame was not enough": the frame_packing_arrangement SEI reaches libavcodec as AV_FRAME_DATA_STEREO3D on keyframes only, so a player that reads the side data once and then lets fix_image_params() run again on the next frame loses the layout.
That is what the PR's second commit (f_decoder_wrapper: keep in-band stereo 3D layout across frames) fixes.

It needs no patched mpv and no sample downloads.
Stock ffmpeg, four commands, about ten seconds.

The block

# Build a 10 s SBS clip with the frame-packing SEI and a keyframe every 48 frames.
ffmpeg -v error -f lavfi -i "testsrc2=size=960x1080:rate=24:duration=10" \
       -f lavfi -i "smptebars=size=960x1080:rate=24:duration=10" \
       -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" \
       -c:v libx264 -crf 20 -pix_fmt yuv420p -g 48 \
       -x264-params frame-packing=3 sei_multi.mp4

# Same thing without the SEI, as a negative control.
ffmpeg -v error -f lavfi -i "testsrc2=size=960x1080:rate=24:duration=10" \
       -f lavfi -i "smptebars=size=960x1080:rate=24:duration=10" \
       -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" \
       -c:v libx264 -crf 20 -pix_fmt yuv420p -g 48 no_sei.mp4

# How many frames actually carry AV_FRAME_DATA_STEREO3D?
for f in sei_multi.mp4 no_sei.mp4; do
  ffprobe -v error -select_streams v:0 -show_frames "$f" > /tmp/f.$$
  printf "%-16s frames=%-5s keyframes=%-4s stereo3d_side_data=%s\n" "$f" \
    "$(grep -c '^media_type=video'          /tmp/f.$$)" \
    "$(grep -c '^key_frame=1'               /tmp/f.$$)" \
    "$(grep -c '^side_data_type=Stereo 3D'  /tmp/f.$$)"
  rm -f /tmp/f.$$
done

# And which frames they are.
ffprobe -v error -select_streams v:0 -show_frames sei_multi.mp4 | awk '
  /^media_type=video/{n++} /^key_frame=1/{k[n]=1} /^side_data_type=Stereo 3D/{s[n]=1}
  END{for(i=1;i<=n;i++) if(k[i]||s[i]) printf "frame %3d  keyframe=%d  stereo3d=%d\n", i, (k[i]?1:0), (s[i]?1:0)}'

Output

sei_multi.mp4    frames=240   keyframes=5    stereo3d_side_data=5
no_sei.mp4       frames=240   keyframes=5    stereo3d_side_data=0
frame   1  keyframe=1  stereo3d=1
frame  49  keyframe=1  stereo3d=1
frame  97  keyframe=1  stereo3d=1
frame 145  keyframe=1  stereo3d=1
frame 193  keyframe=1  stereo3d=1

Same counts and the same frame indices across three major FFmpeg versions, so the result is a property of the file rather than of one build.

Why it is built this way?
Two details do real work and are easy to leave out.
The negative control. no_sei.mp4 is the same clip encoded without -x264-params frame-packing=3 and reports stereo3d_side_data=0. Without it, a reader cannot tell whether the counter detects the SEI or simply always prints a number.
The keyframe interval. -g 48 over 10 seconds gives five keyframes. A 2-second clip has exactly one, and 1 of 1 cannot distinguish "one per keyframe" from "one per file" — which is precisely the distinction the second commit turns on. Frames 1, 49, 97, 145 and 193 settle it: the side data lands on every keyframe and on nothing else, consistent with frame_packing_arrangement_repetition_period = 1 ("persists until the next IDR").

@danielcamposramos

Copy link
Copy Markdown
Author

Another data point on the approach, in case it is useful to the review: Kodi already does this, and has for years.

CDVDVideoCodecFFmpeg takes the stereo mode from the decoded frame's metadata — FFmpeg's H.264 decoder sets a stereo_mode entry from the frame-packing SEI, with the Matroska strings as values (left_right, top_bottom, …):

xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp:1043
AVDictionaryEntry* entry = av_dict_get(m_pFrame->metadata, "stereo_mode", NULL, 0);

Verified by running Kodi 21.2 on a file whose only 3D signal is the SEI (neutral filename, no container tag): autodetected stereo mode for movie mode left_right.

Sample to check against:

ffmpeg -f lavfi -i testsrc2=size=1920x1080:rate=24 -t 10 -c:v libx264 \
  -x264-params frame-packing=3 -pix_fmt yuv420p sbs.mp4
ffprobe -show_frames -read_intervals %+#1 sbs.mp4 | grep side_data_type   # Stereo 3D
ffprobe -show_entries stream_tags=stereo_mode sbs.mp4                     # nothing

And on why it matters outside players: on 2011–2012 Sony BRAVIA sets this SEI is the only signal that makes the display engage 3D by itself.

Measured through two independent DLNA servers, byte-exact: the SEI-carrying file engages 3D, the identical file with the SEI removed plays flat.

Daniel Campos Ramos and others added 2 commits September 19, 2026 01:13
mp_image_from_av_frame() imports nine kinds of frame side data from
libavcodec, but not AV_FRAME_DATA_STEREO3D. As a result the only stereo
3D layout mpv ever saw was the Matroska StereoMode element, read by
demux_mkv.c; a layout signalled in the video stream itself was ignored.

libavcodec decodes the H.264/HEVC frame_packing_arrangement SEI
(payload type 45) and the MP4 st3d box into AVStereo3D side data, so the
information is already attached to the frame by the time mpv receives
it. Map it onto mp_image_params.stereo3d, whose numeric values match the
Matroska StereoMode element, including the right-eye-first variants via
AV_STEREO3D_FLAG_INVERT.

A container level tag, if present, is applied earlier via opaque_ref and
keeps priority, so files that worked before behave exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix_image_params() overwrote the layout on every frame with the
container tag from the demuxer, discarding a layout signalled in the
stream. Worse, the in-band signal is not always carried by every frame:
DVB requires the frame_packing_arrangement SEI on every frame of a
broadcast service, but in x264 output and in every file tested it is
repeated per IDR only, so only keyframes arrive with
AV_FRAME_DATA_STEREO3D attached. The first frame was therefore detected
correctly and the next frame reset the format back to mono for the rest
of playback.

Remember the layout signalled in the stream and reapply it to the frames
that do not repeat it, and only fall back to the container tag when the
stream signals nothing. This mirrors the existing handling of frame
rotation a few lines below, where the value from the frame also takes
priority over the container, and matches the precedence DVB gives the
in-band signal over other signalling (ETSI TS 101 547-2 V1.2.1, clause
6.5). The remembered value is cleared in
mp_decoder_wrapper_reset_params() so it does not leak across segments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@danielcamposramos
danielcamposramos force-pushed the stereo3d-frame-packing-detection branch from baaf79f to 1c5c04f Compare September 19, 2026 04:13
@danielcamposramos

Copy link
Copy Markdown
Author

Look, I am not that good with words and english can be complicated to convey, I therefore disclosured the leverage of AI on the PR. The new rule also landed one day after I filed the PR and merge request.
Please, do not stay out of the 3D video playback fix just because I am Brazilian and used AI to help write some text, look at the content for what it is and think on all future generations that will enjoy seamless 3D content on VR and old school 3D hardware.

@danielcamposramos

Copy link
Copy Markdown
Author

@kasper93 urging for a neutral technical review here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stereo 3D layout signalled in the stream (H.264 frame packing SEI, MP4 st3d) is ignored

3 participants