[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