[FFmpeg-devel] [PATCH 1/5] avformat: introduce AVStreamGroup
James Almer
jamrial at gmail.com
Tue Nov 21 23:14:38 EET 2023
Signed-off-by: James Almer <jamrial at 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
@@ -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 &&
--
2.42.1
More information about the ffmpeg-devel
mailing list