[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:03:18 CEST 2012
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).
--
Clément B.
-------------- next part --------------
From 12756714184538d5c6392186ae52cdc547d93e13 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 +
3 files changed, 152 insertions(+), 14 deletions(-)
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
--
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/b0dbaeb6/attachment.asc>
More information about the ffmpeg-devel
mailing list