[PR] Add stereo3d option (PR #24300)

Jun Zhao via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <[email protected]>
PR #24300 opened by Jun Zhao (mypopydev)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24300
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24300.patch

Add a per-stream output option -stereo3d that injects packed Stereo 3D side data so muxers can write their native layout metadata from sources that have none (PNG sequences and similar). With -strict unofficial, MP4 gets Google st3d; MOV still writes Apple vexu eyes only (no st3d on MOV in this series); Matroska gets StereoMode. The series also warns when MP4 skips st3d/sv3d under default strictness, and keeps a more specific st3d type when merging vexu eyes so dual-box files round-trip in ffprobe.


>From c68bd52ceb92c240e2d8bcd5fe2e6831886916fd Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:44 +0800
Subject: [PATCH 1/6] fftools/ffmpeg: add -stereo3d per-stream output option

Sources such as PNG sequences have no stereoscopic metadata, so there
was no CLI way to write container layout tags without an external
post-processor. Inject AV_PKT_DATA_STEREO3D onto the output stream
after bitstream filters and before write_header.

A bare -stereo3d applies to video and is ignored on other types; an
explicit specifier on a non-video stream is rejected.

Fixes: #24269
Reported-by: optozorax

Signed-off-by: Jun Zhao <[email protected]>
---
 fftools/ffmpeg.h          |  1 +
 fftools/ffmpeg_mux.c      | 22 ++++++++++
 fftools/ffmpeg_mux.h      |  3 ++
 fftools/ffmpeg_mux_init.c | 92 ++++++++++++++++++++++++++++++++++++++-
 fftools/ffmpeg_opt.c      |  3 ++
 5 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index c403acdb59..d3aa191e23 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -203,6 +203,7 @@ typedef struct OptionsContext {
     SpecifierOptList fps_mode;
     SpecifierOptList force_fps;
     SpecifierOptList frame_aspect_ratios;
+    SpecifierOptList stereo3ds;
     SpecifierOptList display_rotations;
     SpecifierOptList display_hflips;
     SpecifierOptList display_vflips;
diff --git a/fftools/ffmpeg_mux.c b/fftools/ffmpeg_mux.c
index b408b02f2a..0e546c286f 100644
--- a/fftools/ffmpeg_mux.c
+++ b/fftools/ffmpeg_mux.c
@@ -30,6 +30,7 @@
 #include "libavutil/intreadwrite.h"
 #include "libavutil/log.h"
 #include "libavutil/mem.h"
+#include "libavutil/stereo3d.h"
 #include "libavutil/time.h"
 #include "libavutil/timestamp.h"
 
@@ -633,6 +634,27 @@ int of_stream_init(OutputFile *of, OutputStream *ost,
     if (ret < 0)
         return ret;
 
+    if (ms->stereo3d_set) {
+        AVCodecParameters *par = ost->st->codecpar;
+        AVStereo3D *stereo;
+        size_t size;
+
+        stereo = av_stereo3d_alloc_size(&size);
+        if (!stereo)
+            return AVERROR(ENOMEM);
+        stereo->type = ms->stereo3d_type;
+
+        if (av_packet_side_data_get(par->coded_side_data, par->nb_coded_side_data,
+                                    AV_PKT_DATA_STEREO3D))
+            av_log(ost, AV_LOG_INFO, "Overriding existing stereoscopic 3D side data\n");
+
+        if (!av_packet_side_data_add(&par->coded_side_data, &par->nb_coded_side_data,
+                                     AV_PKT_DATA_STEREO3D, stereo, size, 0)) {
+            av_freep(&stereo);
+            return AVERROR(ENOMEM);
+        }
+    }
+
     if (ms->stream_duration) {
         ost->st->duration = av_rescale_q(ms->stream_duration, ms->stream_duration_tb,
                                          ost->st->time_base);
diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f458cc882c..3d6686da02 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -87,6 +87,9 @@ typedef struct MuxStream {
     int             force_fps;
 
     const char     *apad;
+
+    int             stereo3d_set;
+    int             stereo3d_type;
 } MuxStream;
 
 typedef struct Muxer {
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index f80b4427b4..4efee7e44f 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/pixdesc.h"
+#include "libavutil/stereo3d.h"
 
 #define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"
 
@@ -545,6 +546,86 @@ static enum AVPixelFormat pix_fmt_parse(OutputStream *ost, const char *name)
     return fmt;
 }
 
+static int parse_stereo3d_type(void *logctx, const char *arg, int *type)
+{
+    static const struct {
+        const char *name;
+        int type;
+    } aliases[] = {
+        { "2d",   AV_STEREO3D_2D },
+        { "mono", AV_STEREO3D_2D },
+        { "sbs",  AV_STEREO3D_SIDEBYSIDE },
+        { "sbsl", AV_STEREO3D_SIDEBYSIDE },
+        { "tb",   AV_STEREO3D_TOPBOTTOM },
+        { "tbl",  AV_STEREO3D_TOPBOTTOM },
+    };
+    static const enum AVStereo3DType v1_types[] = {
+        AV_STEREO3D_2D,
+        AV_STEREO3D_SIDEBYSIDE,
+        AV_STEREO3D_TOPBOTTOM,
+    };
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(aliases); i++) {
+        if (!av_strcasecmp(arg, aliases[i].name)) {
+            *type = aliases[i].type;
+            return 0;
+        }
+    }
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(v1_types); i++) {
+        const char *name = av_stereo3d_type_name(v1_types[i]);
+        if (!av_strcasecmp(arg, name)) {
+            *type = v1_types[i];
+            return 0;
+        }
+    }
+
+    av_log(logctx, AV_LOG_ERROR,
+           "Invalid stereoscopic 3D layout '%s'. "
+           "Valid values are: 2d, mono, sbs, sbsl, side by side, "
+           "tb, tbl, top and bottom.\n", arg);
+    return AVERROR(EINVAL);
+}
+
+static int check_stereo3d_leftovers(Muxer *mux, const OptionsContext *o)
+{
+    AVFormatContext *oc = mux->fc;
+
+    for (int i = 0; i < o->stereo3ds.nb_opt; i++) {
+        const SpecifierOpt *so = &o->stereo3ds.opt[i];
+        int matched_video = 0, matched_other = 0;
+
+        /* Empty specifier applies to video only; ignore other stream types. */
+        if (!so->specifier[0])
+            continue;
+
+        for (unsigned j = 0; j < oc->nb_streams; j++) {
+            AVStream *st = oc->streams[j];
+            if (!stream_specifier_match(&so->stream_spec, oc, st, mux))
+                continue;
+            if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+                matched_video++;
+            else
+                matched_other++;
+        }
+
+        if (matched_other) {
+            av_log(mux, AV_LOG_ERROR,
+                   "-stereo3d is only valid for video streams (specifier '%s').\n",
+                   so->specifier);
+            return AVERROR(EINVAL);
+        }
+        if (!matched_video) {
+            av_log(mux, AV_LOG_ERROR,
+                   "Stream specifier '%s' for -stereo3d matches no video streams.\n",
+                   so->specifier);
+            return AVERROR(EINVAL);
+        }
+    }
+
+    return 0;
+}
+
 static int new_stream_video(Muxer *mux, const OptionsContext *o,
                             OutputStream *ost, int *keep_pix_fmt,
                             enum VideoSyncMethod *vsync_method)
@@ -553,6 +634,7 @@ static int new_stream_video(Muxer *mux, const OptionsContext *o,
     AVFormatContext *oc = mux->fc;
     AVStream *st;
     const char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL;
+    const char *stereo3d = NULL;
     int ret = 0;
 
     st  = ost->st;
@@ -585,6 +667,14 @@ static int new_stream_video(Muxer *mux, const OptionsContext *o,
         ost->frame_aspect_ratio = q;
     }
 
+    opt_match_per_stream_str(ost, &o->stereo3ds, oc, st, &stereo3d);
+    if (stereo3d) {
+        ret = parse_stereo3d_type(ost, stereo3d, &ms->stereo3d_type);
+        if (ret < 0)
+            return ret;
+        ms->stereo3d_set = 1;
+    }
+
     if (ost->enc) {
         AVCodecContext *video_enc = ost->enc->enc_ctx;
         const char *p = NULL, *fps_mode = NULL;
@@ -2053,7 +2143,7 @@ static int create_streams(Muxer *mux, const OptionsContext *o)
         return AVERROR(EINVAL);
     }
 
-    return 0;
+    return check_stereo3d_leftovers(mux, o);
 }
 
 static int setup_sync_queues(Muxer *mux, AVFormatContext *oc,
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index fdbc0fdc13..fd7260c3f7 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -1853,6 +1853,9 @@ const OptionDef options[] = {
     { "aspect",                     OPT_TYPE_STRING, OPT_VIDEO | OPT_PERSTREAM | OPT_OUTPUT,
         { .off = OFFSET(frame_aspect_ratios) },
         "set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)", "aspect" },
+    { "stereo3d",                   OPT_TYPE_STRING, OPT_VIDEO | OPT_PERSTREAM | OPT_OUTPUT,
+        { .off = OFFSET(stereo3ds) },
+        "set stereoscopic 3D layout metadata (does not rearrange pixels)", "layout" },
     { "pix_fmt",                    OPT_TYPE_STRING, OPT_VIDEO | OPT_EXPERT | OPT_PERSTREAM | OPT_INPUT | OPT_OUTPUT,
         { .off = OFFSET(frame_pix_fmts) },
         "set pixel format", "format" },
-- 
2.52.0


>From aefc1763c5099e954a326a14720c1e4f581364ef Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:53 +0800
Subject: [PATCH 2/6] avformat/movenc: warn when st3d/sv3d skipped due to
 strict compliance

Those boxes are written only with -strict unofficial. Fetch the side
data before that gate so a warning is logged only when a box would
have been written, matching dvcC/hvcE.

Whether the box is written is unchanged.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavformat/movenc.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 4c7868c5f8..380f18a859 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -2994,17 +2994,21 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
         mov_write_amve_tag(pb, track);
     }
 
-    if (track->mode == MODE_MP4 && mov->fc->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL) {
+    if (track->mode == MODE_MP4) {
         const AVPacketSideData *stereo_3d = av_packet_side_data_get(track->st->codecpar->coded_side_data,
                                                                     track->st->codecpar->nb_coded_side_data,
                                                                     AV_PKT_DATA_STEREO3D);
         const AVPacketSideData *spherical_mapping = av_packet_side_data_get(track->st->codecpar->coded_side_data,
                                                                             track->st->codecpar->nb_coded_side_data,
                                                                             AV_PKT_DATA_SPHERICAL);
-        if (stereo_3d)
+        if (stereo_3d && mov->fc->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL)
             mov_write_st3d_tag(s, pb, (AVStereo3D*)stereo_3d->data);
-        if (spherical_mapping)
+        else if (stereo_3d)
+            av_log(s, AV_LOG_WARNING, "Not writing 'st3d' box. Requires -strict unofficial.\n");
+        if (spherical_mapping && mov->fc->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL)
             mov_write_sv3d_tag(mov->fc, pb, (AVSphericalMapping*)spherical_mapping->data);
+        else if (spherical_mapping)
+            av_log(s, AV_LOG_WARNING, "Not writing 'sv3d' box. Requires -strict unofficial.\n");
     }
 
     if (track->mode == MODE_MOV || (track->mode == MODE_MP4 &&
-- 
2.52.0


>From 1a787eca6cf62da68b794cc5a807397f51dd8c2d Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:53 +0800
Subject: [PATCH 3/6] avformat/mov: keep st3d type when merging vexu eyes

vexu eyes/stri only records packed vs single-eye, not SBS/TB. Writing
both boxes (MP4 with -strict unofficial) let that UNSPEC type overwrite
the layout already parsed from st3d. Keep the more specific type so
the file round-trips in ffprobe.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavformat/mov.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index f53ce693f8..f6428c57e4 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7566,7 +7566,12 @@ static int mov_read_eyes(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     }
 
     sc->stereo3d->flags                           = flags;
-    sc->stereo3d->type                            = type;
+    /* eyes/stri only records packed vs single-eye, not SBS/TB. Keep a more
+     * specific type already set by st3d. */
+    if (type != AV_STEREO3D_UNSPEC)
+        sc->stereo3d->type = type;
+    else if (sc->stereo3d->type == AV_STEREO3D_2D)
+        sc->stereo3d->type = type;
     sc->stereo3d->view                            = view;
     sc->stereo3d->primary_eye                     = primary_eye;
     sc->stereo3d->baseline                        = baseline;
-- 
2.52.0


>From ae9264bb52f691a85682d8f6aca692004ee6375e Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:54 +0800
Subject: [PATCH 4/6] tests/fate: add a Matroska -stereo3d encode test

Encode vsynth through mpeg4 with -stereo3d:v sbsl and check that
ffprobe reports side-by-side packed Stereo 3D side data.

Signed-off-by: Jun Zhao <[email protected]>
---
 tests/fate/matroska.mak              |  8 ++++++++
 tests/ref/fate/matroska-stereo3d-sbs | 22 ++++++++++++++++++++++
 2 files changed, 30 insertions(+)
 create mode 100644 tests/ref/fate/matroska-stereo3d-sbs

diff --git a/tests/fate/matroska.mak b/tests/fate/matroska.mak
index 6a98845ca2..63c38a1043 100644
--- a/tests/fate/matroska.mak
+++ b/tests/fate/matroska.mak
@@ -339,6 +339,14 @@ fate-matroska-reenc-delete-metadata-filter-output: CMD = transcode matroska $(TA
 FATE_MATROSKA_FFMPEG_FFPROBE-$(call TRANSCODE, MPEG2VIDEO HEVC, NUT MATROSKA, SCALE_FILTER) += fate-matroska-reenc-chapter-nofilter
 fate-matroska-reenc-chapter-nofilter: CMD = transcode matroska $(TARGET_SAMPLES)/mkv/hdr10tags-both.mkv nut "-map 0:v:0 -vf scale=iw:ih -c:v mpeg2video -bitexact -metadata:c:0 NUMBER_OF_FRAMES=test" "-c copy -t 0.1" "-show_entries chapter_tags" "" "" "" null
 
+# Encode-path injection of -stereo3d into Matroska StereoMode via side data.
+FATE_MATROSKA_FFMPEG_FFPROBE-$(call ALLYES, FILE_PROTOCOL RAWVIDEO_DEMUXER MPEG4_ENCODER MATROSKA_MUXER MATROSKA_DEMUXER) \
+                          += fate-matroska-stereo3d-sbs
+fate-matroska-stereo3d-sbs: tests/data/vsynth1.yuv
+fate-matroska-stereo3d-sbs: CMD = transcode rawvideo $(TARGET_PATH)/tests/data/vsynth1.yuv matroska \
+  "-c:v mpeg4 -qscale:v 10 -stereo3d:v sbsl -frames:v 2" \
+  "-c:v copy" "-show_entries stream_side_data_list" "" "" "-s 352x288 -pix_fmt yuv420p"
+
 FATE_SAMPLES_AVCONV += $(FATE_MATROSKA-yes)
 FATE_SAMPLES_FFPROBE += $(FATE_MATROSKA_FFPROBE-yes)
 FATE_SAMPLES_FFMPEG_FFPROBE += $(FATE_MATROSKA_FFMPEG_FFPROBE-yes)
diff --git a/tests/ref/fate/matroska-stereo3d-sbs b/tests/ref/fate/matroska-stereo3d-sbs
new file mode 100644
index 0000000000..f4d5256037
--- /dev/null
+++ b/tests/ref/fate/matroska-stereo3d-sbs
@@ -0,0 +1,22 @@
+16938e7e91ea9492eef3654d3db9af57 *tests/data/fate/matroska-stereo3d-sbs.matroska
+38205 tests/data/fate/matroska-stereo3d-sbs.matroska
+#extradata 0:       30, 0x47ab0576
+#tb 0: 1/1000
+#media_type 0: video
+#codec_id 0: mpeg4
+#dimensions 0: 352x288
+#sar 0: 1/1
+0,          0,          0,       40,    27837, 0xd9809b60
+0,         40,         40,       40,     9806, 0xbebc2826, F=0x0
+[STREAM]
+[SIDE_DATA]
+side_data_type=Stereo 3D
+type=side by side
+inverted=0
+view=packed
+primary_eye=none
+baseline=0
+horizontal_disparity_adjustment=0/1
+horizontal_field_of_view=0/1
+[/SIDE_DATA]
+[/STREAM]
-- 
2.52.0


>From 4c17c2239cc1ef3d93d2b95c33af939698fdc924 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:54 +0800
Subject: [PATCH 5/6] tests/fate: add an MP4 -stereo3d encode test

Encode vsynth through mpeg4 into MP4 with -strict unofficial and
-stereo3d:v sbsl, and check that ffprobe round-trips side-by-side
packed Stereo 3D side data.

Signed-off-by: Jun Zhao <[email protected]>
---
 tests/fate/mov.mak                  |  8 ++++++++
 tests/ref/fate/mov-stereo3d-sbs-mp4 | 22 ++++++++++++++++++++++
 2 files changed, 30 insertions(+)
 create mode 100644 tests/ref/fate/mov-stereo3d-sbs-mp4

diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 1a81962626..ad80a39470 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -408,6 +408,14 @@ FATE_MOV_FFMPEG_SAMPLES-$(call REMUX, MP4 MOV, AAC_PARSER) \
                           += fate-mov-mp4-edst-remainder
 fate-mov-mp4-edst-remainder: CMD = stream_remux mov $(TARGET_SAMPLES)/audiomatch/tones_fdkaac_44100_stereo_aac_lc.m4a "" mp4 "" "" "-c:a copy"
 
+# Encode-path injection of -stereo3d into MP4 st3d (requires unofficial).
+FATE_MOV_FFMPEG_FFPROBE-$(call ALLYES, FILE_PROTOCOL RAWVIDEO_DEMUXER MPEG4_ENCODER MP4_MUXER MOV_DEMUXER) \
+                          += fate-mov-stereo3d-sbs-mp4
+fate-mov-stereo3d-sbs-mp4: tests/data/vsynth1.yuv
+fate-mov-stereo3d-sbs-mp4: CMD = transcode rawvideo $(TARGET_PATH)/tests/data/vsynth1.yuv mp4 \
+  "-c:v mpeg4 -qscale:v 10 -stereo3d:v sbsl -strict unofficial -frames:v 2" \
+  "-c:v copy" "-show_entries stream_side_data_list" "" "" "-s 352x288 -pix_fmt yuv420p"
+
 # format-level branding: major_brand, minor_version, compatible_brands should be deleted on re-encode
 FATE_MOV_FFMPEG_FFPROBE-$(call ENCDEC, AAC AAC, NUT MOV) += fate-mov-reenc-delete-format-metadata
 fate-mov-reenc-delete-format-metadata: CMD = transcode mov $(TARGET_SAMPLES)/cover_art/Owner-iTunes_9.0.3.15.m4a nut "-map 0:a:0 -c:a aac -bitexact -t 0.1" "-c copy -t 0.1" "-show_entries format_tags" "" "" "" null
diff --git a/tests/ref/fate/mov-stereo3d-sbs-mp4 b/tests/ref/fate/mov-stereo3d-sbs-mp4
new file mode 100644
index 0000000000..68bae7ce2a
--- /dev/null
+++ b/tests/ref/fate/mov-stereo3d-sbs-mp4
@@ -0,0 +1,22 @@
+bc24ade91fb35a16a47dcb5664a36701 *tests/data/fate/mov-stereo3d-sbs-mp4.mp4
+38520 tests/data/fate/mov-stereo3d-sbs-mp4.mp4
+#extradata 0:       30, 0x47ab0576
+#tb 0: 1/12800
+#media_type 0: video
+#codec_id 0: mpeg4
+#dimensions 0: 352x288
+#sar 0: 1/1
+0,          0,          0,      512,    27837, 0xd9809b60
+0,        512,        512,      512,     9806, 0xbebc2826, F=0x0
+[STREAM]
+[SIDE_DATA]
+side_data_type=Stereo 3D
+type=side by side
+inverted=0
+view=packed
+primary_eye=none
+baseline=0
+horizontal_disparity_adjustment=0/1
+horizontal_field_of_view=0/1
+[/SIDE_DATA]
+[/STREAM]
-- 
2.52.0


>From da25a23aed39c4ac8833bf67682e7b7c712e78dd Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 28 Aug 2026 10:31:54 +0800
Subject: [PATCH 6/6] doc: document the -stereo3d option

Describe the CLI values, that the option writes layout metadata
rather than rearranging pixels, and how MP4/MOV/Matroska consume
the resulting side data.

Signed-off-by: Jun Zhao <[email protected]>
---
 doc/ffmpeg.texi | 41 +++++++++++++++++++++++++++++++++++++++++
 doc/muxers.texi | 18 ++++++++++++++++++
 2 files changed, 59 insertions(+)

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 1cbb1ccbdd..e4fb3cbec3 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1590,6 +1590,47 @@ If used together with @option{-vcodec copy}, it will affect the aspect ratio
 stored at container level, but not the aspect ratio stored in encoded
 frames, if it exists.
 
+@item -stereo3d[:@var{stream_specifier}] @var{layout} (@emph{output,per-stream})
+Set stereoscopic 3D layout metadata on the output stream. This does
+@emph{not} rearrange pixels; use @option{-vf stereo3d} for that.
+
+The metadata is stored as @code{AV_PKT_DATA_STEREO3D} side data on the
+output stream. Muxers that already consume that side data will write the
+corresponding container boxes or elements, including:
+
+@itemize
+@item
+MP4 Google spatial-media @code{st3d} (requires @option{-strict unofficial})
+@item
+QuickTime MOV Apple @code{vexu} eyes (no @code{st3d})
+@item
+Matroska @code{StereoMode} (unless @option{-metadata stereo_mode} is also set,
+in which case metadata still takes priority)
+@end itemize
+
+@var{layout} is case-insensitive. The following values are accepted:
+
+@table @samp
+@item 2d
+@itemx mono
+Monoscopic video.
+@item sbs
+@itemx sbsl
+@itemx side by side
+Side-by-side, left eye on the left.
+@item tb
+@itemx tbl
+@itemx top and bottom
+Top-and-bottom, left eye on top.
+@end table
+
+Inverted layouts such as @samp{sbsr} are not supported. An empty stream
+specifier applies to video streams only and is ignored for audio and
+other types. An explicit specifier on a non-video stream (for example
+@option{-stereo3d:a}) is an error.
+
+Recommended form: @option{-stereo3d:v sbsl}.
+
 @item -display_rotation[:@var{stream_specifier}] @var{rotation} (@emph{input,per-stream})
 Set video rotation metadata.
 
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 99584e2b8d..33545fcb7d 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -377,6 +377,21 @@ on MPEG-4 Part 14 format with a few incompatible variants, used to
 play files on PlayStation devices.
 @end table
 
+@subsection Stereoscopic 3D
+
+When the output stream has stereoscopic 3D side data (see the
+@option{-stereo3d} option), these muxers write:
+
+@itemize
+@item
+MP4: Google spatial-media @code{st3d} and Apple @code{vexu}, both
+requiring @option{-strict unofficial}. A missing unofficial compliance
+level skips those boxes and logs a warning naming the box that was not
+written.
+@item
+MOV: Apple @code{vexu} eyes only. @code{st3d} is not written.
+@end itemize
+
 @subsection Fragmentation
 
 The @samp{mov}, @samp{mp4}, and @samp{ismv} muxers support
@@ -2867,6 +2882,9 @@ French).
 
 @item stereo_mode
 Set stereo 3D video layout of two views in a single video track.
+If this metadata is unset, the muxer falls back to stereoscopic
+side data on the stream (see the @option{-stereo3d} option).
+Metadata takes priority when both are present.
 
 The following values are recognized:
 @table @samp
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.