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]

Reply via email to