Skip to content

Write the frame packing SEI on every path that outputs 3D video, and find it in files that only signal 3D in the stream - #6389

Merged
SubJunk merged 3 commits into
UniversalMediaServer:mainfrom
Sparky-OS:stereo3d-every-path
Oct 16, 2026
Merged

SubJunk merged 3 commits into
UniversalMediaServer:mainfrom
Sparky-OS:stereo3d-every-path

Conversation

@danielcamposramos

Copy link
Copy Markdown
Contributor

#6330 made UMS signal 3D with x264. Three paths still send a flat picture to the renderers that auto-detect 3D from the SEI:

  • Copying the video (the renderer plays the codec as it is and UMS only remuxes to MPEG-TS). A Matroska StereoMode tag does not survive the remux and nothing writes the SEI in its place. This was (a) of Preserve/inject the H.264 frame_packing SEI so frame-packed 3D auto-engages over DLNA #6329, which was not possible before because no FFmpeg bitstream filter could write the message.
  • Right eye first videos on the x264 transcode. libx264 in FFmpeg takes the Stereo 3D side data of the decoded frame over -x264-params frame-packing, and has no right eye first message (libx264.c logs "Ignoring unsupported inverted stereo value"), so it writes none.
  • x265 does not write the message at all.

We wrote and tested a frame_packing option for FFmpeg's h264_metadata and hevc_metadata that writes the message while the picture is untouched. We held this pull request while we waited for confirmations and help on FFmpeg's side (our request FFmpeg #24531, and the related #24628 and #24643, are still open), and the option goes to FFmpeg as soon as they move; this pull request already works with any FFmpeg. When the FFmpeg that UMS runs has the option (UMS asks once with ffmpeg -h bsf=..., and keeps the answer), FFMpegVideo adds it on the copy, on the x264 transcode (instead of -x264-params frame-packing) and on the x265 transcode, with the type and the eye order of the layout that leaves UMS. Without the option nothing changes: the x264 transcode keeps -x264-params frame-packing. Side by side and top and bottom, left and right eye first. The SEI does not tell half from full resolution, so both give the same message.

The second commit finds the layout of files that signal 3D only with the SEI (MediaInfo reports the container, not the stream). For H.264 and H.265 videos at least 1280 wide and with no layout yet, UMS decodes the first frame with FFmpeg's showinfo filter and reads the Stereo 3D line. Half or full is told from the size, the way the file name case does. These files were not 3D to UMS until now: they were not in the 3D folder and Output3DFormat could not convert them. It costs one FFmpeg run of a frame for each H.264 or H.265 video of 1280 pixels or more without a layout, with a 5 second limit. If you prefer a setting to switch it off, tell me the name you want and I will add it.

How it was checked: a DLNA client in containers browsed UMS, played each title as four test renderers (copy H.264, copy H.265, re-encode H.264, re-encode H.265) and read the received MPEG-TS with FFmpeg's trace_headers bitstream filter. The library has H.264 and HEVC files, side by side and top and bottom, left and right eye first, with the Matroska StereoMode tag only, plus a 2D file of each codec.

  • Before (15.9.0): 6 of 32 3D streams carry the SEI. All are left eye first, and none is copied.
  • After: 32 of 32 carry it, with the type (3 side by side, 4 top and bottom) and the content interpretation (1 left view first, 2 right view first) of the source. The 2D files carry none.
  • With the bitstream filter disabled (a planted fault) the result goes back to 6 of 32.
  • The copied video is untouched: the decoded frames of the received stream and of the source have the same MD5 for the four files compared.
  • A file with the SEI and no container tag (MPEG-TS made with the new filter), played by a renderer that asks for right eye first (Output3DFormat = sbsr): the 15.9.0 jar sends it unchanged; with the second commit UMS finds the layout, the eyes are swapped (PSNR of the received left eye against the source right eye: 45.9 dB, against the source left eye: 3.1 dB) and the message says right eye first.
  • New unit tests (FramePackingFilterTest, FramePackingParserTest) and the existing FFmpegVideoTest, MediaInfoParserTest, FFmpegParserTest, FFmpegHlsSubtitlesTest, TranscodingSettingsTest pass.

Not done: the renderer profiles are untouched, and nothing was tried on a real TV in this round (the earlier measurements on the KDL-46EX725 and KDL-46HX855 in #6329 are the reason for the SEI).

This text and the code were written with Claude (Anthropic) and checked by running them. I reviewed and submit it.

UniversalMediaServer#6330 signalled 3D with x264 only. A video that is copied to MPEG-TS lost its Matroska StereoMode tag and no SEI took its place, x264 does not write the SEI for a right eye first video, and x265 does not write it at all, so renderers that auto-detect 3D showed these flat.

FFmpeg's h264_metadata and hevc_metadata can write the frame packing arrangement SEI without touching the picture with the frame_packing option (FFmpeg patch series: avcodec/bsf/h264_metadata,hevc_metadata: add the frame_packing option). UMS adds it to the copy and to the x264 and x265 transcodes when this FFmpeg has the option, and keeps -x264-params frame-packing when it does not.

Side by side and top and bottom, both eye orders; half and full resolution give the same message, as the SEI does not tell them apart.
MediaInfo reports the layout from the container, not the frame packing arrangement SEI of an H.264 or H.265 video, so a file that only has the SEI was not 3D to UMS: not in the 3D folder, not converted for Output3DFormat. Read it from the first frame with FFmpeg's showinfo filter, for H.264 and H.265 videos at least 1280 pixels wide that have no layout yet. Half or full is told from the size like it is for the file name.
@danielcamposramos

Copy link
Copy Markdown
Contributor Author

Hi @SubJunk, and thank you again for merging #6330.
This one carries it on: UMS now finds 3D in files that signal it only in the stream, and with an FFmpeg that has the new frame packing option it writes the SEI on the copy, the right eye first x264 transcode and x265 too.
Without that option nothing changes, so it is safe with any FFmpeg.
All checks pass and it merges cleanly with main.
Whenever you have a moment, would you take a look?

@SubJunk

SubJunk commented Oct 16, 2026

Copy link
Copy Markdown
Member

@danielcamposramos this is cool, I will merge it, thanks!

Something I find hard about FFmpeg is that the syntax sometimes changes at seemingly random times, like in patch releases and they don't announce it. That has caused bugs for us before.

We have a collection of small videos in src/test/resources/net/pms/parsers that lets us detect changes like that with real files.

Maybe you could help these 3D capabilities be more robust by adding some 3D files in there, with a test that spins up FFmpeg and makes sure the code in this PR continues to parse the output properly

@SubJunk
SubJunk merged commit 513122e into UniversalMediaServer:main Oct 16, 2026
10 checks passed
@danielcamposramos

Copy link
Copy Markdown
Contributor Author

Thank you for merging it, @SubJunk, and for the idea.
I made a set of small 3D videos for src/test/resources/net/pms/parsers with a test that runs FFmpeg and MediaInfo on them, and on their first run they found three gaps in this check, so the fixes go with them.
They are coming as a follow-up pull request.

SubJunk pushed a commit that referenced this pull request Oct 16, 2026
…, and fix what they found (#6400)

* Recognize half side by side and half top-bottom right eye first

The frame packing check of #6389 names a right eye first video up to 1920 pixels wide "half side by side (right eye first)" and one up to 1080 pixels high "half top-bottom (right eye first)", but get3DLayout() had no mode for them and multiViewIsAnaglyph() took them for anaglyph. Such a video was not converted and not signalled, and for an H.265 video with Output3DFormat set, StoreItem called toString() on the missing mode.

Map them to SBS2R and AB2R, like their left eye first twins, and draw 3D subtitles for them too.

Assisted-by: Claude Opus 5.5 via Claude Code

* Run the frame packing check with the FFmpeg of the FFmpeg engine

FramePackingParser asked UmsConfiguration.getFFmpegPath() for FFmpeg, which is null on Linux when there is no FFmpeg on the PATH and UMS uses the one it bundles: the default type of that program info goes back to the installed one, which has no path. So the check of #6389 never ran there, and it did not follow the FFmpeg the user picked either.

Ask the FFmpeg engine for its executable, as FFmpegParser does, and as FFMpegVideo already does when it writes the SEI.

Assisted-by: Claude Opus 5.5 via Claude Code

* Find the frame packing SEI in the output of FFmpeg before 7.1 too

showinfo prints the Stereo 3D side data three ways: "stereoscopic information: type - side by side (inverted)" up to FFmpeg 5.1, "Stereo 3D: type - side by side (inverted)" in 6.0 to 7.0, and "Stereo 3D: type - side by side (inverted), view - packed, primary_eye - none" since 7.1. The check of #6389 read only the last, so with the FFmpeg of Ubuntu 22.04 (4.4) and 24.04 (6.1) or Debian 12 (5.1), which UMS runs on Linux when it is on the PATH, no file was found to be 3D.

Read all three. The view is still checked when FFmpeg prints it.

Assisted-by: Claude Opus 5.5 via Claude Code

* Add 3D test videos, and test that FFmpeg and MediaInfo still find their layout

Eighteen small videos made with FFmpeg from lavfi colour sources, LEFT and RIGHT written on each eye, five frames at 25 fps (eight at 50 fps, one eye per frame, for temporal interleaving), 112 KB in all.

The frame packing arrangement SEI (H.264 D.2.26, H.265 D.3.16): side by side and top and bottom, left and right eye first, half and full size, in H.264 and H.265, in MP4 and MPEG-TS. The other arrangements of H.264 Table D-8 that x264 writes (checkerboard, column and row interleaving, temporal interleaving, 2D) and the temporal interleaving of H.265 Table D.8, which have no 3D mode in UMS and must stay 2D. And Matroska's StereoMode without the SEI, side by side and top and bottom, both eye orders.

FramePackingParserTest parses them like any file, so MediaInfo reads StereoMode and FFmpeg's showinfo the SEI, and checks the layout and the 3D mode. FramePackingFilterTest copies the Matroska videos to MPEG-TS with the bitstream filter UMS adds, and checks that the copy is found to be 3D from the SEI alone; it runs when FFmpeg has the frame_packing option.

Assisted-by: Claude Opus 5.5 via Claude Code
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.

2 participants