[FFmpeg-devel] [PATCH 4/4] ffmpeg: add -amerge option to merge audio streams.

Clément Bœsch ubitux at gmail.com
Thu Apr 5 11:05:36 CEST 2012


On Thu, Apr 05, 2012 at 11:03:18AM +0200, Clément Bœsch wrote:
> On Thu, Mar 29, 2012 at 06:50:03PM +0200, Michael Niedermayer wrote:
> > On Tue, Mar 27, 2012 at 01:18:59AM +0200, Stefano Sabatini wrote:
> > > On date Monday 2012-03-26 17:36:47 +0200, Clément Bœsch encoded:
> > > > On Sat, Mar 24, 2012 at 02:24:59PM +0100, Stefano Sabatini wrote:
> > > > [...]
> > > > > > +            continue;
> > > > > > +        av_bprintf(&lavfi, "\namovie=%s:si=%d [a%d];", ifname, map->stream_index, nb_astreams++);
> > > > > 
> > > > > I see this is quite limited. Suppose that the user wants to specify a
> > > > > seek point or time, or in general codec/format options for the input
> > > > > file, this won't be possible. We can surely add such options to movie,
> > > > > but in general mapping ffmpeg options to *movie options will be a
> > > > > problem, and very hard to maintain, since they use rather different
> > > > > logics.
> > > > > 
> > > > 
> > > > Yup indeed, it's quite a hack. But integrating this feature properly means
> > > > changing quite a bunch of things. The only proper way I see right now is:
> > > > 
> > > >  1) ffmpeg using libavfilter for audio which depends on:
> > > >     * proper libavfilter audio API (AVFrame based API)
> > > >     * filter auto reconfiguration in case of sampling rate change
> > > >     * audio stretching in libavfilter (-async)
> > > >     * -af option (with auto insert of pan for map channel, and various
> > > >       others)
> > > >  2) create in the -amerge scope a merge context filtergraph just like the
> > > >     one used for -af, and reference the different input streams in the
> > > >     output stream context.
> > > >  3) make ffmpeg handle multiple input streams for a given output stream
> > > > 
> > > > This requires a tremendous amount of work before we can hope for this
> > > > feature.
> > > > 
> > > > Of course the current solution is far from ideal; just like you said, we
> > > > can't configure the input properly, and it will basically be limited to
> > > > standard usages. Though, it has the advantage to cover a majority of use
> > > > cases. Also, the hack is quite isolated (it doesn't affect the internals).
> > > > 
> > > > Do you or anyone else has an alternative to propose?
> > > 
> > > I basically agree with this, indeed my objection was not blocking,
> > > although documenting a bit the limitations seems in order. Apart from
> > > that I leave to the ffmpeg maintainer the choice of injecting or not
> > > the extra complexity, I can review the code.
> > 
> > iam fine with the extra complexity.
> > A fate test would be nice though
> > 
> 
> fate test added, better documentation, and fate test added (which might
> increase code coverage quite a bit).
> 

Forgot the fate entry, fixed in the attached patch.

-- 
Clément B.
-------------- next part --------------
From 3c027a723f4fc2246e02d057da68b08ead7a383e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <clement.boesch at smartjog.com>
Date: Thu, 22 Mar 2012 09:58:35 +0100
Subject: [PATCH 4/4] ffmpeg: add -amerge option to merge audio streams.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Work done in collaboration with Matthieu Bouron.

Signed-off-by: Clément Bœsch <clement.boesch at smartjog.com>
Signed-off-by: Matthieu Bouron <matthieu.bouron at smartjog.com>
---
 doc/ffmpeg.texi              |   49 ++++++++++++++----
 ffmpeg.c                     |  114 +++++++++++++++++++++++++++++++++++++++++-
 tests/fate/audio.mak         |    3 +
 tests/ref/fate/ffmpeg_amerge |    1 +
 4 files changed, 153 insertions(+), 14 deletions(-)
 create mode 100644 tests/ref/fate/ffmpeg_amerge

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index a9edad4..dc900bd 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -613,24 +613,16 @@ streams, which are put into the same output file:
 ffmpeg -i stereo.wav -map 0:0 -map 0:0 -map_channel 0.0.0:0.0 -map_channel 0.0.1:0.1 -y out.ogg
 @end example
 
-Note that currently each output stream can only contain channels from a single
+Note that each output stream can only contain channels from a single
 input stream; you can't for example use "-map_channel" to pick multiple input
 audio channels contained in different streams (from the same or different files)
-and merge them into a single output stream. It is therefore not currently
+and merge them into a single output stream. It is therefore not
 possible, for example, to turn two separate mono streams into a single stereo
 stream. However splitting a stereo stream into two single channel mono streams
 is possible.
 
-If you need this feature, a possible workaround is to use the @emph{amerge}
-filter. For example, if you need to merge a media (here @file{input.mkv}) with 2
-mono audio streams into one single stereo channel audio stream (and keep the
-video stream), you can use the following command:
- at example
-ffmpeg -i input.mkv -f lavfi -i "
-amovie=input.mkv:si=1 [a1];
-amovie=input.mkv:si=2 [a2];
-[a1][a2] amerge" -c:a pcm_s16le -c:v copy output.mkv
- at end example
+If you need this feature, see @emph{amerge} filter or "-amerge" option in
+ at command{ffmpeg}.
 
 @item -map_metadata[:@var{metadata_spec_out}] @var{infile}[:@var{metadata_spec_in}] (@emph{output,per-metadata})
 Set metadata information of the next output file from @var{infile}. Note that
@@ -809,6 +801,39 @@ an output mpegts file:
 ffmpeg -i infile -streamid 0:33 -streamid 1:36 out.ts
 @end example
 
+ at item -amerge
+Merge all the audio streams previously mapped. If no streams are specified
+(through "-map"), all the audio streams will be selected for merging.
+
+Merge multiple audio files:
+ at example
+ffmpeg -i input1.wav -i input2.wav -amerge output.wav
+ at end example
+
+Merge all the audio streams, keeping the video:
+ at example
+ffmpeg -i input.mpg -amerge -c:v copy output.mpg
+ at end example
+
+Keep the video (stream 0), swap and merge 2 audio streams (stream 1 and 2):
+ at example
+ffmpeg -i input.mpg -map 0:0 -map 0:2 -map 0:1 -amerge -c:v copy output.mpg
+ at end example
+
+Multiple merges:
+ at example
+ffmpeg -i input.mpg
+    -map 0:0            # select video stream
+    -map 0:1 -map 0:2   # select two audio streams
+    -amerge             # merge the two previously selected audio streams
+    -map 0:3 -map 0:4   # select two other audio streams
+    -amerge             # merge the last two selected audio streams
+        -c:v copy output.mpg
+ at end example
+
+Note: this feature is experimental, and thus limited; the input options are
+likely to be ignored for the audio streams for example.
+
 @item -bsf[:@var{stream_specifier}] @var{bitstream_filters} (@emph{output,per-stream})
 Set bitstream filters for matching streams. @var{bistream_filters} is
 a comma-separated list of bitstream filters. Use the @code{-bsfs} option
diff --git a/ffmpeg.c b/ffmpeg.c
index bb4357f..afa37fe 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -38,6 +38,7 @@
 #include "libavutil/opt.h"
 #include "libavcodec/audioconvert.h"
 #include "libavutil/audioconvert.h"
+#include "libavutil/bprint.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/samplefmt.h"
 #include "libavutil/colorspace.h"
@@ -105,11 +106,12 @@ const int program_birth_year = 2000;
 
 /* select an input stream for an output stream */
 typedef struct StreamMap {
-    int disabled;           /** 1 is this mapping is disabled by a negative map */
+    int disabled;           /** 1 is this mapping is disabled (by a negative map for example) */
     int file_index;
     int stream_index;
     int sync_file_index;
     int sync_stream_index;
+    int protected;          ///< input stream is protected and won't be disabled
 } StreamMap;
 
 typedef struct {
@@ -3368,7 +3370,7 @@ static int opt_map(OptionsContext *o, const char *opt, const char *arg)
         /* disable some already defined maps */
         for (i = 0; i < o->nb_stream_maps; i++) {
             m = &o->stream_maps[i];
-            if (file_idx == m->file_index &&
+            if (file_idx == m->file_index && !m->protected &&
                 check_stream_specifier(input_files[m->file_index].ctx,
                                        input_files[m->file_index].ctx->streams[m->stream_index],
                                        *p == ':' ? p + 1 : p) > 0)
@@ -3472,6 +3474,113 @@ static int opt_map_channel(OptionsContext *o, const char *opt, const char *arg)
     return 0;
 }
 
+static int opt_amerge(OptionsContext *o, const char *opt, const char *arg)
+{
+#if !CONFIG_LAVFI_INDEV
+    av_log(NULL, AV_LOG_ERROR, "Audio merge is only supported with lavfi device enabled.\n");
+#else
+    int i, nb_astreams = 0;
+    const int auto_ist_pick = !o->nb_stream_maps;
+    char lavfi_map_buf[32];
+    char    *lavfi_graph_str;
+    AVBPrint lavfi_graph_buf;
+
+#define DO_MAP(stream_idx) do {                                             \
+    const int file_index = input_streams[stream_idx].file_index;            \
+    snprintf(lavfi_map_buf, sizeof(lavfi_map_buf), "%d:%d",                 \
+             file_index, stream_idx - input_files[file_index].ist_index);   \
+    parse_option(o, "map", lavfi_map_buf, options);                         \
+} while (0)
+
+    if (!nb_input_files) {
+        av_log(NULL, AV_LOG_FATAL, "No input specified before calling -amerge\n");
+        return AVERROR(EINVAL);
+    }
+
+    /* auto input stream pick mode if user has not specified any -map. This
+     * allows command line such as:
+     *   ffmpeg -i merge-my-audio-streams.mpg -c:v copy -amerge out.mpg */
+    if (auto_ist_pick) {
+
+        /* best video stream */
+        i = get_best_input_stream_index(AVMEDIA_TYPE_VIDEO);
+        if (i >= 0)
+            DO_MAP(i);
+
+        /* all audio streams (so they are all merged) */
+        for (i = 0; i < nb_input_streams; i++)
+            if (input_streams[i].st->codec->codec_type == AVMEDIA_TYPE_AUDIO)
+                DO_MAP(i);
+    }
+
+    /* prepare the audio sources based on all the audio mapped streams and
+     * disable the audio maps */
+    av_bprint_init(&lavfi_graph_buf, 256, 1);
+    for (i = 0; i < o->nb_stream_maps; i++) {
+        const StreamMap *map = &o->stream_maps[i];
+        const char *ifname = input_files[map->file_index].ctx->filename;
+        const InputStream *ist;
+
+        ist = &input_streams[input_files[map->file_index].ist_index + map->stream_index];
+        if (map->disabled || map->protected || ist->st->codec->codec_type != AVMEDIA_TYPE_AUDIO)
+            continue;
+        av_bprintf(&lavfi_graph_buf, "\namovie=%s:si=%d [a%d];",
+                   ifname, map->stream_index, nb_astreams++);
+    }
+
+    /* no merge needed */
+    if (nb_astreams < 2) {
+        av_log(NULL, AV_LOG_WARNING,
+               "%d audio stream, -amerge won't have any effect.\n", nb_astreams);
+        av_bprint_finalize(&lavfi_graph_buf, NULL);
+        goto end;
+    }
+
+    /* merge is needed, disable all the audio maps which are not already lavfi
+     * audio merges streams (marked as protected) */
+    for (i = 0; i < o->nb_stream_maps; i++) {
+        const InputStream *ist;
+        StreamMap *map = &o->stream_maps[i];
+
+        ist = &input_streams[input_files[map->file_index].ist_index + map->stream_index];
+        if (ist->st->codec->codec_type == AVMEDIA_TYPE_AUDIO && !map->protected)
+            map->disabled = 1;
+
+    }
+
+    /* merge all the audio sources */
+    for (i = 0; i < nb_astreams - 1; i++) {
+        if (!i) av_bprintf(&lavfi_graph_buf, "\n[a0]");
+        else    av_bprintf(&lavfi_graph_buf, "[m%d]", i - 1);
+        av_bprintf(&lavfi_graph_buf, "[a%d] amerge", i + 1);
+        if (i != nb_astreams - 2)
+            av_bprintf(&lavfi_graph_buf, " [m%d];\n", i);
+    }
+
+    /* create a filtergraph stream outputting the merged stream */
+    av_bprint_finalize(&lavfi_graph_buf, &lavfi_graph_str);
+    parse_option(o, "f", "lavfi",         options);
+    parse_option(o, "i", lavfi_graph_str, options);
+    av_freep(&lavfi_graph_str);
+
+    /* reconstruct stream maps inserting the audio merge */
+    snprintf(lavfi_map_buf, sizeof(lavfi_map_buf), "%d:0", nb_input_files - 1);
+    parse_option(o, "map", lavfi_map_buf, options);
+    o->stream_maps[o->nb_stream_maps - 1].protected = 1;
+
+end:
+    /* auto input stream pick mode must place the subtitles streams after audio
+     * streams */
+    if (auto_ist_pick) {
+        i = get_best_input_stream_index(AVMEDIA_TYPE_SUBTITLE);
+        if (i >= 0)
+            DO_MAP(i);
+    }
+#undef DO_MAP
+#endif
+    return 0;
+}
+
 /**
  * Parse a metadata specifier in arg.
  * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram)
@@ -5178,6 +5287,7 @@ static const OptionDef options[] = {
     { "an", OPT_BOOL | OPT_AUDIO | OPT_OFFSET, {.off = OFFSET(audio_disable)}, "disable audio" },
     { "acodec", HAS_ARG | OPT_AUDIO | OPT_FUNC2, {(void*)opt_audio_codec}, "force audio codec ('copy' to copy stream)", "codec" },
     { "atag", HAS_ARG | OPT_EXPERT | OPT_AUDIO | OPT_FUNC2, {(void*)opt_old2new}, "force audio tag/fourcc", "fourcc/tag" },
+    { "amerge", OPT_AUDIO | OPT_FUNC2, {(void*)opt_amerge}, "merge all audio streams" },
     { "vol", OPT_INT | HAS_ARG | OPT_AUDIO, {(void*)&audio_volume}, "change audio volume (256=normal)" , "volume" }, //
     { "sample_fmt", HAS_ARG | OPT_EXPERT | OPT_AUDIO | OPT_SPEC | OPT_STRING, {.off = OFFSET(sample_fmts)}, "set sample format", "format" },
     { "rmvol", HAS_ARG | OPT_AUDIO | OPT_FLOAT | OPT_SPEC, {.off = OFFSET(rematrix_volume)}, "rematrix volume (as factor)", "volume" },
diff --git a/tests/fate/audio.mak b/tests/fate/audio.mak
index f694cb9..a1cf707 100644
--- a/tests/fate/audio.mak
+++ b/tests/fate/audio.mak
@@ -31,5 +31,8 @@ fate-nellymoser: REF = $(SAMPLES)/nellymoser/nellymoser.pcm
 FATE_AUDIO += fate-ws_snd
 fate-ws_snd: CMD = md5 -i $(SAMPLES)/vqa/ws_snd.vqa -f s16le
 
+FATE_AUDIO += fate-ffmpeg_amerge
+fate-ffmpeg_amerge: CMD = md5 -i $(SAMPLES)/4xm/dracula.4xm -map 0:1 -map 0:3 -map 0:5 -amerge -f s16le
+
 FATE_TESTS += $(FATE_AUDIO)
 fate-audio: $(FATE_AUDIO)
\ No newline at end of file
diff --git a/tests/ref/fate/ffmpeg_amerge b/tests/ref/fate/ffmpeg_amerge
new file mode 100644
index 0000000..65e00f3
--- /dev/null
+++ b/tests/ref/fate/ffmpeg_amerge
@@ -0,0 +1 @@
+b2f1454112692a2fb46fc4630a4871ed
-- 
1.7.9.1

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20120405/7d6a0b9a/attachment.asc>


More information about the ffmpeg-devel mailing list