diff mbox series

[FFmpeg-devel,1/5] avformat: introduce AVStreamGroup

Message ID 20231121211442.8723-2-jamrial@gmail.com
State New
Headers show
Series avformat: introduce AVStreamGroup | expand

Checks

Context Check Description
yinshiyou/make_fate_loongarch64 success Make fate finished
yinshiyou/make_loongarch64 warning New warnings during build
andriy/make_fate_x86 success Make fate finished
andriy/make_x86 warning New warnings during build

Commit Message

James Almer Nov. 21, 2023, 9:14 p.m. UTC
Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/avformat.c | 178 +++++++++++++++++++++++++++++++++++++++--
 libavformat/avformat.h | 163 +++++++++++++++++++++++++++++++++++++
 libavformat/dump.c     |  33 ++++++--
 libavformat/internal.h |  33 ++++++++
 libavformat/options.c  |  90 +++++++++++++++++++++
 5 files changed, 486 insertions(+), 11 deletions(-)

Comments

Marton Balint Nov. 21, 2023, 9:57 p.m. UTC | #1
On Tue, 21 Nov 2023, James Almer wrote:

> Signed-off-by: James Almer <jamrial@gmail.com>
> ---
> libavformat/avformat.c | 178 +++++++++++++++++++++++++++++++++++++++--
> libavformat/avformat.h | 163 +++++++++++++++++++++++++++++++++++++
> libavformat/dump.c     |  33 ++++++--
> libavformat/internal.h |  33 ++++++++
> libavformat/options.c  |  90 +++++++++++++++++++++
> 5 files changed, 486 insertions(+), 11 deletions(-)
>
> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
> index 5b8bb7879e..4e31924c71 100644
> --- a/libavformat/avformat.c
> +++ b/libavformat/avformat.c

[...]

> @@ -493,6 +524,46 @@ static int match_stream_specifier(const AVFormatContext *s, const AVStream *st,
>                 match = 0;
>             if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC))
>                 match = 0;
> +        } else if (*spec == 'g' && *(spec + 1) == ':') {
> +            int64_t group_idx = -1, group_id = -1;
> +            int found = 0;
> +            char *endptr;
> +            spec += 2;
> +            if (*spec == '#' || (*spec == 'i' && *(spec + 1) == ':')) {
> +                spec += 1 + (*spec == 'i');
> +                group_id = strtol(spec, &endptr, 0);
> +                if (spec == endptr || (*endptr && *endptr++ != ':'))
> +                    return AVERROR(EINVAL);
> +                spec = endptr;
> +            } else {
> +                group_idx = strtol(spec, &endptr, 0);
> +                /* Disallow empty id and make sure that if we are not at the end, then another specifier must follow. */
> +                if (spec == endptr || (*endptr && *endptr++ != ':'))
> +                    return AVERROR(EINVAL);
> +                spec = endptr;
> +            }
> +            if (match) {
> +                if (group_id > 0) {
> +                    for (unsigned i = 0; i < s->nb_stream_groups; i++) {
> +                        if (group_id == s->stream_groups[i]->id) {
> +                            group_idx = i;
> +                            break;
> +                        }
> +                    }
> +			    }
> +                if (group_idx < 0 || group_idx > s->nb_stream_groups)
> +                    return AVERROR(EINVAL);
> +                for (unsigned j = 0; j < s->stream_groups[group_idx]->nb_streams; j++) {
> +                    if (st->index == s->stream_groups[group_idx]->streams[j]->index) {
> +                        found = 1;
> +                        if (g)
> +                            *g = s->stream_groups[group_idx];
> +                        break;
> +                    }
> +                }
> +            }
> +            if (!found)
> +                match = 0;

The documentation for the stream specifier changes are missing from
doc/fftools-common-opts.texi. You should update relevant docs preferably
in this patch...

[...]

> --- a/libavformat/avformat.h
> +++ b/libavformat/avformat.h
> @@ -1018,6 +1018,77 @@ typedef struct AVStream {
>     int pts_wrap_bits;
> } AVStream;
>
> +enum AVStreamGroupParamsType {
> +    AV_STREAM_GROUP_PARAMS_NONE,
> +};
> +
> +typedef struct AVStreamGroup {
> +    /**
> +     * A class for @ref avoptions. Set by avformat_stream_group_create().
> +     */
> +    const AVClass *av_class;
> +
> +    void *priv_data;
> +
> +    /**
> +     * Group index in AVFormatContext.
> +     */
> +    unsigned int index;
> +
> +    /**
> +     * Group type-specific group ID.
> +     *
> +     * decoding: set by libavformat
> +     * encoding: may set by the user, replaced by libavformat if left unset
> +     */
> +    int64_t id;

I don't quite get the encoding case where libavformat replaces the unset 
ID... Also, what is unset? 0 or -1? Because as far as I see 0 is the 
default, maybe -1 is better if unset has some special meaning?

[...]

> diff --git a/libavformat/internal.h b/libavformat/internal.h
> index 7702986c9c..c6181683ef 100644
> --- a/libavformat/internal.h
> +++ b/libavformat/internal.h
> @@ -202,6 +202,7 @@ typedef struct FFStream {
>      */
>     AVStream pub;
>
> +    AVFormatContext *fmtctx;
>     /**
>      * Set to 1 if the codec allows reordering, so pts can be different
>      * from dts.
> @@ -427,6 +428,26 @@ static av_always_inline const FFStream *cffstream(const AVStream *st)
>     return (const FFStream*)st;
> }
>
> +typedef struct FFStreamGroup {
> +    /**
> +     * The public context.
> +     */
> +    AVStreamGroup pub;
> +
> +    AVFormatContext *fmtctx;

If the only purpose of storing this is sanity checking if the user calls 
API with matching stream and stream group, then maybe better to make it 
ptrdiff_t, so nobody will try to misuse this to access the format context 
or do logging, etc...

Regards,
Marton
James Almer Nov. 25, 2023, 3:37 p.m. UTC | #2
On 11/21/2023 6:57 PM, Marton Balint wrote:
> 
> 
> On Tue, 21 Nov 2023, James Almer wrote:
> 
>> Signed-off-by: James Almer <jamrial@gmail.com>
>> ---
>> libavformat/avformat.c | 178 +++++++++++++++++++++++++++++++++++++++--
>> libavformat/avformat.h | 163 +++++++++++++++++++++++++++++++++++++
>> libavformat/dump.c     |  33 ++++++--
>> libavformat/internal.h |  33 ++++++++
>> libavformat/options.c  |  90 +++++++++++++++++++++
>> 5 files changed, 486 insertions(+), 11 deletions(-)
>>
>> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
>> index 5b8bb7879e..4e31924c71 100644
>> --- a/libavformat/avformat.c
>> +++ b/libavformat/avformat.c
> 
> [...]
> 
>> @@ -493,6 +524,46 @@ static int match_stream_specifier(const 
>> AVFormatContext *s, const AVStream *st,
>>                 match = 0;
>>             if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC))
>>                 match = 0;
>> +        } else if (*spec == 'g' && *(spec + 1) == ':') {
>> +            int64_t group_idx = -1, group_id = -1;
>> +            int found = 0;
>> +            char *endptr;
>> +            spec += 2;
>> +            if (*spec == '#' || (*spec == 'i' && *(spec + 1) == ':')) {
>> +                spec += 1 + (*spec == 'i');
>> +                group_id = strtol(spec, &endptr, 0);
>> +                if (spec == endptr || (*endptr && *endptr++ != ':'))
>> +                    return AVERROR(EINVAL);
>> +                spec = endptr;
>> +            } else {
>> +                group_idx = strtol(spec, &endptr, 0);
>> +                /* Disallow empty id and make sure that if we are not 
>> at the end, then another specifier must follow. */
>> +                if (spec == endptr || (*endptr && *endptr++ != ':'))
>> +                    return AVERROR(EINVAL);
>> +                spec = endptr;
>> +            }
>> +            if (match) {
>> +                if (group_id > 0) {
>> +                    for (unsigned i = 0; i < s->nb_stream_groups; i++) {
>> +                        if (group_id == s->stream_groups[i]->id) {
>> +                            group_idx = i;
>> +                            break;
>> +                        }
>> +                    }
>> +                }
>> +                if (group_idx < 0 || group_idx > s->nb_stream_groups)
>> +                    return AVERROR(EINVAL);
>> +                for (unsigned j = 0; j < 
>> s->stream_groups[group_idx]->nb_streams; j++) {
>> +                    if (st->index == 
>> s->stream_groups[group_idx]->streams[j]->index) {
>> +                        found = 1;
>> +                        if (g)
>> +                            *g = s->stream_groups[group_idx];
>> +                        break;
>> +                    }
>> +                }
>> +            }
>> +            if (!found)
>> +                match = 0;
> 
> The documentation for the stream specifier changes are missing from
> doc/fftools-common-opts.texi. You should update relevant docs preferably
> in this patch...

Will do.

> 
> [...]
> 
>> --- a/libavformat/avformat.h
>> +++ b/libavformat/avformat.h
>> @@ -1018,6 +1018,77 @@ typedef struct AVStream {
>>     int pts_wrap_bits;
>> } AVStream;
>>
>> +enum AVStreamGroupParamsType {
>> +    AV_STREAM_GROUP_PARAMS_NONE,
>> +};
>> +
>> +typedef struct AVStreamGroup {
>> +    /**
>> +     * A class for @ref avoptions. Set by 
>> avformat_stream_group_create().
>> +     */
>> +    const AVClass *av_class;
>> +
>> +    void *priv_data;
>> +
>> +    /**
>> +     * Group index in AVFormatContext.
>> +     */
>> +    unsigned int index;
>> +
>> +    /**
>> +     * Group type-specific group ID.
>> +     *
>> +     * decoding: set by libavformat
>> +     * encoding: may set by the user, replaced by libavformat if left 
>> unset
>> +     */
>> +    int64_t id;
> 
> I don't quite get the encoding case where libavformat replaces the unset 
> ID... Also, what is unset? 0 or -1? Because as far as I see 0 is the 
> default, maybe -1 is better if unset has some special meaning?

I copied the doxy from the relevant AVStream field. It may be bogus 
there though, seeing for example how the mp4 muxer has an option to use 
the ids when muxing, but ultimately trusting them and not changing them.

Can remove that part here and just leave it as may be set by the user.

> 
> [...]
> 
>> diff --git a/libavformat/internal.h b/libavformat/internal.h
>> index 7702986c9c..c6181683ef 100644
>> --- a/libavformat/internal.h
>> +++ b/libavformat/internal.h
>> @@ -202,6 +202,7 @@ typedef struct FFStream {
>>      */
>>     AVStream pub;
>>
>> +    AVFormatContext *fmtctx;
>>     /**
>>      * Set to 1 if the codec allows reordering, so pts can be different
>>      * from dts.
>> @@ -427,6 +428,26 @@ static av_always_inline const FFStream 
>> *cffstream(const AVStream *st)
>>     return (const FFStream*)st;
>> }
>>
>> +typedef struct FFStreamGroup {
>> +    /**
>> +     * The public context.
>> +     */
>> +    AVStreamGroup pub;
>> +
>> +    AVFormatContext *fmtctx;
> 
> If the only purpose of storing this is sanity checking if the user calls 
> API with matching stream and stream group, then maybe better to make it 
> ptrdiff_t, so nobody will try to misuse this to access the format 
> context or do logging, etc...

It's the same in AVStream. And why couldn't a module access it for 
logging or similar? It's internal, not exposed, and the group is 
strictly a part of its parent AVFormatContext.

> 
> Regards,
> Marton
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
diff mbox series

Patch

diff --git a/libavformat/avformat.c b/libavformat/avformat.c
index 5b8bb7879e..4e31924c71 100644
--- a/libavformat/avformat.c
+++ b/libavformat/avformat.c
@@ -80,6 +80,25 @@  FF_ENABLE_DEPRECATION_WARNINGS
     av_freep(pst);
 }
 
+void ff_free_stream_group(AVStreamGroup **pstg)
+{
+    AVStreamGroup *stg = *pstg;
+
+    if (!stg)
+        return;
+
+    av_freep(&stg->streams);
+    av_dict_free(&stg->metadata);
+    av_freep(&stg->priv_data);
+    switch (stg->type) {
+    // Structs in the union are freed here
+    default:
+        break;
+    }
+
+    av_freep(pstg);
+}
+
 void ff_remove_stream(AVFormatContext *s, AVStream *st)
 {
     av_assert0(s->nb_streams>0);
@@ -88,6 +107,14 @@  void ff_remove_stream(AVFormatContext *s, AVStream *st)
     ff_free_stream(&s->streams[ --s->nb_streams ]);
 }
 
+void ff_remove_stream_group(AVFormatContext *s, AVStreamGroup *stg)
+{
+    av_assert0(s->nb_stream_groups > 0);
+    av_assert0(s->stream_groups[ s->nb_stream_groups - 1 ] == stg);
+
+    ff_free_stream_group(&s->stream_groups[ --s->nb_stream_groups ]);
+}
+
 /* XXX: suppress the packet queue */
 void ff_flush_packet_queue(AVFormatContext *s)
 {
@@ -118,6 +145,9 @@  void avformat_free_context(AVFormatContext *s)
 
     for (unsigned i = 0; i < s->nb_streams; i++)
         ff_free_stream(&s->streams[i]);
+    for (unsigned i = 0; i < s->nb_stream_groups; i++)
+        ff_free_stream_group(&s->stream_groups[i]);
+    s->nb_stream_groups = 0;
     s->nb_streams = 0;
 
     for (unsigned i = 0; i < s->nb_programs; i++) {
@@ -138,6 +168,7 @@  void avformat_free_context(AVFormatContext *s)
     av_dict_free(&si->id3v2_meta);
     av_packet_free(&si->pkt);
     av_packet_free(&si->parse_pkt);
+    av_freep(&s->stream_groups);
     av_freep(&s->streams);
     ff_flush_packet_queue(s);
     av_freep(&s->url);
@@ -464,7 +495,7 @@  int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
  */
 static int match_stream_specifier(const AVFormatContext *s, const AVStream *st,
                                   const char *spec, const char **indexptr,
-                                  const AVProgram **p)
+                                  const AVStreamGroup **g, const AVProgram **p)
 {
     int match = 1;                      /* Stores if the specifier matches so far. */
     while (*spec) {
@@ -493,6 +524,46 @@  static int match_stream_specifier(const AVFormatContext *s, const AVStream *st,
                 match = 0;
             if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC))
                 match = 0;
+        } else if (*spec == 'g' && *(spec + 1) == ':') {
+            int64_t group_idx = -1, group_id = -1;
+            int found = 0;
+            char *endptr;
+            spec += 2;
+            if (*spec == '#' || (*spec == 'i' && *(spec + 1) == ':')) {
+                spec += 1 + (*spec == 'i');
+                group_id = strtol(spec, &endptr, 0);
+                if (spec == endptr || (*endptr && *endptr++ != ':'))
+                    return AVERROR(EINVAL);
+                spec = endptr;
+            } else {
+                group_idx = strtol(spec, &endptr, 0);
+                /* Disallow empty id and make sure that if we are not at the end, then another specifier must follow. */
+                if (spec == endptr || (*endptr && *endptr++ != ':'))
+                    return AVERROR(EINVAL);
+                spec = endptr;
+            }
+            if (match) {
+                if (group_id > 0) {
+                    for (unsigned i = 0; i < s->nb_stream_groups; i++) {
+                        if (group_id == s->stream_groups[i]->id) {
+                            group_idx = i;
+                            break;
+                        }
+                    }
+			    }
+                if (group_idx < 0 || group_idx > s->nb_stream_groups)
+                    return AVERROR(EINVAL);
+                for (unsigned j = 0; j < s->stream_groups[group_idx]->nb_streams; j++) {
+                    if (st->index == s->stream_groups[group_idx]->streams[j]->index) {
+                        found = 1;
+                        if (g)
+                            *g = s->stream_groups[group_idx];
+                        break;
+                    }
+                }
+            }
+            if (!found)
+                match = 0;
         } else if (*spec == 'p' && *(spec + 1) == ':') {
             int prog_id;
             int found = 0;
@@ -592,9 +663,10 @@  int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
     char *endptr;
     const char *indexptr = NULL;
     const AVProgram *p = NULL;
+    const AVStreamGroup *g = NULL;
     int nb_streams;
 
-    ret = match_stream_specifier(s, st, spec, &indexptr, &p);
+    ret = match_stream_specifier(s, st, spec, &indexptr, &g, &p);
     if (ret < 0)
         goto error;
 
@@ -612,10 +684,11 @@  int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
         return (index == st->index);
 
     /* If we requested a matching stream index, we have to ensure st is that. */
-    nb_streams = p ? p->nb_stream_indexes : s->nb_streams;
+    nb_streams = g ? g->nb_streams : (p ? p->nb_stream_indexes : s->nb_streams);
     for (int i = 0; i < nb_streams && index >= 0; i++) {
-        const AVStream *candidate = s->streams[p ? p->stream_index[i] : i];
-        ret = match_stream_specifier(s, candidate, spec, NULL, NULL);
+        unsigned idx = g ? g->streams[i]->index : (p ? p->stream_index[i] : i);
+        const AVStream *candidate = s->streams[idx];
+        ret = match_stream_specifier(s, candidate, spec, NULL, NULL, NULL);
         if (ret < 0)
             goto error;
         if (ret > 0 && index-- == 0 && st == candidate)
@@ -629,6 +702,101 @@  error:
     return ret;
 }
 
+
+/**
+ * Matches a stream specifier (but ignores requested index).
+ *
+ * @param indexptr set to point to the requested stream index if there is one
+ *
+ * @return <0 on error
+ *         0  if st is NOT a matching stream
+ *         >0 if st is a matching stream
+ */
+static int match_stream_group_specifier(const AVFormatContext *s, const AVStreamGroup *stg,
+                                        const char *spec, const char **indexptr)
+{
+    int match = 1;                      /* Stores if the specifier matches so far. */
+    while (*spec) {
+        if (*spec <= '9' && *spec >= '0') { /* opt:index */
+            if (indexptr)
+                *indexptr = spec;
+            return match;
+        } else if (*spec == 't' && *(spec + 1) == ':') {
+            int64_t group_type = -1;
+            int found = 0;
+            char *endptr;
+            spec += 2;
+            group_type = strtol(spec, &endptr, 0);
+            /* Disallow empty type and make sure that if we are not at the end, then another specifier must follow. */
+            if (spec == endptr || (*endptr && *endptr++ != ':'))
+                return AVERROR(EINVAL);
+            spec = endptr;
+            if (match && group_type > 0) {
+                for (unsigned i = 0; i < s->nb_stream_groups; i++) {
+                    if (group_type == s->stream_groups[i]->type) {
+                        found = 1;
+                        break;
+                    }
+			    }
+            }
+            if (!found)
+                match = 0;
+        } else if (*spec == '#' ||
+                   (*spec == 'i' && *(spec + 1) == ':')) {
+            int group_id;
+            char *endptr;
+            spec += 1 + (*spec == 'i');
+            group_id = strtol(spec, &endptr, 0);
+            if (spec == endptr || *endptr)                /* Disallow empty id and make sure we are at the end. */
+                return AVERROR(EINVAL);
+            return match && (group_id == stg->id);
+        }
+    }
+
+    return match;
+}
+
+int avformat_match_stream_group_specifier(AVFormatContext *s, AVStreamGroup *stg,
+                                          const char *spec)
+{
+    int ret, index;
+    char *endptr;
+    const char *indexptr = NULL;
+
+    ret = match_stream_group_specifier(s, stg, spec, &indexptr);
+    if (ret < 0)
+        goto error;
+
+    if (!indexptr)
+        return ret;
+
+    index = strtol(indexptr, &endptr, 0);
+    if (*endptr) {                  /* We can't have anything after the requested index. */
+        ret = AVERROR(EINVAL);
+        goto error;
+    }
+
+    /* This is not really needed but saves us a loop for simple stream index specifiers. */
+    if (spec == indexptr)
+        return (index == stg->index);
+
+    /* If we requested a matching stream index, we have to ensure stg is that. */
+    for (int i = 0; i < s->nb_stream_groups && index >= 0; i++) {
+        const AVStreamGroup *candidate = s->stream_groups[i];
+        ret = match_stream_group_specifier(s, candidate, spec, NULL);
+        if (ret < 0)
+            goto error;
+        if (ret > 0 && index-- == 0 && stg == candidate)
+            return 1;
+    }
+    return 0;
+
+error:
+    if (ret == AVERROR(EINVAL))
+        av_log(s, AV_LOG_ERROR, "Invalid stream group specifier: %s.\n", spec);
+    return ret;
+}
+
 AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame)
 {
     AVRational undef = {0, 1};
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index 9e7eca007e..0b4ae096d5 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -1018,6 +1018,77 @@  typedef struct AVStream {
     int pts_wrap_bits;
 } AVStream;
 
+enum AVStreamGroupParamsType {
+    AV_STREAM_GROUP_PARAMS_NONE,
+};
+
+typedef struct AVStreamGroup {
+    /**
+     * A class for @ref avoptions. Set by avformat_stream_group_create().
+     */
+    const AVClass *av_class;
+
+    void *priv_data;
+
+    /**
+     * Group index in AVFormatContext.
+     */
+    unsigned int index;
+
+    /**
+     * Group type-specific group ID.
+     *
+     * decoding: set by libavformat
+     * encoding: may set by the user, replaced by libavformat if left unset
+     */
+    int64_t id;
+
+    /**
+     * Group type
+     *
+     * decoding: set by libavformat on group creation
+     * encoding: set by avformat_stream_group_create()
+     */
+    enum AVStreamGroupParamsType type;
+
+    /**
+     * Group type-specific parameters
+     */
+    union {
+        uintptr_t dummy; // Placeholder
+    } params;
+
+    /**
+     * Metadata that applies to the whole group.
+     *
+     * - demuxing: set by libavformat on group creation
+     * - muxing: may be set by the caller before avformat_write_header()
+     *
+     * Freed by libavformat in avformat_free_context().
+     */
+    AVDictionary *metadata;
+
+    /**
+     * Number of elements in AVStreamGroup.streams.
+     *
+     * Set by avformat_stream_group_add_stream() must not be modified by any other code.
+     */
+    unsigned int nb_streams;
+
+    /**
+     * A list of streams in the group. New entries are created with
+     * avformat_stream_group_add_stream().
+     *
+     * - demuxing: entries are created by libavformat on group creation.
+     *             If AVFMTCTX_NOHEADER is set in ctx_flags, then new entries may also
+     *             appear in av_read_frame().
+     * - muxing: entries are created by the user before avformat_write_header().
+     *
+     * Freed by libavformat in avformat_free_context().
+     */
+    AVStream **streams;
+} AVStreamGroup;
+
 struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
 
 #if FF_API_GET_END_PTS
@@ -1726,6 +1797,26 @@  typedef struct AVFormatContext {
      * @return 0 on success, a negative AVERROR code on failure
      */
     int (*io_close2)(struct AVFormatContext *s, AVIOContext *pb);
+
+    /**
+     * Number of elements in AVFormatContext.stream_groups.
+     *
+     * Set by avformat_stream_group_create(), must not be modified by any other code.
+     */
+    unsigned int nb_stream_groups;
+
+    /**
+     * A list of all stream groups in the file. New groups are created with
+     * avformat_stream_group_create(), and filled with avformat_stream_group_add_stream().
+     *
+     * - demuxing: groups may be created by libavformat in avformat_open_input().
+     *             If AVFMTCTX_NOHEADER is set in ctx_flags, then new groups may also
+     *             appear in av_read_frame().
+     * - muxing: groups may be created by the user before avformat_write_header().
+     *
+     * Freed by libavformat in avformat_free_context().
+     */
+    AVStreamGroup **stream_groups;
 } AVFormatContext;
 
 /**
@@ -1844,6 +1935,37 @@  const AVClass *avformat_get_class(void);
  */
 const AVClass *av_stream_get_class(void);
 
+/**
+ * Get the AVClass for AVStreamGroup. It can be used in combination with
+ * AV_OPT_SEARCH_FAKE_OBJ for examining options.
+ *
+ * @see av_opt_find().
+ */
+const AVClass *av_stream_group_get_class(void);
+
+/**
+ * Add a new empty stream group to a media file.
+ *
+ * When demuxing, it may be called by the demuxer in read_header(). If the
+ * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also
+ * be called in read_packet().
+ *
+ * When muxing, may be called by the user before avformat_write_header().
+ *
+ * User is required to call avformat_free_context() to clean up the allocation
+ * by avformat_stream_group_create().
+ *
+ * New streams can be added to the group with avformat_stream_group_add_stream().
+ *
+ * @param s media file handle
+ *
+ * @return newly created group or NULL on error.
+ * @see avformat_new_stream, avformat_stream_group_add_stream.
+ */
+AVStreamGroup *avformat_stream_group_create(AVFormatContext *s,
+                                            enum AVStreamGroupParamsType type,
+                                            AVDictionary **options);
+
 /**
  * Add a new stream to a media file.
  *
@@ -1863,6 +1985,31 @@  const AVClass *av_stream_get_class(void);
  */
 AVStream *avformat_new_stream(AVFormatContext *s, const struct AVCodec *c);
 
+/**
+ * Add an already allocated stream to a stream group.
+ *
+ * When demuxing, it may be called by the demuxer in read_header(). If the
+ * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also
+ * be called in read_packet().
+ *
+ * When muxing, may be called by the user before avformat_write_header() after
+ * having allocated a new group with avformat_stream_group_create() and stream with
+ * avformat_new_stream().
+ *
+ * User is required to call avformat_free_context() to clean up the allocation
+ * by avformat_stream_group_add_stream().
+ *
+ * @param stg stream group belonging to a media file.
+ * @param st  stream in the media file to add to the group.
+ *
+ * @retval 0                 success
+ * @retval AVERROR(EEXIST)   the stream was already in the group
+ * @retval "another negative error code" legitimate errors
+ *
+ * @see avformat_new_stream, avformat_stream_group_create.
+ */
+int avformat_stream_group_add_stream(AVStreamGroup *stg, AVStream *st);
+
 #if FF_API_AVSTREAM_SIDE_DATA
 /**
  * Wrap an existing array as stream side data.
@@ -2819,6 +2966,22 @@  AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream,
 int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
                                     const char *spec);
 
+/**
+ * Check if the group stg contained in s is matched by the stream group
+ * specifier spec.
+ *
+ * See the "stream group specifiers" chapter in the documentation for the
+ * syntax of spec.
+ *
+ * @return  >0 if stg is matched by spec;
+ *          0  if stg is not matched by spec;
+ *          AVERROR code if spec is invalid
+ *
+ * @note  A stream group specifier can match several groups in the format.
+ */
+int avformat_match_stream_group_specifier(AVFormatContext *s, AVStreamGroup *stg,
+                                          const char *spec);
+
 int avformat_queue_attached_pictures(AVFormatContext *s);
 
 enum AVTimebaseSource {
diff --git a/libavformat/dump.c b/libavformat/dump.c
index c0868a1bb3..ededeedaa9 100644
--- a/libavformat/dump.c
+++ b/libavformat/dump.c
@@ -509,7 +509,7 @@  static void dump_sidedata(void *ctx, const AVStream *st, const char *indent)
 
 /* "user interface" functions */
 static void dump_stream_format(const AVFormatContext *ic, int i,
-                               int index, int is_output)
+                               int group_index, int index, int is_output)
 {
     char buf[256];
     int flags = (is_output ? ic->oformat->flags : ic->iformat->flags);
@@ -517,6 +517,8 @@  static void dump_stream_format(const AVFormatContext *ic, int i,
     const FFStream *const sti = cffstream(st);
     const AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
     const char *separator = ic->dump_separator;
+    const char *group_indent = group_index >= 0 ? "    " : "";
+    const char *extra_indent = group_index >= 0 ? "        " : "      ";
     AVCodecContext *avctx;
     int ret;
 
@@ -543,7 +545,8 @@  static void dump_stream_format(const AVFormatContext *ic, int i,
     avcodec_string(buf, sizeof(buf), avctx, is_output);
     avcodec_free_context(&avctx);
 
-    av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d", index, i);
+    av_log(NULL, AV_LOG_INFO, "%s  Stream #%d", group_indent, index);
+    av_log(NULL, AV_LOG_INFO, ":%d", i);
 
     /* the pid is an important information, so we display it */
     /* XXX: add a generic system */
@@ -621,9 +624,24 @@  static void dump_stream_format(const AVFormatContext *ic, int i,
         av_log(NULL, AV_LOG_INFO, " (non-diegetic)");
     av_log(NULL, AV_LOG_INFO, "\n");
 
-    dump_metadata(NULL, st->metadata, "    ");
+    dump_metadata(NULL, st->metadata, extra_indent);
 
-    dump_sidedata(NULL, st, "    ");
+    dump_sidedata(NULL, st, extra_indent);
+}
+
+static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed,
+                              int i, int index, int is_output)
+{
+    const AVStreamGroup *stg = ic->stream_groups[i];
+    char buf[512];
+    int ret;
+
+    av_log(NULL, AV_LOG_INFO, "  Stream group #%d:%d[0x%"PRIx64"]:", index, i, stg->id);
+
+    switch (stg->type) {
+    default:
+        break;
+    }
 }
 
 void av_dump_format(AVFormatContext *ic, int index,
@@ -699,7 +717,7 @@  void av_dump_format(AVFormatContext *ic, int index,
             dump_metadata(NULL, program->metadata, "    ");
             for (k = 0; k < program->nb_stream_indexes; k++) {
                 dump_stream_format(ic, program->stream_index[k],
-                                   index, is_output);
+                                   -1, index, is_output);
                 printed[program->stream_index[k]] = 1;
             }
             total += program->nb_stream_indexes;
@@ -708,9 +726,12 @@  void av_dump_format(AVFormatContext *ic, int index,
             av_log(NULL, AV_LOG_INFO, "  No Program\n");
     }
 
+    for (i = 0; i < ic->nb_stream_groups; i++)
+         dump_stream_group(ic, printed, i, index, is_output);
+
     for (i = 0; i < ic->nb_streams; i++)
         if (!printed[i])
-            dump_stream_format(ic, i, index, is_output);
+            dump_stream_format(ic, i, -1, index, is_output);
 
     av_free(printed);
 }
diff --git a/libavformat/internal.h b/libavformat/internal.h
index 7702986c9c..c6181683ef 100644
--- a/libavformat/internal.h
+++ b/libavformat/internal.h
@@ -202,6 +202,7 @@  typedef struct FFStream {
      */
     AVStream pub;
 
+    AVFormatContext *fmtctx;
     /**
      * Set to 1 if the codec allows reordering, so pts can be different
      * from dts.
@@ -427,6 +428,26 @@  static av_always_inline const FFStream *cffstream(const AVStream *st)
     return (const FFStream*)st;
 }
 
+typedef struct FFStreamGroup {
+    /**
+     * The public context.
+     */
+    AVStreamGroup pub;
+
+    AVFormatContext *fmtctx;
+} FFStreamGroup;
+
+
+static av_always_inline FFStreamGroup *ffstreamgroup(AVStreamGroup *stg)
+{
+    return (FFStreamGroup*)stg;
+}
+
+static av_always_inline const FFStreamGroup *cffstreamgroup(const AVStreamGroup *stg)
+{
+    return (const FFStreamGroup*)stg;
+}
+
 #ifdef __GNUC__
 #define dynarray_add(tab, nb_ptr, elem)\
 do {\
@@ -608,6 +629,18 @@  void ff_free_stream(AVStream **st);
  */
 void ff_remove_stream(AVFormatContext *s, AVStream *st);
 
+/**
+ * Frees a stream group without modifying the corresponding AVFormatContext.
+ * Must only be called if the latter doesn't matter or if the stream
+ * is not yet attached to an AVFormatContext.
+ */
+void ff_free_stream_group(AVStreamGroup **pstg);
+/**
+ * Remove a stream group from its AVFormatContext and free it.
+ * The group must be the last stream of the AVFormatContext.
+ */
+void ff_remove_stream_group(AVFormatContext *s, AVStreamGroup *stg);
+
 unsigned int ff_codec_get_tag(const AVCodecTag *tags, enum AVCodecID id);
 
 enum AVCodecID ff_codec_get_id(const AVCodecTag *tags, unsigned int tag);
diff --git a/libavformat/options.c b/libavformat/options.c
index 1d8c52246b..9ddc28842c 100644
--- a/libavformat/options.c
+++ b/libavformat/options.c
@@ -271,6 +271,7 @@  AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
     if (!st->codecpar)
         goto fail;
 
+    sti->fmtctx = s;
     sti->avctx = avcodec_alloc_context3(NULL);
     if (!sti->avctx)
         goto fail;
@@ -325,6 +326,95 @@  fail:
     return NULL;
 }
 
+static const AVOption stream_group_options[] = {
+    {"id", "Set group id", offsetof(AVStreamGroup, id), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+    { NULL }
+};
+
+static const AVClass stream_group_class = {
+    .class_name     = "AVStreamGroup",
+    .item_name      = av_default_item_name,
+    .version        = LIBAVUTIL_VERSION_INT,
+    .option         = stream_group_options,
+};
+
+const AVClass *av_stream_group_get_class(void)
+{
+    return &stream_group_class;
+}
+
+AVStreamGroup *avformat_stream_group_create(AVFormatContext *s,
+                                            enum AVStreamGroupParamsType type,
+                                            AVDictionary **options)
+{
+    AVStreamGroup **stream_groups;
+    AVStreamGroup *stg;
+    FFStreamGroup *stgi;
+
+    stream_groups = av_realloc_array(s->stream_groups, s->nb_stream_groups + 1,
+                                     sizeof(*stream_groups));
+    if (!stream_groups)
+        return NULL;
+    s->stream_groups = stream_groups;
+
+    stgi = av_mallocz(sizeof(*stgi));
+    if (!stgi)
+        return NULL;
+    stg = &stgi->pub;
+
+    stg->av_class = &stream_group_class;
+    av_opt_set_defaults(stg);
+    stg->type = type;
+    switch (type) {
+    // Structs in the union are allocated here
+    default:
+        goto fail;
+    }
+
+    if (options) {
+        if (av_opt_set_dict2(stg, options, AV_OPT_SEARCH_CHILDREN))
+            goto fail;
+    }
+
+    stgi->fmtctx = s;
+    stg->index   = s->nb_stream_groups;
+
+    s->stream_groups[s->nb_stream_groups++] = stg;
+
+    return stg;
+fail:
+    ff_free_stream_group(&stg);
+    return NULL;
+}
+
+static int stream_group_add_stream(AVStreamGroup *stg, AVStream *st)
+{
+    AVStream **streams = av_realloc_array(stg->streams, stg->nb_streams + 1,
+                                          sizeof(*stg->streams));
+    if (!streams)
+        return AVERROR(ENOMEM);
+
+    stg->streams = streams;
+    stg->streams[stg->nb_streams++] = st;
+
+    return 0;
+}
+
+int avformat_stream_group_add_stream(AVStreamGroup *stg, AVStream *st)
+{
+    const FFStreamGroup *stgi = cffstreamgroup(stg);
+    const FFStream *sti = cffstream(st);
+
+    if (stgi->fmtctx != sti->fmtctx)
+        return AVERROR(EINVAL);
+
+    for (int i = 0; i < stg->nb_streams; i++)
+        if (stg->streams[i]->index == st->index)
+            return AVERROR(EEXIST);
+
+    return stream_group_add_stream(stg, st);
+}
+
 static int option_is_disposition(const AVOption *opt)
 {
     return opt->type == AV_OPT_TYPE_CONST &&