Commit 6728dd37 authored by Clément Bœsch's avatar Clément Bœsch Committed by Clément Bœsch

pan: add channel mapping capability.

parent 66fdbcbb
......@@ -1659,6 +1659,7 @@ mp_filter_deps="gpl avcodec"
mptestsrc_filter_deps="gpl"
negate_filter_deps="lut_filter"
ocv_filter_deps="libopencv"
pan_filter_deps="swresample"
scale_filter_deps="swscale"
tinterlace_filter_deps="gpl"
yadif_filter_deps="gpl"
......
......@@ -315,6 +315,9 @@ Ported from SoX.
Mix channels with specific gain levels. The filter accepts the output
channel layout followed by a set of channels definitions.
This filter is also designed to remap efficiently the channels of an audio
stream.
The filter accepts parameters of the form:
"@var{l}:@var{outdef}:@var{outdef}:..."
......@@ -342,6 +345,8 @@ If the `=' in a channel specification is replaced by `<', then the gains for
that specification will be renormalized so that the total is 1, thus
avoiding clipping noise.
@subsection Mixing examples
For example, if you want to down-mix from stereo to mono, but with a bigger
factor for the left channel:
@example
......@@ -358,6 +363,46 @@ Note that @command{ffmpeg} integrates a default down-mix (and up-mix) system
that should be preferred (see "-ac" option) unless you have very specific
needs.
@subsection Remapping examples
The channel remapping will be effective if, and only if:
@itemize
@item gain coefficients are zeroes or ones,
@item only one input per channel output,
@item the number of output channels is supported by libswresample (16 at the
moment)
@c if SWR_CH_MAX changes, fix the line above.
@end itemize
If all these conditions are satisfied, the filter will notify the user ("Pure
channel mapping detected"), and use an optimized and lossless method to do the
remapping.
For example, if you have a 5.1 source and want a stereo audio stream by
dropping the extra channels:
@example
pan="stereo: c0=FL : c1=FR"
@end example
Given the same source, you can also switch front left and front right channels
and keep the input channel layout:
@example
pan="5.1: c0=c1 : c1=c0 : c2=c2 : c3=c3 : c4=c4 : c5=c5"
@end example
If the input is a stereo audio stream, you can mute the front left channel (and
still keep the stereo channel layout) with:
@example
pan="stereo:c1=c1"
@end example
Still with a stereo audio stream input, you can copy the right channel in both
front left and right:
@example
pan="stereo: c0=FR : c1=FR"
@end example
@section silencedetect
Detect silence in an audio stream.
......
......@@ -30,12 +30,14 @@
#include <stdio.h>
#include "libavutil/audioconvert.h"
#include "libavutil/avstring.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
#include "avfilter.h"
#include "internal.h"
#define MAX_CHANNELS 63
typedef struct {
typedef struct PanContext {
int64_t out_channel_layout;
union {
double d[MAX_CHANNELS][MAX_CHANNELS];
......@@ -46,6 +48,16 @@ typedef struct {
int need_renumber;
int nb_input_channels;
int nb_output_channels;
int pure_gains;
void (*filter_samples)(struct PanContext*,
AVFilterBufferRef*,
AVFilterBufferRef*,
int);
/* channel mapping specific */
int channel_map[SWR_CH_MAX];
struct SwrContext *swr;
} PanContext;
static int parse_channel_name(char **arg, int *rchannel, int *rnamed)
......@@ -179,6 +191,31 @@ static av_cold int init(AVFilterContext *ctx, const char *args0, void *opaque)
return 0;
}
static void filter_samples_channel_mapping(PanContext *pan, AVFilterBufferRef *outsamples, AVFilterBufferRef *insamples, int n);
static void filter_samples_panning (PanContext *pan, AVFilterBufferRef *outsamples, AVFilterBufferRef *insamples, int n);
static int are_gains_pure(const PanContext *pan)
{
int i, j;
for (i = 0; i < MAX_CHANNELS; i++) {
int nb_gain = 0;
for (j = 0; j < MAX_CHANNELS; j++) {
double gain = pan->gain.d[i][j];
/* channel mapping is effective only if 0% or 100% of a channel is
* selected... */
if (gain != 0. && gain != 1.)
return 0;
/* ...and if the output channel is only composed of one input */
if (gain && nb_gain++)
return 0;
}
}
return 1;
}
static int query_formats(AVFilterContext *ctx)
{
PanContext *pan = ctx->priv;
......@@ -186,11 +223,21 @@ static int query_formats(AVFilterContext *ctx)
AVFilterLink *outlink = ctx->outputs[0];
AVFilterFormats *formats;
if (pan->nb_output_channels <= SWR_CH_MAX)
pan->pure_gains = are_gains_pure(pan);
if (pan->pure_gains) {
/* libswr supports any sample and packing formats */
avfilter_set_common_sample_formats(ctx, avfilter_make_all_formats(AVMEDIA_TYPE_AUDIO));
avfilter_set_common_packing_formats(ctx, avfilter_make_all_packing_formats());
pan->filter_samples = filter_samples_channel_mapping;
} else {
const enum AVSampleFormat sample_fmts[] = {AV_SAMPLE_FMT_S16, -1};
const int packing_fmts[] = {AVFILTER_PACKED, -1};
avfilter_set_common_sample_formats (ctx, avfilter_make_format_list(sample_fmts));
avfilter_set_common_packing_formats(ctx, avfilter_make_format_list(packing_fmts));
pan->filter_samples = filter_samples_panning;
}
// inlink supports any channel layout
formats = avfilter_make_all_channel_layouts();
......@@ -222,6 +269,44 @@ static int config_props(AVFilterLink *link)
}
}
}
// gains are pure, init the channel mapping
if (pan->pure_gains) {
// sanity check; can't be done in query_formats since the inlink
// channel layout is unknown at that time
if (pan->nb_input_channels > SWR_CH_MAX) {
av_log(ctx, AV_LOG_ERROR,
"libswresample support a maximum of %d channels. "
"Feel free to ask for a higher limit.\n", SWR_CH_MAX);
return AVERROR_PATCHWELCOME;
}
// get channel map from the pure gains
for (i = 0; i < pan->nb_output_channels; i++) {
int ch_id = -1;
for (j = 0; j < pan->nb_input_channels; j++) {
if (pan->gain.d[i][j]) {
ch_id = j;
break;
}
}
pan->channel_map[i] = ch_id;
}
// init libswresample context
pan->swr = swr_alloc_set_opts(pan->swr,
pan->out_channel_layout, link->format, link->sample_rate,
link->channel_layout, link->format, link->sample_rate,
0, ctx);
if (!pan->swr)
return AVERROR(ENOMEM);
av_opt_set_int(pan->swr, "icl", pan->out_channel_layout, 0);
av_opt_set_int(pan->swr, "uch", pan->nb_output_channels, 0);
swr_set_channel_mapping(pan->swr, pan->channel_map);
r = swr_init(pan->swr);
if (r < 0)
return r;
} else {
// renormalize
for (i = 0; i < pan->nb_output_channels; i++) {
if (!((pan->need_renorm >> i) & 1))
......@@ -239,6 +324,7 @@ static int config_props(AVFilterLink *link)
for (j = 0; j < pan->nb_input_channels; j++)
pan->gain.d[i][j] /= t;
}
}
// summary
for (i = 0; i < pan->nb_output_channels; i++) {
cur = buf;
......@@ -249,6 +335,17 @@ static int config_props(AVFilterLink *link)
}
av_log(ctx, AV_LOG_INFO, "o%d = %s\n", i, buf);
}
// add channel mapping summary if possible
if (pan->pure_gains) {
av_log(ctx, AV_LOG_INFO, "Pure channel mapping detected:");
for (i = 0; i < pan->nb_output_channels; i++)
if (pan->channel_map[i] < 0)
av_log(ctx, AV_LOG_INFO, " M");
else
av_log(ctx, AV_LOG_INFO, " %d", pan->channel_map[i]);
av_log(ctx, AV_LOG_INFO, "\n");
return 0;
}
// convert to integer
for (i = 0; i < pan->nb_output_channels; i++) {
for (j = 0; j < pan->nb_input_channels; j++) {
......@@ -261,19 +358,26 @@ static int config_props(AVFilterLink *link)
return 0;
}
static void filter_samples_channel_mapping(PanContext *pan,
AVFilterBufferRef *outsamples,
AVFilterBufferRef *insamples,
int n)
{
swr_convert(pan->swr, outsamples->data, n, (void *)insamples->data, n);
}
static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
static void filter_samples_panning(PanContext *pan,
AVFilterBufferRef *outsamples,
AVFilterBufferRef *insamples,
int n)
{
PanContext *const pan = inlink->dst->priv;
int i, o, n = insamples->audio->nb_samples;
int i, o;
/* input */
const int16_t *in = (int16_t *)insamples->data[0];
const int16_t *in_end = in + n * pan->nb_input_channels;
/* output */
AVFilterLink *const outlink = inlink->dst->outputs[0];
AVFilterBufferRef *outsamples = avfilter_get_audio_buffer(outlink, AV_PERM_WRITE, n);
int16_t *out = (int16_t *)outsamples->data[0];
for (; in < in_end; in += pan->nb_input_channels) {
......@@ -284,16 +388,33 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
*(out++) = v >> 8;
}
}
}
static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
{
int n = insamples->audio->nb_samples;
AVFilterLink *const outlink = inlink->dst->outputs[0];
AVFilterBufferRef *outsamples = avfilter_get_audio_buffer(outlink, AV_PERM_WRITE, n);
PanContext *pan = inlink->dst->priv;
pan->filter_samples(pan, outsamples, insamples, n);
avfilter_filter_samples(outlink, outsamples);
avfilter_unref_buffer(insamples);
}
static av_cold void uninit(AVFilterContext *ctx)
{
PanContext *pan = ctx->priv;
swr_free(&pan->swr);
}
AVFilter avfilter_af_pan = {
.name = "pan",
.description = NULL_IF_CONFIG_SMALL("Remix channels with coefficients (panning)."),
.priv_size = sizeof(PanContext),
.init = init,
.uninit = uninit,
.query_formats = query_formats,
.inputs = (const AVFilterPad[]) {
......
......@@ -30,7 +30,7 @@
#define LIBAVFILTER_VERSION_MAJOR 2
#define LIBAVFILTER_VERSION_MINOR 59
#define LIBAVFILTER_VERSION_MICRO 101
#define LIBAVFILTER_VERSION_MICRO 102
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
LIBAVFILTER_VERSION_MINOR, \
......
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