PR #23123 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23123
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23123.patch


From f3220848172f0c20896bbbe81f88d12e8006046b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 16 May 2026 19:46:53 +0200
Subject: [PATCH 1/2] avformat: add an Dolby Vision stream group
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 doc/APIchanges            |  4 ++++
 libavformat/avformat.c    |  5 +++++
 libavformat/avformat.h    | 21 +++++++++++++++++++++
 libavformat/matroskadec.c |  4 ++++
 libavformat/options.c     | 25 +++++++++++++++++++++++++
 libavformat/version.h     |  2 +-
 6 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index 28e7b7b31c..8ed27c47bd 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,10 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2026-05-16 - xxxxxxxxxx - lavf 62.17.100 - avformat.h
+  Add AV_STREAM_GROUP_PARAMS_DOLBY_VISION
+  Add AVStreamGroupDolbyVision
+
 2026-05-16 - xxxxxxxxxxx - lavf 62.16.100 - avformat.h
   Add AVFMT_FIXED_FRAMESIZE.
 
diff --git a/libavformat/avformat.c b/libavformat/avformat.c
index 3bc79a3592..02c5a52182 100644
--- a/libavformat/avformat.c
+++ b/libavformat/avformat.c
@@ -106,6 +106,10 @@ void ff_free_stream_group(AVStreamGroup **pstg)
         av_opt_free(stg->params.lcevc);
         av_freep(&stg->params.lcevc);
         break;
+    case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
+        av_opt_free(stg->params.dovi);
+        av_freep(&stg->params.dovi);
+        break;
     default:
         break;
     }
@@ -264,6 +268,7 @@ const char *avformat_stream_group_name(enum 
AVStreamGroupParamsType type)
     case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION:     return "IAMF Mix 
Presentation";
     case AV_STREAM_GROUP_PARAMS_TILE_GRID:                 return "Tile Grid";
     case AV_STREAM_GROUP_PARAMS_LCEVC:                     return "LCEVC 
(Split video and enhancement)";
+    case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:              return "Dolby 
Vision (Split base and enhancement layer)";
     }
     return NULL;
 }
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index d6740fcf5d..95e1043b51 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -1087,12 +1087,32 @@ typedef struct AVStreamGroupLCEVC {
     int height;
 } AVStreamGroupLCEVC;
 
+/**
+ * AVStreamGroupDolbyVision is meant to define the relation between a Dolby
+ * Vision base layer video stream and a separate enhancement layer video
+ * stream (Profile 7 dual-layer encoding).
+ *
+ * The group is expected to contain exactly two streams: the base layer and
+ * the enhancement layer, both video streams. The base layer carries the
+ * AVDOVIDecoderConfigurationRecord side data. The enhancement layer is
+ * identified by @ref el_index.
+ */
+typedef struct AVStreamGroupDolbyVision {
+    const AVClass *av_class;
+
+    /**
+     * Index of the Dolby Vision enhancement layer stream in AVStreamGroup.
+     */
+    unsigned int el_index;
+} AVStreamGroupDolbyVision;
+
 enum AVStreamGroupParamsType {
     AV_STREAM_GROUP_PARAMS_NONE,
     AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT,
     AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION,
     AV_STREAM_GROUP_PARAMS_TILE_GRID,
     AV_STREAM_GROUP_PARAMS_LCEVC,
+    AV_STREAM_GROUP_PARAMS_DOLBY_VISION,
 };
 
 struct AVIAMFAudioElement;
@@ -1135,6 +1155,7 @@ typedef struct AVStreamGroup {
         struct AVIAMFMixPresentation *iamf_mix_presentation;
         struct AVStreamGroupTileGrid *tile_grid;
         struct AVStreamGroupLCEVC *lcevc;
+        struct AVStreamGroupDolbyVision *dovi;
     } params;
 
     /**
diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c
index bc584abc75..5eccaea3ed 100644
--- a/libavformat/matroskadec.c
+++ b/libavformat/matroskadec.c
@@ -3465,6 +3465,10 @@ static int matroska_read_header(AVFormatContext *s)
 
     matroska_convert_tags(s);
 
+    res = mkv_parse_dovi_streams(s);
+    if (res < 0)
+        return res;
+
     return 0;
 }
 
diff --git a/libavformat/options.c b/libavformat/options.c
index 9365a16e1d..15ecfa9ac6 100644
--- a/libavformat/options.c
+++ b/libavformat/options.c
@@ -366,6 +366,19 @@ static const AVClass lcevc_class = {
     .option     = lcevc_options,
 };
 
+#define OFFSET(x) offsetof(AVStreamGroupDolbyVision, x)
+static const AVOption dovi_options[] = {
+    { "el_index", "Index of the Dolby Vision enhancement layer stream within 
the group", OFFSET(el_index), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS 
},
+    { NULL },
+};
+#undef OFFSET
+
+static const AVClass dovi_class = {
+    .class_name = "AVStreamGroupDolbyVision",
+    .version    = LIBAVUTIL_VERSION_INT,
+    .option     = dovi_options,
+};
+
 static void *stream_group_child_next(void *obj, void *prev)
 {
     AVStreamGroup *stg = obj;
@@ -379,6 +392,8 @@ static void *stream_group_child_next(void *obj, void *prev)
             return stg->params.tile_grid;
         case AV_STREAM_GROUP_PARAMS_LCEVC:
             return stg->params.lcevc;
+        case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
+            return stg->params.dovi;
         default:
             break;
         }
@@ -409,6 +424,9 @@ static const AVClass *stream_group_child_iterate(void 
**opaque)
     case AV_STREAM_GROUP_PARAMS_LCEVC:
         ret = &lcevc_class;
         break;
+    case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
+        ret = &dovi_class;
+        break;
     default:
         break;
     }
@@ -485,6 +503,13 @@ AVStreamGroup 
*avformat_stream_group_create(AVFormatContext *s,
         stg->params.lcevc->av_class = &lcevc_class;
         av_opt_set_defaults(stg->params.lcevc);
         break;
+    case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
+        stg->params.dovi = av_mallocz(sizeof(*stg->params.dovi));
+        if (!stg->params.dovi)
+            goto fail;
+        stg->params.dovi->av_class = &dovi_class;
+        av_opt_set_defaults(stg->params.dovi);
+        break;
     default:
         goto fail;
     }
diff --git a/libavformat/version.h b/libavformat/version.h
index 9e1f484db4..2a28a3bf40 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -31,7 +31,7 @@
 
 #include "version_major.h"
 
-#define LIBAVFORMAT_VERSION_MINOR  16
+#define LIBAVFORMAT_VERSION_MINOR  17
 #define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
-- 
2.52.0


From c575ab1a2b34bb3b716716403e236b7b71e8a6ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 16 May 2026 19:52:06 +0200
Subject: [PATCH 2/2] avformat/mov: create Dolby Vision stream group

---
 libavformat/isom.h |  1 +
 libavformat/mov.c  | 81 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+)

diff --git a/libavformat/isom.h b/libavformat/isom.h
index d7e138585a..543e1cd470 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -435,6 +435,7 @@ void ff_mp4_parse_es_descr(AVIOContext *pb, int *es_id);
 #define MOV_SAMPLE_DEPENDENCY_NO      0x2
 
 #define MOV_TREF_FLAG_ENHANCEMENT     0x1
+#define MOV_TREF_FLAG_DOVI_EL         0x2
 
 #define TAG_IS_AVCI(tag)                    \
     ((tag) == MKTAG('a', 'i', '5', 'p') ||  \
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 0d982e5a79..ed52968ba9 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -2571,6 +2571,30 @@ static int mov_read_sbas(MOVContext* c, AVIOContext* pb, 
MOVAtom atom)
     return 0;
 }
 
+static int mov_read_vdep(MOVContext* c, AVIOContext* pb, MOVAtom atom)
+{
+    AVStream* st;
+    MOVStreamContext* sc;
+
+    if (c->fc->nb_streams < 1)
+        return 0;
+
+    /* Dolby Vision Profile 7 places a single vdep tref on the EL track
+     * pointing to the BL track. Anything beyond the first reference is
+     * out of spec and ignored. */
+    if (atom.size > 4) {
+        av_log(c->fc, AV_LOG_WARNING,
+               "More than one vdep reference is not supported.\n");
+    }
+
+    st = c->fc->streams[c->fc->nb_streams - 1];
+    sc = st->priv_data;
+    sc->tref_id = avio_rb32(pb);
+    sc->tref_flags |= MOV_TREF_FLAG_DOVI_EL;
+
+    return 0;
+}
+
 /**
  * An strf atom is a BITMAPINFOHEADER struct. This struct is 40 bytes itself,
  * but can have extradata appended at the end after the 40 bytes belonging
@@ -9560,6 +9584,7 @@ static const MOVParseTableEntry mov_default_parse_table[] 
= {
 { MKTAG('p','a','s','p'), mov_read_pasp },
 { MKTAG('c','l','a','p'), mov_read_clap },
 { MKTAG('s','b','a','s'), mov_read_sbas },
+{ MKTAG('v','d','e','p'), mov_read_vdep },
 { MKTAG('s','i','d','x'), mov_read_sidx },
 { MKTAG('s','t','b','l'), mov_read_default },
 { MKTAG('s','t','c','o'), mov_read_stco },
@@ -10868,6 +10893,57 @@ static int mov_parse_lcevc_streams(AVFormatContext *s)
     return 0;
 }
 
+static int mov_parse_dovi_streams(AVFormatContext *s)
+{
+    int err;
+
+    if (s->nb_streams <= 1)
+        return 0;
+
+    for (int i = 0; i < s->nb_streams; i++) {
+        AVStreamGroup *stg;
+        AVStream *st = s->streams[i];
+        AVStream *st_base;
+        MOVStreamContext *sc = st->priv_data;
+        int j = 0;
+
+        /* Find a Dolby Vision Profile 7 enhancement-layer stream. */
+        if (st->codecpar->codec_id != AV_CODEC_ID_HEVC ||
+            !(sc->tref_flags & MOV_TREF_FLAG_DOVI_EL))
+            continue;
+
+        stg = avformat_stream_group_create(s, 
AV_STREAM_GROUP_PARAMS_DOLBY_VISION, NULL);
+        if (!stg)
+            return AVERROR(ENOMEM);
+
+        stg->id = st->id;
+
+        while ((st_base = mov_find_reference_track(s, st, j))) {
+            err = avformat_stream_group_add_stream(stg, st_base);
+            if (err < 0)
+                return err;
+
+            j = st_base->index + 1;
+        }
+        if (!j) {
+            int loglevel = (s->error_recognition & AV_EF_EXPLODE) ? 
AV_LOG_ERROR : AV_LOG_WARNING;
+            av_log(s, loglevel, "Failed to find base layer for Dolby Vision EL 
stream\n");
+            ff_remove_stream_group(s, stg);
+            if (s->error_recognition & AV_EF_EXPLODE)
+                return AVERROR_INVALIDDATA;
+            continue;
+        }
+
+        err = avformat_stream_group_add_stream(stg, st);
+        if (err < 0)
+            return err;
+
+        stg->params.dovi->el_index = stg->nb_streams - 1;
+    }
+
+    return 0;
+}
+
 static void fix_stream_ids(AVFormatContext *s)
 {
     int highest_id = 0, lowest_iamf_id = INT_MAX;
@@ -10989,6 +11065,11 @@ static int mov_read_header(AVFormatContext *s)
     if (err < 0)
         return err;
 
+    /* Create Dolby Vision Profile 7 stream groups. */
+    err = mov_parse_dovi_streams(s);
+    if (err < 0)
+        return err;
+
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *st = s->streams[i];
         FFStream *const sti = ffstream(st);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to