forked from FFmpeg/FFmpeg
fftools/ffmpeg_mux_init: allow mapping a stream group from one of the inputs
Signed-off-by: James Almer <jamrial@gmail.com>
This commit is contained in:
parent
8616cfe089
commit
ecf87dd230
2 changed files with 177 additions and 5 deletions
|
@ -663,10 +663,11 @@ Not all muxers support embedded thumbnails, and those who do, only support a few
|
||||||
Creates a program with the specified @var{title}, @var{program_num} and adds the specified
|
Creates a program with the specified @var{title}, @var{program_num} and adds the specified
|
||||||
@var{stream}(s) to it.
|
@var{stream}(s) to it.
|
||||||
|
|
||||||
@item -stream_group type=@var{type}:st=@var{stream}[:st=@var{stream}][:stg=@var{stream_group}][:id=@var{stream_group_id}...] (@emph{output})
|
@item -stream_group [map=@var{input_file_id}=@var{stream_group}][type=@var{type}:]st=@var{stream}[:st=@var{stream}][:stg=@var{stream_group}][:id=@var{stream_group_id}...] (@emph{output})
|
||||||
|
|
||||||
Creates a stream group of the specified @var{type}, @var{stream_group_id} and adds the specified
|
Creates a stream group of the specified @var{type} and @var{stream_group_id}, or by
|
||||||
@var{stream}(s) and/or previously defined @var{stream_group}(s) to it.
|
@var{map}ping an input group, adding the specified @var{stream}(s) and/or previously
|
||||||
|
defined @var{stream_group}(s) to it.
|
||||||
|
|
||||||
@var{type} can be one of the following:
|
@var{type} can be one of the following:
|
||||||
@table @option
|
@table @option
|
||||||
|
@ -863,6 +864,27 @@ all sub-mix element's @var{annotations}s
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
|
E.g. to create an scalable 5.1 IAMF file from several WAV input files
|
||||||
|
@example
|
||||||
|
ffmpeg -i front.wav -i back.wav -i center.wav -i lfe.wav
|
||||||
|
-map 0:0 -map 1:0 -map 2:0 -map 3:0 -c:a opus
|
||||||
|
-stream_group type=iamf_audio_element:id=1:st=0:st=1:st=2:st=3,
|
||||||
|
demixing=parameter_id=998,
|
||||||
|
recon_gain=parameter_id=101,
|
||||||
|
layer=ch_layout=stereo,
|
||||||
|
layer=ch_layout=5.1,
|
||||||
|
-stream_group type=iamf_mix_presentation:id=2:stg=0:annotations=en-us=Mix_Presentation,
|
||||||
|
submix=parameter_id=100:parameter_rate=48000|element=stg=0:parameter_id=100:annotations=en-us=Scalable_Submix|layout=sound_system=stereo|layout=sound_system=5.1
|
||||||
|
-streamid 0:0 -streamid 1:1 -streamid 2:2 -streamid 3:3 output.iamf
|
||||||
|
@end example
|
||||||
|
|
||||||
|
To copy the two stream groups (Audio Element and Mix Presentation) from an input IAMF file with four
|
||||||
|
streams into an mp4 output
|
||||||
|
@example
|
||||||
|
ffmpeg -i input.iamf -c:a copy -stream_group map=0=0:st=0:st=1:st=2:st=3 -stream_group map=0=1:stg=0
|
||||||
|
-streamid 0:0 -streamid 1:1 -streamid 2:2 -streamid 3:3 output.mp4
|
||||||
|
@end example
|
||||||
|
|
||||||
@item -target @var{type} (@emph{output})
|
@item -target @var{type} (@emph{output})
|
||||||
Specify target file type (@code{vcd}, @code{svcd}, @code{dvd}, @code{dv},
|
Specify target file type (@code{vcd}, @code{svcd}, @code{dvd}, @code{dv},
|
||||||
@code{dv50}). @var{type} may be prefixed with @code{pal-}, @code{ntsc-} or
|
@code{dv50}). @var{type} may be prefixed with @code{pal-}, @code{ntsc-} or
|
||||||
|
|
|
@ -2232,11 +2232,137 @@ fail:
|
||||||
return ret;
|
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)
|
static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
|
||||||
{
|
{
|
||||||
AVFormatContext *oc = mux->fc;
|
AVFormatContext *oc = mux->fc;
|
||||||
AVStreamGroup *stg;
|
AVStreamGroup *stg;
|
||||||
AVDictionary *dict = NULL, *tmp = NULL;
|
AVDictionary *dict = NULL, *tmp = NULL;
|
||||||
|
char *mapped_string = NULL;
|
||||||
const AVDictionaryEntry *e;
|
const AVDictionaryEntry *e;
|
||||||
const AVOption opts[] = {
|
const AVOption opts[] = {
|
||||||
{ "type", "Set group type", offsetof(AVStreamGroup, type), AV_OPT_TYPE_INT,
|
{ "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;
|
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
|
// "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) {
|
if (!e) {
|
||||||
av_log(mux, AV_LOG_ERROR, "No type specified for Stream Group in \"%s\"\n", token);
|
av_log(mux, AV_LOG_ERROR, "No type specified for Stream Group in \"%s\"\n", token);
|
||||||
ret = AVERROR(EINVAL);
|
ret = AVERROR(EINVAL);
|
||||||
|
@ -2278,7 +2427,6 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_dict_copy(&tmp, dict, 0);
|
|
||||||
stg = avformat_stream_group_create(oc, type, &tmp);
|
stg = avformat_stream_group_create(oc, type, &tmp);
|
||||||
if (!stg) {
|
if (!stg) {
|
||||||
ret = AVERROR(ENOMEM);
|
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
|
// make sure that nothing but "st" and "stg" entries are left in the dict
|
||||||
e = NULL;
|
e = NULL;
|
||||||
|
av_dict_set(&tmp, "map", NULL, 0);
|
||||||
av_dict_set(&tmp, "type", NULL, 0);
|
av_dict_set(&tmp, "type", NULL, 0);
|
||||||
while (e = av_dict_iterate(tmp, e)) {
|
while (e = av_dict_iterate(tmp, e)) {
|
||||||
if (!strcmp(e->key, "st") || !strcmp(e->key, "stg"))
|
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;
|
ret = 0;
|
||||||
end:
|
end:
|
||||||
|
av_free(mapped_string);
|
||||||
av_dict_free(&dict);
|
av_dict_free(&dict);
|
||||||
av_dict_free(&tmp);
|
av_dict_free(&tmp);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue