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

Clément Bœsch ubitux at gmail.com
Thu Mar 22 16:50:41 CET 2012


From: Clément Bœsch <clement.boesch at smartjog.com>

Work done in collaboration with Matthieu Bouron <matthieu.bouron at smartjog.com>.
---
 Changelog       |    1 +
 doc/ffmpeg.texi |   40 ++++++++++++++------
 ffmpeg.c        |  111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 138 insertions(+), 14 deletions(-)

diff --git a/Changelog b/Changelog
index 6c9f551..73250b1 100644
--- a/Changelog
+++ b/Changelog
@@ -19,6 +19,7 @@ version next:
 - RealAudio Lossless decoder
 - ZeroCodec decoder
 - tile video filter
+- audio stream merge in ffmpeg (-amerge option)
 
 
 version 0.10:
diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 402a4ae..172a8fc 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
@@ -787,6 +779,30 @@ 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 -map 0:1 -map 0:2 -amerge -map 0:3 -map 0:4 -amerge -c:v copy output.mpg
+ at end 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 305fdfa..0dcbcb1 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 {
@@ -3320,7 +3322,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)
@@ -3424,6 +3426,110 @@ 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 *afilter, lavfi_map_buf[32];
+    AVBPrint lavfi;
+
+#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, 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, "\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, 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, "\n[a0]");
+        else    av_bprintf(&lavfi, "[m%d]", i - 1);
+        av_bprintf(&lavfi, "[a%d] amerge", i + 1);
+        if (i != nb_astreams - 2)
+            av_bprintf(&lavfi, " [m%d];\n", i);
+    }
+
+    /* create a filtergraph stream outputing the merged stream */
+    av_bprint_finalize(&lavfi, &afilter);
+    parse_option(o, "f", "lavfi", options);
+    parse_option(o, "i", afilter, options);
+
+    /* 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)
@@ -5126,6 +5232,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" },
-- 
1.7.9.1



More information about the ffmpeg-devel mailing list