* [FFmpeg-devel] [PATCH 2/2] avutil/opt: add support for children objects in av_opt_serialize
2024-04-12 23:16 [FFmpeg-devel] [PATCH 1/2] avutil/test/opt: test the AV_OPT_SERIALIZE_SKIP_DEFAULTS flag James Almer
@ 2024-04-12 23:16 ` James Almer
2024-04-15 12:51 ` James Almer
2024-04-13 14:21 ` [FFmpeg-devel] [PATCH 3/3] avutil/tests/opt: test av_opt_find2() James Almer
2024-04-15 15:09 ` [FFmpeg-devel] [PATCH 4/4] fftools/ffmpeg_mux_init: allow mapping a stream group from one of the inputs James Almer
2 siblings, 1 reply; 6+ messages in thread
From: James Almer @ 2024-04-12 23:16 UTC (permalink / raw)
To: ffmpeg-devel
Signed-off-by: James Almer <jamrial@gmail.com>
---
libavutil/opt.c | 65 +++++++++++++++++++++++++++++--------------
libavutil/opt.h | 1 +
libavutil/tests/opt.c | 47 +++++++++++++++++++++++++++++--
tests/ref/fate/opt | 2 +-
4 files changed, 90 insertions(+), 25 deletions(-)
diff --git a/libavutil/opt.c b/libavutil/opt.c
index d11e9d2ac5..ecbf7efe5f 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -2386,26 +2386,22 @@ int av_opt_is_set_to_default_by_name(void *obj, const char *name, int search_fla
return av_opt_is_set_to_default(target, o);
}
-int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
- const char key_val_sep, const char pairs_sep)
+static int opt_serialize(void *obj, int opt_flags, int flags, int *cnt,
+ AVBPrint *bprint, const char key_val_sep, const char pairs_sep)
{
const AVOption *o = NULL;
+ void *child = NULL;
uint8_t *buf;
- AVBPrint bprint;
- int ret, cnt = 0;
+ int ret;
const char special_chars[] = {pairs_sep, key_val_sep, '\0'};
- if (pairs_sep == '\0' || key_val_sep == '\0' || pairs_sep == key_val_sep ||
- pairs_sep == '\\' || key_val_sep == '\\') {
- av_log(obj, AV_LOG_ERROR, "Invalid separator(s) found.");
- return AVERROR(EINVAL);
- }
-
- if (!obj || !buffer)
- return AVERROR(EINVAL);
-
- *buffer = NULL;
- av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED);
+ if (flags & AV_OPT_SERIALIZE_SEARCH_CHILDREN)
+ while (child = av_opt_child_next(obj, child)) {
+ ret = opt_serialize(child, opt_flags, flags, cnt, bprint,
+ key_val_sep, pairs_sep);
+ if (ret < 0)
+ return ret;
+ }
while (o = av_opt_next(obj, o)) {
if (o->type == AV_OPT_TYPE_CONST)
@@ -2417,18 +2413,45 @@ int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
if (flags & AV_OPT_SERIALIZE_SKIP_DEFAULTS && av_opt_is_set_to_default(obj, o) > 0)
continue;
if ((ret = av_opt_get(obj, o->name, 0, &buf)) < 0) {
- av_bprint_finalize(&bprint, NULL);
+ av_bprint_finalize(bprint, NULL);
return ret;
}
if (buf) {
- if (cnt++)
- av_bprint_append_data(&bprint, &pairs_sep, 1);
- av_bprint_escape(&bprint, o->name, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
- av_bprint_append_data(&bprint, &key_val_sep, 1);
- av_bprint_escape(&bprint, buf, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
+ if ((*cnt)++)
+ av_bprint_append_data(bprint, &pairs_sep, 1);
+ av_bprint_escape(bprint, o->name, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
+ av_bprint_append_data(bprint, &key_val_sep, 1);
+ av_bprint_escape(bprint, buf, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
av_freep(&buf);
}
}
+
+ return 0;
+}
+
+int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
+ const char key_val_sep, const char pairs_sep)
+{
+ AVBPrint bprint;
+ int ret, cnt = 0;
+
+ if (pairs_sep == '\0' || key_val_sep == '\0' || pairs_sep == key_val_sep ||
+ pairs_sep == '\\' || key_val_sep == '\\') {
+ av_log(obj, AV_LOG_ERROR, "Invalid separator(s) found.");
+ return AVERROR(EINVAL);
+ }
+
+ if (!obj || !buffer)
+ return AVERROR(EINVAL);
+
+ *buffer = NULL;
+ av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED);
+
+ ret = opt_serialize(obj, opt_flags, flags, &cnt, &bprint,
+ key_val_sep, pairs_sep);
+ if (ret < 0)
+ return ret;
+
ret = av_bprint_finalize(&bprint, buffer);
if (ret < 0)
return ret;
diff --git a/libavutil/opt.h b/libavutil/opt.h
index e6013662f6..855e363091 100644
--- a/libavutil/opt.h
+++ b/libavutil/opt.h
@@ -929,6 +929,7 @@ int av_opt_flag_is_set(void *obj, const char *field_name, const char *flag_name)
#define AV_OPT_SERIALIZE_SKIP_DEFAULTS 0x00000001 ///< Serialize options that are not set to default values only.
#define AV_OPT_SERIALIZE_OPT_FLAGS_EXACT 0x00000002 ///< Serialize options that exactly match opt_flags only.
+#define AV_OPT_SERIALIZE_SEARCH_CHILDREN 0x00000004 ///< Serialize options in possible children of the given object.
/**
* Serialize object's options.
diff --git a/libavutil/tests/opt.c b/libavutil/tests/opt.c
index 32301ba842..53d8951bcf 100644
--- a/libavutil/tests/opt.c
+++ b/libavutil/tests/opt.c
@@ -30,6 +30,7 @@
typedef struct TestContext {
const AVClass *class;
+ struct ChildContext *child;
int num;
int toggle;
char *string;
@@ -123,10 +124,46 @@ static const char *test_get_name(void *ctx)
return "test";
}
+typedef struct ChildContext {
+ const AVClass *class;
+ int64_t child_num64;
+ int child_num;
+} ChildContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(ChildContext, x)
+
+static const AVOption child_options[]= {
+ {"child_num64", "set num 64bit", OFFSET(child_num64), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, 100, 1 },
+ {"child_num", "set child_num", OFFSET(child_num), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 100, 1 },
+ { NULL },
+};
+
+static const char *child_get_name(void *ctx)
+{
+ return "child";
+}
+
+static const AVClass child_class = {
+ .class_name = "ChildContext",
+ .item_name = child_get_name,
+ .option = child_options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+static void *test_child_next(void *obj, void *prev)
+{
+ TestContext *test_ctx = obj;
+ if (!prev)
+ return test_ctx->child;
+ return NULL;
+}
+
static const AVClass test_class = {
.class_name = "TestContext",
.item_name = test_get_name,
.option = test_options,
+ .child_next = test_child_next,
.version = LIBAVUTIL_VERSION_INT,
};
@@ -277,11 +314,15 @@ int main(void)
av_set_options_string(&test_ctx, buf, "=", ",");
av_free(buf);
if (av_opt_serialize(&test_ctx, 0, 0, &buf, '=', ',') >= 0) {
+ ChildContext child_ctx = { 0 };
printf("%s\n", buf);
av_free(buf);
- if (av_opt_serialize(&test_ctx, 0, AV_OPT_SERIALIZE_SKIP_DEFAULTS, &buf, '=', ',') >= 0) {
- if (strlen(buf))
- printf("%s\n", buf);
+ test_ctx.child = &child_ctx;
+ child_ctx.class = &child_class;
+ if (av_opt_serialize(&test_ctx, 0,
+ AV_OPT_SERIALIZE_SKIP_DEFAULTS|AV_OPT_SERIALIZE_SEARCH_CHILDREN,
+ &buf, '=', ',') >= 0) {
+ printf("%s\n", buf);
av_free(buf);
}
}
diff --git a/tests/ref/fate/opt b/tests/ref/fate/opt
index 43bf0929a3..05d57066a1 100644
--- a/tests/ref/fate/opt
+++ b/tests/ref/fate/opt
@@ -179,7 +179,7 @@ Setting entry with key 'array_int' to value ''
Setting entry with key 'array_str' to value 'str0|str\|1|str\\2'
Setting entry with key 'array_dict' to value 'k00=v\\\\00:k01=v\,01,k10=v\\=1\\:0'
num=0,toggle=1,rational=1/1,string=default,escape=\\\=\,,flags=0x00000001,size=200x300,pix_fmt=0bgr,sample_fmt=s16,video_rate=25/1,duration=0.001,color=0xffc0cbff,cl=hexagonal,bin=62696E00,bin1=,bin2=,num64=1,flt=0.333333,dbl=0.333333,bool1=auto,bool2=true,bool3=false,dict1=,dict2=happy\=\\:-),array_int=,array_str=str0|str\\|1|str\\\\2,array_dict=k00\=v\\\\\\\\00:k01\=v\\\,01\,k10\=v\\\\\=1\\\\:0
-flt=0.333333,dbl=0.333333,array_int=
+child_num=0,flt=0.333333,dbl=0.333333,array_int=
Testing av_set_options_string()
Setting options string ''
--
2.44.0
_______________________________________________
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".
^ permalink raw reply [flat|nested] 6+ messages in thread
* [FFmpeg-devel] [PATCH 4/4] fftools/ffmpeg_mux_init: allow mapping a stream group from one of the inputs
2024-04-12 23:16 [FFmpeg-devel] [PATCH 1/2] avutil/test/opt: test the AV_OPT_SERIALIZE_SKIP_DEFAULTS flag James Almer
2024-04-12 23:16 ` [FFmpeg-devel] [PATCH 2/2] avutil/opt: add support for children objects in av_opt_serialize James Almer
2024-04-13 14:21 ` [FFmpeg-devel] [PATCH 3/3] avutil/tests/opt: test av_opt_find2() James Almer
@ 2024-04-15 15:09 ` James Almer
2 siblings, 0 replies; 6+ messages in thread
From: James Almer @ 2024-04-15 15:09 UTC (permalink / raw)
To: ffmpeg-devel
Do this by extending the -stream_group option to accept a map key where the
value selects the input file and stream group.
The can and should set the streams in the output that the copied group will
reference, same as they'd do if they created a group from scratch.
Example command line:
ffmpeg -i input.iamf -map 0 -c:a copy -f null -stream_group \
map=0=0:st=0:st=1:st=2:st=3 -stream_group map=0=1:st=0:st=1:st=2:st=3
Signed-off-by: James Almer <jamrial@gmail.com>
---
fftools/ffmpeg_mux_init.c | 154 +++++++++++++++++++++++++++++++++++++-
1 file changed, 152 insertions(+), 2 deletions(-)
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 6d8bd5bcdf..a46b0628d8 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -2232,11 +2232,137 @@ fail:
return ret;
}
+static int of_serialize_options(Muxer *mux, void *obj, AVBPrint *bp)
+{
+ char *ptr;
+ int ret;
+
+ ret = av_opt_serialize(obj, 0, AV_OPT_SERIALIZE_SKIP_DEFAULTS | AV_OPT_SERIALIZE_SEARCH_CHILDREN,
+ &ptr, '=', ':');
+ if (ret < 0) {
+ av_log(mux, AV_LOG_ERROR, "Failed to serialize group\n");
+ return ret;
+ }
+
+ av_bprintf(bp, "%s", ptr);
+ ret = strlen(ptr);
+ av_free(ptr);
+
+ return ret;
+}
+
+#define SERIALIZE(parent, child) do { \
+ ret = of_serialize_options(mux, parent->child, bp); \
+ if (ret < 0) \
+ return ret; \
+} while (0)
+
+#define SERIALIZE_LOOP(parent, child, suffix, separator) do { \
+ for (int j = 0; j < parent->nb_## child ## suffix; j++) { \
+ av_bprintf(bp, separator#child "="); \
+ SERIALIZE(parent, child ## suffix[j]); \
+ } \
+} while (0)
+
+static int64_t get_stream_group_index_from_id(Muxer *mux, int64_t id)
+{
+ AVFormatContext *oc = mux->fc;
+
+ for (unsigned i = 0; i < oc->nb_stream_groups; i++)
+ if (oc->stream_groups[i]->id == id)
+ return oc->stream_groups[i]->index;
+
+ return AVERROR(EINVAL);
+}
+
+static int of_map_group(Muxer *mux, AVDictionary **dict, AVBPrint *bp, const char *map)
+{
+ AVStreamGroup *stg;
+ int ret, file_idx, stream_idx;
+ char *ptr;
+
+ file_idx = strtol(map, &ptr, 0);
+ if (file_idx >= nb_input_files || file_idx < 0 || map == ptr) {
+ av_log(mux, AV_LOG_ERROR, "Invalid input file index: %d.\n", file_idx);
+ return AVERROR(EINVAL);
+ }
+
+ stream_idx = strtol(*ptr == '=' ? ptr + 1 : ptr, &ptr, 0);
+ if (*ptr || stream_idx >= input_files[file_idx]->ctx->nb_stream_groups || stream_idx < 0) {
+ av_log(mux, AV_LOG_ERROR, "Invalid input stream group index: %d.\n", stream_idx);
+ return AVERROR(EINVAL);
+ }
+
+ stg = input_files[file_idx]->ctx->stream_groups[stream_idx];
+ ret = of_serialize_options(mux, stg, bp);
+ if (ret < 0)
+ return ret;
+
+ ret = av_dict_parse_string(dict, bp->str, "=", ":", 0);
+ if (ret < 0)
+ av_log(mux, AV_LOG_ERROR, "Error parsing mapped group specification %s\n", ptr);
+ av_dict_set_int(dict, "type", stg->type, 0);
+
+ av_log(mux, AV_LOG_VERBOSE, "stg %s\n", bp->str);
+ av_bprint_clear(bp);
+ switch(stg->type) {
+ case AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT: {
+ AVIAMFAudioElement *audio_element = stg->params.iamf_audio_element;
+
+ if (audio_element->demixing_info) {
+ av_bprintf(bp, ",demixing=");
+ SERIALIZE(audio_element, demixing_info);
+ }
+ if (audio_element->recon_gain_info) {
+ av_bprintf(bp, ",recon_gain=");
+ SERIALIZE(audio_element, recon_gain_info);
+ }
+ SERIALIZE_LOOP(audio_element, layer, s, ",");
+ break;
+ }
+ case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION: {
+ AVIAMFMixPresentation *mix = stg->params.iamf_mix_presentation;
+
+ for (int i = 0; i < mix->nb_submixes; i++) {
+ AVIAMFSubmix *submix = mix->submixes[i];
+
+ av_bprintf(bp, ",submix=");
+ SERIALIZE(mix, submixes[i]);
+ for (int j = 0; j < submix->nb_elements; j++) {
+ AVIAMFSubmixElement *element = submix->elements[j];
+ int64_t id = get_stream_group_index_from_id(mux, element->audio_element_id);
+
+ if (id < 0) {
+ av_log(mux, AV_LOG_ERROR, "Invalid or missing stream group index in"
+ "submix element");
+ return id;
+ }
+
+ av_bprintf(bp, "|element=");
+ SERIALIZE(submix, elements[j]);
+ if (ret)
+ av_bprintf(bp, ":");
+ av_bprintf(bp, "stg=%"PRId64, id);
+ }
+ SERIALIZE_LOOP(submix, layout, s, "|");
+ }
+ break;
+ }
+ default:
+ av_log(mux, AV_LOG_ERROR, "Unsupported mapped group type %d.\n", stg->type);
+ ret = AVERROR(EINVAL);
+ break;
+ }
+ av_log(mux, AV_LOG_VERBOSE, "extra %s\n", bp->str);
+ return 0;
+}
+
static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
{
AVFormatContext *oc = mux->fc;
AVStreamGroup *stg;
AVDictionary *dict = NULL, *tmp = NULL;
+ char *mapped_string = NULL;
const AVDictionaryEntry *e;
const AVOption opts[] = {
{ "type", "Set group type", offsetof(AVStreamGroup, type), AV_OPT_TYPE_INT,
@@ -2262,8 +2388,31 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
return ret;
}
+ av_dict_copy(&tmp, dict, 0);
+ e = av_dict_get(dict, "map", NULL, 0);
+ if (e) {
+ AVBPrint bp;
+
+ if (ptr) {
+ av_log(mux, AV_LOG_ERROR, "Unexpected extra parameters when mapping a"
+ " stream group\n");
+ ret = AVERROR(EINVAL);
+ goto end;
+ }
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+ ret = of_map_group(mux, &tmp, &bp, e->value);
+ if (ret < 0) {
+ av_bprint_finalize(&bp, NULL);
+ goto end;
+ }
+
+ av_bprint_finalize(&bp, &mapped_string);
+ ptr = mapped_string;
+ }
+
// "type" is not a user settable AVOption in AVStreamGroup, so handle it here
- e = av_dict_get(dict, "type", NULL, 0);
+ e = av_dict_get(tmp, "type", NULL, 0);
if (!e) {
av_log(mux, AV_LOG_ERROR, "No type specified for Stream Group in \"%s\"\n", token);
ret = AVERROR(EINVAL);
@@ -2278,7 +2427,6 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
goto end;
}
- av_dict_copy(&tmp, dict, 0);
stg = avformat_stream_group_create(oc, type, &tmp);
if (!stg) {
ret = AVERROR(ENOMEM);
@@ -2331,6 +2479,7 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
// make sure that nothing but "st" and "stg" entries are left in the dict
e = NULL;
+ av_dict_set(&tmp, "map", NULL, 0);
av_dict_set(&tmp, "type", NULL, 0);
while (e = av_dict_iterate(tmp, e)) {
if (!strcmp(e->key, "st") || !strcmp(e->key, "stg"))
@@ -2343,6 +2492,7 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
ret = 0;
end:
+ av_free(mapped_string);
av_dict_free(&dict);
av_dict_free(&tmp);
--
2.44.0
_______________________________________________
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".
^ permalink raw reply [flat|nested] 6+ messages in thread