Commit e0545262 authored by Nicolas George's avatar Nicolas George

amerge: accept multiple inputs.

parent e8e492b3
...@@ -168,9 +168,16 @@ aformat=sample_fmts\=u8\,s16:channel_layouts\=stereo ...@@ -168,9 +168,16 @@ aformat=sample_fmts\=u8\,s16:channel_layouts\=stereo
@section amerge @section amerge
Merge two audio streams into a single multi-channel stream. Merge two or more audio streams into a single multi-channel stream.
This filter does not need any argument. The filter accepts the following named options:
@table @option
@item inputs
Set the number of inputs. Default is 2.
@end table
If the channel layouts of the inputs are disjoint, and therefore compatible, If the channel layouts of the inputs are disjoint, and therefore compatible,
the channel layout of the output will be set accordingly and the channels the channel layout of the output will be set accordingly and the channels
...@@ -189,7 +196,7 @@ On the other hand, if both input are in stereo, the output channels will be ...@@ -189,7 +196,7 @@ On the other hand, if both input are in stereo, the output channels will be
in the default order: a1, a2, b1, b2, and the channel layout will be in the default order: a1, a2, b1, b2, and the channel layout will be
arbitrarily set to 4.0, which may or may not be the expected value. arbitrarily set to 4.0, which may or may not be the expected value.
Both inputs must have the same sample rate, and format. All inputs must have the same sample rate, and format.
If inputs do not have the same duration, the output will stop with the If inputs do not have the same duration, the output will stop with the
shortest. shortest.
...@@ -199,8 +206,7 @@ Example: merge two mono files into a stereo stream: ...@@ -199,8 +206,7 @@ Example: merge two mono files into a stereo stream:
amovie=left.wav [l] ; amovie=right.mp3 [r] ; [l] [r] amerge amovie=left.wav [l] ; amovie=right.mp3 [r] ; [l] [r] amerge
@end example @end example
If you need to do multiple merges (for instance multiple mono audio streams in Example: multiple merges:
a single video media), you can do:
@example @example
ffmpeg -f lavfi -i " ffmpeg -f lavfi -i "
amovie=input.mkv:si=0 [a0]; amovie=input.mkv:si=0 [a0];
...@@ -209,11 +215,7 @@ amovie=input.mkv:si=2 [a2]; ...@@ -209,11 +215,7 @@ amovie=input.mkv:si=2 [a2];
amovie=input.mkv:si=3 [a3]; amovie=input.mkv:si=3 [a3];
amovie=input.mkv:si=4 [a4]; amovie=input.mkv:si=4 [a4];
amovie=input.mkv:si=5 [a5]; amovie=input.mkv:si=5 [a5];
[a0][a1] amerge [x0]; [a0][a1][a2][a3][a4][a5] amerge=inputs=6" -c:a pcm_s16le output.mkv
[x0][a2] amerge [x1];
[x1][a3] amerge [x2];
[x2][a4] amerge [x3];
[x3][a5] amerge" -c:a pcm_s16le output.mkv
@end example @end example
@section amix @section amix
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
* Audio merging filter * Audio merging filter
*/ */
#include "libavutil/bprint.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h" // only for SWR_CH_MAX #include "libswresample/swresample.h" // only for SWR_CH_MAX
#include "avfilter.h" #include "avfilter.h"
#include "audio.h" #include "audio.h"
...@@ -30,6 +32,8 @@ ...@@ -30,6 +32,8 @@
#include "internal.h" #include "internal.h"
typedef struct { typedef struct {
const AVClass *class;
int nb_inputs;
int route[SWR_CH_MAX]; /**< channels routing, see copy_samples */ int route[SWR_CH_MAX]; /**< channels routing, see copy_samples */
int bps; int bps;
struct amerge_input { struct amerge_input {
...@@ -37,27 +41,41 @@ typedef struct { ...@@ -37,27 +41,41 @@ typedef struct {
int nb_ch; /**< number of channels for the input */ int nb_ch; /**< number of channels for the input */
int nb_samples; int nb_samples;
int pos; int pos;
} in[2]; } *in;
} AMergeContext; } AMergeContext;
#define OFFSET(x) offsetof(AMergeContext, x)
static const AVOption amerge_options[] = {
{ "inputs", "specify the number of inputs", OFFSET(nb_inputs),
AV_OPT_TYPE_INT, { .dbl = 2 }, 2, SWR_CH_MAX },
};
static const AVClass amerge_class = {
.class_name = "AMergeContext",
.item_name = av_default_item_name,
.option = amerge_options,
};
static av_cold void uninit(AVFilterContext *ctx) static av_cold void uninit(AVFilterContext *ctx)
{ {
AMergeContext *am = ctx->priv; AMergeContext *am = ctx->priv;
int i; int i;
for (i = 0; i < 2; i++) for (i = 0; i < am->nb_inputs; i++)
ff_bufqueue_discard_all(&am->in[i].queue); ff_bufqueue_discard_all(&am->in[i].queue);
av_freep(&am->in);
} }
static int query_formats(AVFilterContext *ctx) static int query_formats(AVFilterContext *ctx)
{ {
AMergeContext *am = ctx->priv; AMergeContext *am = ctx->priv;
int64_t inlayout[2], outlayout; int64_t inlayout[SWR_CH_MAX], outlayout = 0;
AVFilterFormats *formats; AVFilterFormats *formats;
AVFilterChannelLayouts *layouts; AVFilterChannelLayouts *layouts;
int i; int i, overlap = 0, nb_ch = 0;
for (i = 0; i < 2; i++) { for (i = 0; i < am->nb_inputs; i++) {
if (!ctx->inputs[i]->in_channel_layouts || if (!ctx->inputs[i]->in_channel_layouts ||
!ctx->inputs[i]->in_channel_layouts->nb_channel_layouts) { !ctx->inputs[i]->in_channel_layouts->nb_channel_layouts) {
av_log(ctx, AV_LOG_ERROR, av_log(ctx, AV_LOG_ERROR,
...@@ -71,33 +89,38 @@ static int query_formats(AVFilterContext *ctx) ...@@ -71,33 +89,38 @@ static int query_formats(AVFilterContext *ctx)
av_log(ctx, AV_LOG_INFO, "Using \"%s\" for input %d\n", buf, i + 1); av_log(ctx, AV_LOG_INFO, "Using \"%s\" for input %d\n", buf, i + 1);
} }
am->in[i].nb_ch = av_get_channel_layout_nb_channels(inlayout[i]); am->in[i].nb_ch = av_get_channel_layout_nb_channels(inlayout[i]);
if (outlayout & inlayout[i])
overlap++;
outlayout |= inlayout[i];
nb_ch += am->in[i].nb_ch;
} }
if (am->in[0].nb_ch + am->in[1].nb_ch > SWR_CH_MAX) { if (nb_ch > SWR_CH_MAX) {
av_log(ctx, AV_LOG_ERROR, "Too many channels (max %d)\n", SWR_CH_MAX); av_log(ctx, AV_LOG_ERROR, "Too many channels (max %d)\n", SWR_CH_MAX);
return AVERROR(EINVAL); return AVERROR(EINVAL);
} }
if (inlayout[0] & inlayout[1]) { if (overlap) {
av_log(ctx, AV_LOG_WARNING, av_log(ctx, AV_LOG_WARNING,
"Inputs overlap: output layout will be meaningless\n"); "Inputs overlap: output layout will be meaningless\n");
for (i = 0; i < am->in[0].nb_ch + am->in[1].nb_ch; i++) for (i = 0; i < nb_ch; i++)
am->route[i] = i; am->route[i] = i;
outlayout = av_get_default_channel_layout(am->in[0].nb_ch + outlayout = av_get_default_channel_layout(nb_ch);
am->in[1].nb_ch);
if (!outlayout) if (!outlayout)
outlayout = ((int64_t)1 << (am->in[0].nb_ch + am->in[1].nb_ch)) - 1; outlayout = ((int64_t)1 << nb_ch) - 1;
} else { } else {
int *route[2] = { am->route, am->route + am->in[0].nb_ch }; int *route[SWR_CH_MAX];
int c, out_ch_number = 0; int c, out_ch_number = 0;
outlayout = inlayout[0] | inlayout[1]; route[0] = am->route;
for (i = 1; i < am->nb_inputs; i++)
route[i] = route[i - 1] + am->in[i - 1].nb_ch;
for (c = 0; c < 64; c++) for (c = 0; c < 64; c++)
for (i = 0; i < 2; i++) for (i = 0; i < am->nb_inputs; i++)
if ((inlayout[i] >> c) & 1) if ((inlayout[i] >> c) & 1)
*(route[i]++) = out_ch_number++; *(route[i]++) = out_ch_number++;
} }
formats = avfilter_make_format_list(ff_packed_sample_fmts); formats = avfilter_make_format_list(ff_packed_sample_fmts);
avfilter_set_common_sample_formats(ctx, formats); avfilter_set_common_sample_formats(ctx, formats);
for (i = 0; i < 2; i++) { for (i = 0; i < am->nb_inputs; i++) {
layouts = NULL; layouts = NULL;
ff_add_channel_layout(&layouts, inlayout[i]); ff_add_channel_layout(&layouts, inlayout[i]);
ff_channel_layouts_ref(layouts, &ctx->inputs[i]->out_channel_layouts); ff_channel_layouts_ref(layouts, &ctx->inputs[i]->out_channel_layouts);
...@@ -113,26 +136,31 @@ static int config_output(AVFilterLink *outlink) ...@@ -113,26 +136,31 @@ static int config_output(AVFilterLink *outlink)
{ {
AVFilterContext *ctx = outlink->src; AVFilterContext *ctx = outlink->src;
AMergeContext *am = ctx->priv; AMergeContext *am = ctx->priv;
int64_t layout; AVBPrint bp;
char name[3][256];
int i; int i;
if (ctx->inputs[0]->sample_rate != ctx->inputs[1]->sample_rate) { for (i = 1; i < am->nb_inputs; i++) {
if (ctx->inputs[i]->sample_rate != ctx->inputs[0]->sample_rate) {
av_log(ctx, AV_LOG_ERROR, av_log(ctx, AV_LOG_ERROR,
"Inputs must have the same sample rate " "Inputs must have the same sample rate "
"(%"PRIi64" vs %"PRIi64")\n", "(%"PRIi64" for in%d vs %"PRIi64")\n",
ctx->inputs[0]->sample_rate, ctx->inputs[1]->sample_rate); ctx->inputs[i]->sample_rate, i, ctx->inputs[0]->sample_rate);
return AVERROR(EINVAL); return AVERROR(EINVAL);
} }
}
am->bps = av_get_bytes_per_sample(ctx->outputs[0]->format); am->bps = av_get_bytes_per_sample(ctx->outputs[0]->format);
outlink->sample_rate = ctx->inputs[0]->sample_rate; outlink->sample_rate = ctx->inputs[0]->sample_rate;
outlink->time_base = ctx->inputs[0]->time_base; outlink->time_base = ctx->inputs[0]->time_base;
for (i = 0; i < 3; i++) {
layout = (i < 2 ? ctx->inputs[i] : ctx->outputs[0])->channel_layout; av_bprint_init(&bp, 0, 1);
av_get_channel_layout_string(name[i], sizeof(name[i]), -1, layout); for (i = 0; i < am->nb_inputs; i++) {
av_bprintf(&bp, "%sin%d:", i ? " + " : "", i);
av_bprint_channel_layout(&bp, -1, ctx->inputs[i]->channel_layout);
} }
av_log(ctx, AV_LOG_INFO, av_bprintf(&bp, " -> out:");
"in1:%s + in2:%s -> out:%s\n", name[0], name[1], name[2]); av_bprint_channel_layout(&bp, -1, ctx->outputs[0]->channel_layout);
av_log(ctx, AV_LOG_INFO, "%s\n", bp.str);
return 0; return 0;
} }
...@@ -142,7 +170,7 @@ static int request_frame(AVFilterLink *outlink) ...@@ -142,7 +170,7 @@ static int request_frame(AVFilterLink *outlink)
AMergeContext *am = ctx->priv; AMergeContext *am = ctx->priv;
int i, ret; int i, ret;
for (i = 0; i < 2; i++) for (i = 0; i < am->nb_inputs; i++)
if (!am->in[i].nb_samples) if (!am->in[i].nb_samples)
if ((ret = avfilter_request_frame(ctx->inputs[i])) < 0) if ((ret = avfilter_request_frame(ctx->inputs[i])) < 0)
return ret; return ret;
...@@ -150,7 +178,8 @@ static int request_frame(AVFilterLink *outlink) ...@@ -150,7 +178,8 @@ static int request_frame(AVFilterLink *outlink)
} }
/** /**
* Copy samples from two input streams to one output stream. * Copy samples from several input streams to one output stream.
* @param nb_inputs number of inputs
* @param in inputs; used only for the nb_ch field; * @param in inputs; used only for the nb_ch field;
* @param route routing values; * @param route routing values;
* input channel i goes to output channel route[i]; * input channel i goes to output channel route[i];
...@@ -164,21 +193,24 @@ static int request_frame(AVFilterLink *outlink) ...@@ -164,21 +193,24 @@ static int request_frame(AVFilterLink *outlink)
* @param ns number of samples to copy * @param ns number of samples to copy
* @param bps bytes per sample * @param bps bytes per sample
*/ */
static inline void copy_samples(struct amerge_input in[2], int *route, uint8_t *ins[2], static inline void copy_samples(int nb_inputs, struct amerge_input in[],
int *route, uint8_t *ins[],
uint8_t **outs, int ns, int bps) uint8_t **outs, int ns, int bps)
{ {
int *route_cur; int *route_cur;
int i, c; int i, c, nb_ch = 0;
for (i = 0; i < nb_inputs; i++)
nb_ch += in[i].nb_ch;
while (ns--) { while (ns--) {
route_cur = route; route_cur = route;
for (i = 0; i < 2; i++) { for (i = 0; i < nb_inputs; i++) {
for (c = 0; c < in[i].nb_ch; c++) { for (c = 0; c < in[i].nb_ch; c++) {
memcpy((*outs) + bps * *(route_cur++), ins[i], bps); memcpy((*outs) + bps * *(route_cur++), ins[i], bps);
ins[i] += bps; ins[i] += bps;
} }
} }
*outs += (in[0].nb_ch + in[1].nb_ch) * bps; *outs += nb_ch * bps;
} }
} }
...@@ -187,21 +219,26 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples) ...@@ -187,21 +219,26 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
AVFilterContext *ctx = inlink->dst; AVFilterContext *ctx = inlink->dst;
AMergeContext *am = ctx->priv; AMergeContext *am = ctx->priv;
AVFilterLink *const outlink = ctx->outputs[0]; AVFilterLink *const outlink = ctx->outputs[0];
int input_number = inlink == ctx->inputs[1]; int input_number;
int nb_samples, ns, i; int nb_samples, ns, i;
AVFilterBufferRef *outbuf, *inbuf[2]; AVFilterBufferRef *outbuf, *inbuf[SWR_CH_MAX];
uint8_t *ins[2], *outs; uint8_t *ins[SWR_CH_MAX], *outs;
for (input_number = 0; input_number < am->nb_inputs; input_number++)
if (inlink == ctx->inputs[input_number])
break;
av_assert1(input_number < am->nb_inputs);
ff_bufqueue_add(ctx, &am->in[input_number].queue, insamples); ff_bufqueue_add(ctx, &am->in[input_number].queue, insamples);
am->in[input_number].nb_samples += insamples->audio->nb_samples; am->in[input_number].nb_samples += insamples->audio->nb_samples;
if (!am->in[!input_number].nb_samples) nb_samples = am->in[0].nb_samples;
for (i = 1; i < am->nb_inputs; i++)
nb_samples = FFMIN(nb_samples, am->in[i].nb_samples);
if (!nb_samples)
return; return;
nb_samples = FFMIN(am->in[0].nb_samples,
am->in[1].nb_samples);
outbuf = ff_get_audio_buffer(ctx->outputs[0], AV_PERM_WRITE, nb_samples); outbuf = ff_get_audio_buffer(ctx->outputs[0], AV_PERM_WRITE, nb_samples);
outs = outbuf->data[0]; outs = outbuf->data[0];
for (i = 0; i < 2; i++) { for (i = 0; i < am->nb_inputs; i++) {
inbuf[i] = ff_bufqueue_peek(&am->in[i].queue, 0); inbuf[i] = ff_bufqueue_peek(&am->in[i].queue, 0);
ins[i] = inbuf[i]->data[0] + ins[i] = inbuf[i]->data[0] +
am->in[i].pos * am->in[i].nb_ch * am->bps; am->in[i].pos * am->in[i].nb_ch * am->bps;
...@@ -218,27 +255,27 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples) ...@@ -218,27 +255,27 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
while (nb_samples) { while (nb_samples) {
ns = nb_samples; ns = nb_samples;
for (i = 0; i < 2; i++) for (i = 0; i < am->nb_inputs; i++)
ns = FFMIN(ns, inbuf[i]->audio->nb_samples - am->in[i].pos); ns = FFMIN(ns, inbuf[i]->audio->nb_samples - am->in[i].pos);
/* Unroll the most common sample formats: speed +~350% for the loop, /* Unroll the most common sample formats: speed +~350% for the loop,
+~13% overall (including two common decoders) */ +~13% overall (including two common decoders) */
switch (am->bps) { switch (am->bps) {
case 1: case 1:
copy_samples(am->in, am->route, ins, &outs, ns, 1); copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 1);
break; break;
case 2: case 2:
copy_samples(am->in, am->route, ins, &outs, ns, 2); copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 2);
break; break;
case 4: case 4:
copy_samples(am->in, am->route, ins, &outs, ns, 4); copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 4);
break; break;
default: default:
copy_samples(am->in, am->route, ins, &outs, ns, am->bps); copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, am->bps);
break; break;
} }
nb_samples -= ns; nb_samples -= ns;
for (i = 0; i < 2; i++) { for (i = 0; i < am->nb_inputs; i++) {
am->in[i].nb_samples -= ns; am->in[i].nb_samples -= ns;
am->in[i].pos += ns; am->in[i].pos += ns;
if (am->in[i].pos == inbuf[i]->audio->nb_samples) { if (am->in[i].pos == inbuf[i]->audio->nb_samples) {
...@@ -253,25 +290,45 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples) ...@@ -253,25 +290,45 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
ff_filter_samples(ctx->outputs[0], outbuf); ff_filter_samples(ctx->outputs[0], outbuf);
} }
static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
{
AMergeContext *am = ctx->priv;
int ret, i;
char name[16];
am->class = &amerge_class;
av_opt_set_defaults(am);
ret = av_set_options_string(am, args, "=", ":");
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Error parsing options: '%s'\n", args);
return ret;
}
am->in = av_calloc(am->nb_inputs, sizeof(*am->in));
if (!am->in)
return AVERROR(ENOMEM);
for (i = 0; i < am->nb_inputs; i++) {
AVFilterPad pad = {
.name = name,
.type = AVMEDIA_TYPE_AUDIO,
.filter_samples = filter_samples,
.min_perms = AV_PERM_READ | AV_PERM_PRESERVE,
};
snprintf(name, sizeof(name), "in%d", i);
avfilter_insert_inpad(ctx, i, &pad);
}
return 0;
}
AVFilter avfilter_af_amerge = { AVFilter avfilter_af_amerge = {
.name = "amerge", .name = "amerge",
.description = NULL_IF_CONFIG_SMALL("Merge two audio streams into " .description = NULL_IF_CONFIG_SMALL("Merge two audio streams into "
"a single multi-channel stream."), "a single multi-channel stream."),
.priv_size = sizeof(AMergeContext), .priv_size = sizeof(AMergeContext),
.init = init,
.uninit = uninit, .uninit = uninit,
.query_formats = query_formats, .query_formats = query_formats,
.inputs = (const AVFilterPad[]) { .inputs = (const AVFilterPad[]) { { .name = NULL } },
{ .name = "in1",
.type = AVMEDIA_TYPE_AUDIO,
.filter_samples = filter_samples,
.min_perms = AV_PERM_READ | AV_PERM_PRESERVE, },
{ .name = "in2",
.type = AVMEDIA_TYPE_AUDIO,
.filter_samples = filter_samples,
.min_perms = AV_PERM_READ | AV_PERM_PRESERVE, },
{ .name = NULL }
},
.outputs = (const AVFilterPad[]) { .outputs = (const AVFilterPad[]) {
{ .name = "default", { .name = "default",
.type = AVMEDIA_TYPE_AUDIO, .type = AVMEDIA_TYPE_AUDIO,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment