diff --git a/Changelog b/Changelog index e24f39d3c9..965b2b2043 100644 --- a/Changelog +++ b/Changelog @@ -13,6 +13,7 @@ version next: - asplit audio filter - tinterlace video filter - astreamsync audio filter +- amerge audio filter version 0.9: diff --git a/doc/filters.texi b/doc/filters.texi index 487ec210c7..3109d0d529 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -156,6 +156,39 @@ aformat=u8\\,s16:mono:packed aformat=s16:mono\\,stereo:all @end example +@section amerge + +Merge two audio streams into a single multi-channel stream. + +This filter does not need any argument. + +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 +will be reordered as necessary. If the channel layouts of the inputs are not +disjoint, the output will have all the channels of the first input then all +the channels of the second input, in that order, and the channel layout of +the output will be the default value corresponding to the total number of +channels. + +For example, if the first input is in 2.1 (FL+FR+LF) and the second input +is FC+BL+BR, then the output will be in 5.1, with the channels in the +following order: a1, a2, b1, a3, b2, b3 (a1 is the first channel of the +first input, b1 is the first channel of the second input). + +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 +arbitrarily set to 4.0, which may or may not be the expected value. + +Both inputs must have the same sample rate, format and packing. + +If inputs do not have the same duration, the output will stop with the +shortest. + +Example: merge two mono files into a stereo stream: +@example +amovie=left.wav [l] ; amovie=right.mp3 [r] ; [l] [r] amerge +@end example + @section anull Pass the audio source unchanged to the output. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 318039e9c2..0d8f120f1a 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -26,6 +26,7 @@ OBJS-$(CONFIG_AVCODEC) += avcodec.o OBJS-$(CONFIG_ACONVERT_FILTER) += af_aconvert.o OBJS-$(CONFIG_AFORMAT_FILTER) += af_aformat.o +OBJS-$(CONFIG_AMERGE_FILTER) += af_amerge.o OBJS-$(CONFIG_ANULL_FILTER) += af_anull.o OBJS-$(CONFIG_ARESAMPLE_FILTER) += af_aresample.o OBJS-$(CONFIG_ASHOWINFO_FILTER) += af_ashowinfo.o diff --git a/libavfilter/af_amerge.c b/libavfilter/af_amerge.c new file mode 100644 index 0000000000..736379c449 --- /dev/null +++ b/libavfilter/af_amerge.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2011 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Audio merging filter + */ + +#include "libswresample/swresample.h" // only for SWR_CH_MAX +#include "avfilter.h" +#include "internal.h" + +#define QUEUE_SIZE 16 + +typedef struct { + int nb_in_ch[2]; /**< number of channels for each input */ + int route[SWR_CH_MAX]; /**< channels routing, see copy_samples */ + int bps; + struct amerge_queue { + AVFilterBufferRef *buf[QUEUE_SIZE]; + int nb_buf, nb_samples, pos; + } queue[2]; +} AMergeContext; + +static av_cold void uninit(AVFilterContext *ctx) +{ + AMergeContext *am = ctx->priv; + int i, j; + + for (i = 0; i < 2; i++) + for (j = 0; j < am->queue[i].nb_buf; j++) + avfilter_unref_buffer(am->queue[i].buf[j]); +} + +static int query_formats(AVFilterContext *ctx) +{ + AMergeContext *am = ctx->priv; + int64_t inlayout[2], outlayout; + const int packing_fmts[] = { AVFILTER_PACKED, -1 }; + AVFilterFormats *formats; + int i; + + for (i = 0; i < 2; i++) { + if (!ctx->inputs[i]->in_chlayouts || + !ctx->inputs[i]->in_chlayouts->format_count) { + av_log(ctx, AV_LOG_ERROR, + "No channel layout for input %d\n", i + 1); + return AVERROR(EINVAL); + } + inlayout[i] = ctx->inputs[i]->in_chlayouts->formats[0]; + if (ctx->inputs[i]->in_chlayouts->format_count > 1) { + char buf[256]; + av_get_channel_layout_string(buf, sizeof(buf), 0, inlayout[i]); + av_log(ctx, AV_LOG_INFO, "Using \"%s\" for input %d\n", buf, i + 1); + } + am->nb_in_ch[i] = av_get_channel_layout_nb_channels(inlayout[i]); + } + if (am->nb_in_ch[0] + am->nb_in_ch[1] > SWR_CH_MAX) { + av_log(ctx, AV_LOG_ERROR, "Too many channels (max %d)\n", SWR_CH_MAX); + return AVERROR(EINVAL); + } + if (inlayout[0] & inlayout[1]) { + av_log(ctx, AV_LOG_WARNING, + "Inputs overlap: output layout will be meaningless\n"); + for (i = 0; i < am->nb_in_ch[0] + am->nb_in_ch[1]; i++) + am->route[i] = i; + outlayout = av_get_default_channel_layout(am->nb_in_ch[0] + + am->nb_in_ch[1]); + if (!outlayout) + outlayout = ((int64_t)1 << (am->nb_in_ch[0] + am->nb_in_ch[1])) - 1; + } else { + int *route[2] = { am->route, am->route + am->nb_in_ch[0] }; + int c, out_ch_number = 0; + + outlayout = inlayout[0] | inlayout[1]; + for (c = 0; c < 64; c++) + for (i = 0; i < 2; i++) + if ((inlayout[i] >> c) & 1) + *(route[i]++) = out_ch_number++; + } + formats = avfilter_make_all_formats(AVMEDIA_TYPE_AUDIO); + avfilter_set_common_sample_formats(ctx, formats); + formats = avfilter_make_format_list(packing_fmts); + avfilter_set_common_packing_formats(ctx, formats); + for (i = 0; i < 2; i++) { + formats = NULL; + avfilter_add_format(&formats, inlayout[i]); + avfilter_formats_ref(formats, &ctx->inputs[i]->out_chlayouts); + } + formats = NULL; + avfilter_add_format(&formats, outlayout); + avfilter_formats_ref(formats, &ctx->outputs[0]->in_chlayouts); + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AMergeContext *am = ctx->priv; + int64_t layout; + char name[3][256]; + int i; + + if (ctx->inputs[0]->sample_rate != ctx->inputs[1]->sample_rate) { + av_log(ctx, AV_LOG_ERROR, + "Inputs must have the same sample rate " + "(%"PRIi64" vs %"PRIi64")\n", + ctx->inputs[0]->sample_rate, ctx->inputs[1]->sample_rate); + return AVERROR(EINVAL); + } + am->bps = av_get_bytes_per_sample(ctx->outputs[0]->format); + outlink->sample_rate = ctx->inputs[0]->sample_rate; + 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_get_channel_layout_string(name[i], sizeof(name[i]), -1, layout); + } + av_log(ctx, AV_LOG_INFO, + "in1:%s + in2:%s -> out:%s\n", name[0], name[1], name[2]); + return 0; +} + +static int request_frame(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AMergeContext *am = ctx->priv; + int i; + + for (i = 0; i < 2; i++) + if (!am->queue[i].nb_samples) + avfilter_request_frame(ctx->inputs[i]); + return 0; +} + +/** + * Copy samples from two input streams to one output stream. + * @param nb_in_ch number of channels in each input stream + * @param route routing values; + * input channel i goes to output channel route[i]; + * i < nb_in_ch[0] are the channels from the first output; + * i >= nb_in_ch[0] are the channels from the second output + * @param ins pointer to the samples of each inputs, in packed format; + * will be left at the end of the copied samples + * @param outs pointer to the samples of the output, in packet format; + * must point to a buffer big enough; + * will be left at the end of the copied samples + * @param ns number of samples to copy + * @param bps bytes per sample + */ +static inline void copy_samples(int nb_in_ch[2], int *route, uint8_t *ins[2], + uint8_t **outs, int ns, int bps) +{ + int *route_cur; + int i, c; + + while (ns--) { + route_cur = route; + for (i = 0; i < 2; i++) { + for (c = 0; c < nb_in_ch[i]; c++) { + memcpy((*outs) + bps * *(route_cur++), ins[i], bps); + ins[i] += bps; + } + } + *outs += (nb_in_ch[0] + nb_in_ch[1]) * bps; + } +} + +static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples) +{ + AVFilterContext *ctx = inlink->dst; + AMergeContext *am = ctx->priv; + int input_number = inlink == ctx->inputs[1]; + struct amerge_queue *inq = &am->queue[input_number]; + int nb_samples, ns, i; + AVFilterBufferRef *outbuf, **inbuf[2]; + uint8_t *ins[2], *outs; + + if (inq->nb_buf == QUEUE_SIZE) { + av_log(ctx, AV_LOG_ERROR, "Packet queue overflow; dropped\n"); + avfilter_unref_buffer(insamples); + return; + } + inq->buf[inq->nb_buf++] = avfilter_ref_buffer(insamples, AV_PERM_READ | + AV_PERM_PRESERVE); + inq->nb_samples += insamples->audio->nb_samples; + avfilter_unref_buffer(insamples); + if (!am->queue[!input_number].nb_samples) + return; + + nb_samples = FFMIN(am->queue[0].nb_samples, + am->queue[1].nb_samples); + outbuf = avfilter_get_audio_buffer(ctx->outputs[0], AV_PERM_WRITE, + nb_samples); + outs = outbuf->data[0]; + for (i = 0; i < 2; i++) { + inbuf[i] = am->queue[i].buf; + ins[i] = (*inbuf[i])->data[0] + + am->queue[i].pos * am->nb_in_ch[i] * am->bps; + } + while (nb_samples) { + ns = nb_samples; + for (i = 0; i < 2; i++) + ns = FFMIN(ns, (*inbuf[i])->audio->nb_samples - am->queue[i].pos); + /* Unroll the most common sample formats: speed +~350% for the loop, + +~13% overall (including two common decoders) */ + switch (am->bps) { + case 1: + copy_samples(am->nb_in_ch, am->route, ins, &outs, ns, 1); + break; + case 2: + copy_samples(am->nb_in_ch, am->route, ins, &outs, ns, 2); + break; + case 4: + copy_samples(am->nb_in_ch, am->route, ins, &outs, ns, 4); + break; + default: + copy_samples(am->nb_in_ch, am->route, ins, &outs, ns, am->bps); + break; + } + + nb_samples -= ns; + for (i = 0; i < 2; i++) { + am->queue[i].nb_samples -= ns; + am->queue[i].pos += ns; + if (am->queue[i].pos == (*inbuf[i])->audio->nb_samples) { + am->queue[i].pos = 0; + avfilter_unref_buffer(*inbuf[i]); + *inbuf[i] = NULL; + inbuf[i]++; + ins[i] = *inbuf[i] ? (*inbuf[i])->data[0] : NULL; + } + } + } + for (i = 0; i < 2; i++) { + int nbufused = inbuf[i] - am->queue[i].buf; + if (nbufused) { + am->queue[i].nb_buf -= nbufused; + memmove(am->queue[i].buf, inbuf[i], + am->queue[i].nb_buf * sizeof(**inbuf)); + } + } + avfilter_filter_samples(ctx->outputs[0], outbuf); +} + +AVFilter avfilter_af_amerge = { + .name = "amerge", + .description = NULL_IF_CONFIG_SMALL("Merge two audio streams into " + "a single multi-channel stream."), + .priv_size = sizeof(AMergeContext), + .uninit = uninit, + .query_formats = query_formats, + + .inputs = (const AVFilterPad[]) { + { .name = "in1", + .type = AVMEDIA_TYPE_AUDIO, + .filter_samples = filter_samples, + .min_perms = AV_PERM_READ, }, + { .name = "in2", + .type = AVMEDIA_TYPE_AUDIO, + .filter_samples = filter_samples, + .min_perms = AV_PERM_READ, }, + { .name = NULL } + }, + .outputs = (const AVFilterPad[]) { + { .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_output, + .request_frame = request_frame, }, + { .name = NULL } + }, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index cba7704865..621568e60c 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -36,6 +36,7 @@ void avfilter_register_all(void) REGISTER_FILTER (ACONVERT, aconvert, af); REGISTER_FILTER (AFORMAT, aformat, af); + REGISTER_FILTER (AMERGE, amerge, af); REGISTER_FILTER (ANULL, anull, af); REGISTER_FILTER (ARESAMPLE, aresample, af); REGISTER_FILTER (ASHOWINFO, ashowinfo, af); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 0daa84b0cd..2ab23877ab 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -30,7 +30,7 @@ #include "libavcodec/avcodec.h" #define LIBAVFILTER_VERSION_MAJOR 2 -#define LIBAVFILTER_VERSION_MINOR 56 +#define LIBAVFILTER_VERSION_MINOR 57 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \