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 <jamr...@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-requ...@ffmpeg.org with subject "unsubscribe".

Reply via email to