Skip to content

Parse the H.264/HEVC frame_packing_arrangement SEI so frame-packed 3D is detected without a container tag #3419

Open

Description

@danielcamposramos

[REQUIRED] Use case description

Media3 detects stereoscopic video from container-level signalling only. Frame-packed 3D that carries its layout in the elementary stream is invisible to it.

What is present today:

  • MatroskaExtractor reads the Matroska StereoMode element (private static final int ID_STEREO_MODE = 0x53B8;).
  • BoxParser reads Apple's vexu box, and HevcConfig parses the 3D Reference Display Information SEI, for MV-HEVC spatial video.
  • H264Reader's SEI handling is documented as "An SEI reader for consuming closed caption channels", and wires up only CEA-608/708.

What is absent: the classic frame_packing_arrangement SEI — ITU-T H.264 Annex D, payload type 45, and its HEVC equivalent. Searching this repository for frame_packing, FramePacking or frame packing returns no hits.

The consequence is narrow but real: an H.264 stream whose only stereoscopic signal is that SEI plays as a flat double-width (or double-height) picture, with no way for an app to know it is 3D. That is the normal case for:

  • MPEG-TS delivery, where container-level stereoscopic tags do not exist at all. In the DVB frame-compatible 3DTV specification (ETSI TS 101 547-2) the in-stream SEI is the signal, and it takes precedence over container signalling.
  • Any remux from Matroska to TS, which drops StereoMode silently.
  • Broadcast-era and disc-derived material generally, which is where most frame-packed content in existence comes from.

This was requested once before, on the ExoPlayer tracker: google/ExoPlayer#7869 (September 2020), answered with "as you are the first one to ask for it, we will probably not look into it in the near future" and labelled low priority. This is the second ask, five years on, with measurements and with the rest of the ecosystem having moved.

Where the ecosystem now stands, in case it helps weigh the cost:

  • GStreamer already does the whole loop: gst_h264_parser_parse_frame_packing() decodes payload type 45, h264parse maps it to GstVideoMultiviewMode and publishes it on caps, and x264enc derives its write-side parameter from those caps automatically.
  • FFmpeg exposes the same signal as AV_FRAME_DATA_STEREO3D, so anything built on libavcodec can read it in a few lines.
  • VLC parses payload type 45 into multiview_mode in its H.264 packetizer and uses it for stereo rendering.
  • HandBrake merged a patch to write the SEI when encoding (PR #8100, September 2026).
  • Universal Media Server has an approved PR doing the same for its transcodes (PR #6330).

Why it is worth doing now rather than in 2020. Stereoscopic playback stopped being a niche the moment headsets became ordinary again, and Android is the platform most of them run on. Media3 already grew MV-HEVC support for Apple spatial video (#2011); this is the same class of feature for the far larger body of existing content, which is frame-packed rather than multi-view.

The behaviour is measurable on hardware, not theoretical. On two 2011/2012 televisions I own, an MP4 whose only stereoscopic signal is this SEI switches the set into 3D automatically; the identical file with the SEI removed — a difference of 791 bytes, picture data untouched — plays flat; and a Matroska carrying only the container StereoMode tag is ignored. Method and files: https://github.com/danielcamposramos/sony-bravia-linux/blob/main/docs/3d-signalling-ecosystem.md

Proposed solution

Parse SEI payload type 45 where the elementary stream is already being walked (H264Reader/SeiReader, and the HEVC equivalent), and map it onto the Format.stereoMode values that already exist:

frame_packing_arrangement_type Suggested C.STEREO_MODE_*
3 (side-by-side) STEREO_MODE_LEFT_RIGHT
4 (top-bottom) STEREO_MODE_TOP_BOTTOM
0 (checkerboard), 1 (column), 2 (row), 5 (temporal) no existing constant; ignoring them is reasonable for a first pass

content_interpretation_type carries eye order and would select between left-first and right-first. A container tag, where one exists, should presumably still win, so this would only fill in stereoMode when the container did not state it.

One adjacent observation, since it is in the same area and may be a separate small fix: WebmConstants defines STEREO_MODE = 0x53B8 and lists it in the element table, but WebmWriter, WebmMuxer and WebmElements contain no reference to it, so Media3's own muxer does not appear to write the tag back out on transcode.

Alternatives considered

  • Remux to add a container tag. Fails for the MPEG-TS and streaming cases, which is where this signal is most common, and requires knowing the layout in the first place.
  • Leave it to the app. An app can only guess from resolution and aspect, or ask the user to pick a layout by hand, which is what players without this support do today.

Happy to help with the implementation if that would be useful — I understand a code contribution would need the Google CLA, and I am willing to do that if you would take the patch.

Activity

  1. self-assigned this
    on Sep 22, 2026
  2. microkatz commented on Oct 1, 2026

    @microkatz
    Contributor

    @danielcamposramos

    Thank you for reporting the issue! I have submitted a fix for the MPEG-TS in-stream processing, 58f396c, into the main branch. Ideally containers, MP4/fMP4, would contain the correct stereo info themselves.

    Does this fulfill your immediate requirements? If yes, then we can close the ticket.

  3. danielcamposramos commented on Oct 1, 2026

    @danielcamposramos
    Author

    Thank you, Michael. That covers the MPEG-TS case, H.264 and H.265 both.

    But, not yet for MP4, though, and that is the case I measured.
    In MP4, common encoders (x264, FFmpeg, HandBrake) write the frame-packed layout only in the SEI.
    So in MP4 and fMP4 the SEI is the stereo signal there is.
    That is the 791-byte difference in the report: the same file with the SEI switches the TV into 3D, and without it plays flat.

    I've enhanced your code for MP4 in #3439, building on 58f396c and your NalUnitUtil.parseSeiStereoMode:

    • Mp4Extractor and FragmentedMp4Extractor read the stereo mode from the SEI, only when the container declares none.
    • WebmMuxer now writes StereoMode, so a transcode keeps the layout.
    • Tests and three small assets; the extractor, muxer and container test suites pass.

    content_interpretation_type 2 means frame 0 is the right view.
    Media3 had no right-first constant for side by side or top-bottom, so I added them in the same PR: C.STEREO_MODE_RIGHT_LEFT and C.STEREO_MODE_BOTTOM_TOP, read from the SEI and from Matroska StereoMode 11 and 2, written by WebmMuxer, and rendered with the eyes swapped by the spherical renderer.

  4. microkatz commented on Oct 20, 2026

    @microkatz
    Contributor

    @danielcamposramos

    I have seen your update and the additional PR that you supplied to extend the feature for MP4 processing. It is still on our radar and we will get to reviewing the issue and PR. We appreciate your patience!

  5. danielcamposramos commented on Oct 20, 2026

    @danielcamposramos
    Author

    @microkatz No worries, is just that as I progress and discover more nuances, I like to share upstream so everyone is in the same page. I am working on something with KDE that also touches Android, if you have couple minutes, just take a peek here:
    flat-headset

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions