mpv-player / mpv-player/mpv

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

Open
#18,489 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
37k
Forks
3.5k
Avg merge
1d 10h
Merged PRs (30d)
22

Description

mpv Information

mpv v0.41.0, libplacebo v7.360.1 — Debian 14, x86_64, libavcodec 63.1.101.

Summary

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:

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
Actual Behavior
plain.mkv            stereo-in=mono
container_only.mkv   stereo-in=sbs2l
sei_only.mp4         stereo-in=mono     <-- expected sbs2l
Expected Behavior

sei_only.mp4 should report stereo-in=sbs2l. The two files are the same picture carrying the
same layout declared two different ways.

The data is present and correct

The SEI is in the stream — trace_headers parses it:

$ ffmpeg -v trace -i sei_only.mp4 -map 0:v:0 -frames:v 2 -c copy \
      -bsf:v trace_headers -f null - 2>&1 | grep frame_packing
frame_packing_arrangement_id                 1 = 0
frame_packing_arrangement_cancel_flag        0 = 0
frame_packing_arrangement_type         0000011 = 3      # 3 = side by side
frame_packing_arrangement_repetition_period  010 = 1

And libavcodec already converts it to AVStereo3D and attaches it to the decoded frame:

$ ffmpeg -v info -i sei_only.mp4 -vf showinfo -frames:v 1 -f null -
[Parsed_showinfo_0] side data - Stereo 3D: type - side by side, view - packed, primary_eye - none

So by the time mpv receives the AVFrame, the layout is already decoded, normalised and
attached.

Diagnosis

stereo3d has exactly one source in the tree. demux/demux_mkv.c:752 reads the Matroska
element, and no other demuxer sets it — grep -rn 'stereo3d\|StereoMode\|stereo_mode' demux/*.c
matches only demux_mkv.c.

mp_image_from_av_frame() (video/mp_image.c) imports nine kinds of frame side data from
libavcodec — AV_FRAME_DATA_DISPLAYMATRIX, ICC_PROFILE, MASTERING_DISPLAY_METADATA,
CONTENT_LIGHT_LEVEL, DYNAMIC_HDR_PLUS, A53_CC, DOVI_METADATA, DOVI_RPU_BUFFER,
FILM_GRAIN_PARAMS — but not AV_FRAME_DATA_STEREO3D.

Additionally, fix_image_params() in filters/f_decoder_wrapper.c overwrites the layout on
every frame with the container tag from the demuxer.

Why reading the side data is not enough on its own

Worth recording, because it is the non-obvious half of the bug. The in-band signal is attached
only to the frames that carry it — measured on the files above:

file frames with Stereo 3D side data keyframes
sei_only.mp4 (48 frames) 1 1
a real SBS MP4 (first 200 frames) 4 4

An exact 1:1 match with keyframes, consistent with frame_packing_arrangement_repetition_period = 1 ("persists until the next IDR"). So reading the side data alone still produced mono: the
keyframe was detected, the following frame carried no side data, fix_image_params() ran again
and reset the format. The layout has to persist across the frames that do not repeat it.

Relation to existing issues
  • #1045 ("auto-detecting 3D input format") is closed and predates AVStereo3D being wired
    through this path.
  • #18348 concerns the removed full-width sbsl/abl choices; its reporter's file is a
    Matroska whose tag mpv reads correctly. That is the layout vocabulary; this is the
    detection source.
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 going to keep appearing.

Fix

A tested two-part patch is ready and I will open a pull request referencing this issue: read
AV_FRAME_DATA_STEREO3D in mp_image_from_av_frame(), and remember an in-band layout across
the frames that do not repeat it in f_decoder_wrapper.c, falling back to the container tag
only when the stream signals nothing.

Verified on a patched build: sei_only.mp4 goes from mono to sbs2l, three real SEI-only
MP4s go from mono to sbs2l/sbs2r, and no container-tagged or unsignalled file changes
behaviour. One of those files declares side by side (inverted) — right eye first — while its
filename says SBS, so it is also a case where guessing from the filename would swap the
viewer's eyes.


Disclosure

An AI partner helped produce the outcomes and this post.

  • Harness: Claude Code (Anthropic's agentic CLI)
  • Model class: Claude Opus (Anthropic frontier tier)
  • Model: Claude Opus 5 (1M context), model ID claude-opus-5[1m]

Every result above was executed and measured on the machine described, 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with mp_image_from_av_frame() in video/mp_image.c and fix_image_params() in filters/f_decoder_wrapper.c, then run the reproduction commands against the reported files. Done means the in-band SEI case reports the correct stereo layout persistently, while container-tagged and unsignalled files retain their existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
backend, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.