mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2025-02-23 07:16:56 +00:00
pan: add channel mapping capability.
This commit is contained in:
parent
66fdbcbbc9
commit
6728dd37ac
1
configure
vendored
1
configure
vendored
@ -1659,6 +1659,7 @@ mp_filter_deps="gpl avcodec"
|
|||||||
mptestsrc_filter_deps="gpl"
|
mptestsrc_filter_deps="gpl"
|
||||||
negate_filter_deps="lut_filter"
|
negate_filter_deps="lut_filter"
|
||||||
ocv_filter_deps="libopencv"
|
ocv_filter_deps="libopencv"
|
||||||
|
pan_filter_deps="swresample"
|
||||||
scale_filter_deps="swscale"
|
scale_filter_deps="swscale"
|
||||||
tinterlace_filter_deps="gpl"
|
tinterlace_filter_deps="gpl"
|
||||||
yadif_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
|
Mix channels with specific gain levels. The filter accepts the output
|
||||||
channel layout followed by a set of channels definitions.
|
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:
|
The filter accepts parameters of the form:
|
||||||
"@var{l}:@var{outdef}:@var{outdef}:..."
|
"@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
|
that specification will be renormalized so that the total is 1, thus
|
||||||
avoiding clipping noise.
|
avoiding clipping noise.
|
||||||
|
|
||||||
|
@subsection Mixing examples
|
||||||
|
|
||||||
For example, if you want to down-mix from stereo to mono, but with a bigger
|
For example, if you want to down-mix from stereo to mono, but with a bigger
|
||||||
factor for the left channel:
|
factor for the left channel:
|
||||||
@example
|
@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
|
that should be preferred (see "-ac" option) unless you have very specific
|
||||||
needs.
|
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
|
@section silencedetect
|
||||||
|
|
||||||
Detect silence in an audio stream.
|
Detect silence in an audio stream.
|
||||||
|
@ -30,12 +30,14 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "libavutil/audioconvert.h"
|
#include "libavutil/audioconvert.h"
|
||||||
#include "libavutil/avstring.h"
|
#include "libavutil/avstring.h"
|
||||||
|
#include "libavutil/opt.h"
|
||||||
|
#include "libswresample/swresample.h"
|
||||||
#include "avfilter.h"
|
#include "avfilter.h"
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
#define MAX_CHANNELS 63
|
#define MAX_CHANNELS 63
|
||||||
|
|
||||||
typedef struct {
|
typedef struct PanContext {
|
||||||
int64_t out_channel_layout;
|
int64_t out_channel_layout;
|
||||||
union {
|
union {
|
||||||
double d[MAX_CHANNELS][MAX_CHANNELS];
|
double d[MAX_CHANNELS][MAX_CHANNELS];
|
||||||
@ -46,6 +48,16 @@ typedef struct {
|
|||||||
int need_renumber;
|
int need_renumber;
|
||||||
int nb_input_channels;
|
int nb_input_channels;
|
||||||
int nb_output_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;
|
} PanContext;
|
||||||
|
|
||||||
static int parse_channel_name(char **arg, int *rchannel, int *rnamed)
|
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;
|
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)
|
static int query_formats(AVFilterContext *ctx)
|
||||||
{
|
{
|
||||||
PanContext *pan = ctx->priv;
|
PanContext *pan = ctx->priv;
|
||||||
@ -186,11 +223,21 @@ static int query_formats(AVFilterContext *ctx)
|
|||||||
AVFilterLink *outlink = ctx->outputs[0];
|
AVFilterLink *outlink = ctx->outputs[0];
|
||||||
AVFilterFormats *formats;
|
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 enum AVSampleFormat sample_fmts[] = {AV_SAMPLE_FMT_S16, -1};
|
||||||
const int packing_fmts[] = {AVFILTER_PACKED, -1};
|
const int packing_fmts[] = {AVFILTER_PACKED, -1};
|
||||||
|
|
||||||
avfilter_set_common_sample_formats (ctx, avfilter_make_format_list(sample_fmts));
|
avfilter_set_common_sample_formats (ctx, avfilter_make_format_list(sample_fmts));
|
||||||
avfilter_set_common_packing_formats(ctx, avfilter_make_format_list(packing_fmts));
|
avfilter_set_common_packing_formats(ctx, avfilter_make_format_list(packing_fmts));
|
||||||
|
pan->filter_samples = filter_samples_panning;
|
||||||
|
}
|
||||||
|
|
||||||
// inlink supports any channel layout
|
// inlink supports any channel layout
|
||||||
formats = avfilter_make_all_channel_layouts();
|
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
|
// renormalize
|
||||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||||
if (!((pan->need_renorm >> i) & 1))
|
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++)
|
for (j = 0; j < pan->nb_input_channels; j++)
|
||||||
pan->gain.d[i][j] /= t;
|
pan->gain.d[i][j] /= t;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// summary
|
// summary
|
||||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||||
cur = buf;
|
cur = buf;
|
||||||
@ -249,6 +335,17 @@ static int config_props(AVFilterLink *link)
|
|||||||
}
|
}
|
||||||
av_log(ctx, AV_LOG_INFO, "o%d = %s\n", i, buf);
|
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
|
// convert to integer
|
||||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||||
for (j = 0; j < pan->nb_input_channels; j++) {
|
for (j = 0; j < pan->nb_input_channels; j++) {
|
||||||
@ -261,19 +358,26 @@ static int config_props(AVFilterLink *link)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void filter_samples_channel_mapping(PanContext *pan,
|
||||||
static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
|
AVFilterBufferRef *outsamples,
|
||||||
|
AVFilterBufferRef *insamples,
|
||||||
|
int n)
|
||||||
{
|
{
|
||||||
PanContext *const pan = inlink->dst->priv;
|
swr_convert(pan->swr, outsamples->data, n, (void *)insamples->data, n);
|
||||||
int i, o, n = insamples->audio->nb_samples;
|
}
|
||||||
|
|
||||||
|
static void filter_samples_panning(PanContext *pan,
|
||||||
|
AVFilterBufferRef *outsamples,
|
||||||
|
AVFilterBufferRef *insamples,
|
||||||
|
int n)
|
||||||
|
{
|
||||||
|
int i, o;
|
||||||
|
|
||||||
/* input */
|
/* input */
|
||||||
const int16_t *in = (int16_t *)insamples->data[0];
|
const int16_t *in = (int16_t *)insamples->data[0];
|
||||||
const int16_t *in_end = in + n * pan->nb_input_channels;
|
const int16_t *in_end = in + n * pan->nb_input_channels;
|
||||||
|
|
||||||
/* output */
|
/* 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];
|
int16_t *out = (int16_t *)outsamples->data[0];
|
||||||
|
|
||||||
for (; in < in_end; in += pan->nb_input_channels) {
|
for (; in < in_end; in += pan->nb_input_channels) {
|
||||||
@ -284,16 +388,33 @@ static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamples)
|
|||||||
*(out++) = v >> 8;
|
*(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_filter_samples(outlink, outsamples);
|
||||||
avfilter_unref_buffer(insamples);
|
avfilter_unref_buffer(insamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static av_cold void uninit(AVFilterContext *ctx)
|
||||||
|
{
|
||||||
|
PanContext *pan = ctx->priv;
|
||||||
|
swr_free(&pan->swr);
|
||||||
|
}
|
||||||
|
|
||||||
AVFilter avfilter_af_pan = {
|
AVFilter avfilter_af_pan = {
|
||||||
.name = "pan",
|
.name = "pan",
|
||||||
.description = NULL_IF_CONFIG_SMALL("Remix channels with coefficients (panning)."),
|
.description = NULL_IF_CONFIG_SMALL("Remix channels with coefficients (panning)."),
|
||||||
.priv_size = sizeof(PanContext),
|
.priv_size = sizeof(PanContext),
|
||||||
.init = init,
|
.init = init,
|
||||||
|
.uninit = uninit,
|
||||||
.query_formats = query_formats,
|
.query_formats = query_formats,
|
||||||
|
|
||||||
.inputs = (const AVFilterPad[]) {
|
.inputs = (const AVFilterPad[]) {
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#define LIBAVFILTER_VERSION_MAJOR 2
|
#define LIBAVFILTER_VERSION_MAJOR 2
|
||||||
#define LIBAVFILTER_VERSION_MINOR 59
|
#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, \
|
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
|
||||||
LIBAVFILTER_VERSION_MINOR, \
|
LIBAVFILTER_VERSION_MINOR, \
|
||||||
|
Loading…
Reference in New Issue
Block a user