On Fri,  8 Jun 2012 18:46:42 -0700, Alex Converse <[email protected]> 
wrote:
> Inspired by MPlayer's af_channels filter and SoX's remix effect.
> ---
>  Changelog                 |    1 +
>  libavfilter/Makefile      |    1 +
>  libavfilter/af_channels.c |  218 
> +++++++++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c  |    1 +
>  4 files changed, 221 insertions(+), 0 deletions(-)
>  create mode 100644 libavfilter/af_channels.c
> 
> diff --git a/Changelog b/Changelog
> index 898a247..2ee532a 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -24,6 +24,7 @@ version <next>:
>  - avprobe output is now standard INI or JSON. The old format can still
>    be used with -of old.
>  - Indeo Audio decoder
> +- audio channel mapping filter
>  
>  
>  version 0.8:
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 914f0c6..a3a1f27 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -29,6 +29,7 @@ OBJS-$(CONFIG_AMIX_FILTER)                   += af_amix.o
>  OBJS-$(CONFIG_ANULL_FILTER)                  += af_anull.o
>  OBJS-$(CONFIG_ASPLIT_FILTER)                 += split.o
>  OBJS-$(CONFIG_ASYNCTS_FILTER)                += af_asyncts.o
> +OBJS-$(CONFIG_CHANNELS_FILTER)               += af_channels.o
>  OBJS-$(CONFIG_RESAMPLE_FILTER)               += af_resample.o
>  
>  OBJS-$(CONFIG_ANULLSRC_FILTER)               += asrc_anullsrc.o
> diff --git a/libavfilter/af_channels.c b/libavfilter/af_channels.c
> new file mode 100644
> index 0000000..afbe8f1
> --- /dev/null
> +++ b/libavfilter/af_channels.c
> @@ -0,0 +1,218 @@
> +/*
> + * Copyright (c) 2012 Google, Inc.
> + *
> + * This file is part of Libav.
> + *
> + * Libav 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.
> + *
> + * Libav 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with Libav; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> + */
> +
> +/**
> + * @file
> + * audio channel mapping filter
> + */
> +
> +#include "libavutil/audioconvert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/mathematics.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +
> +#include "audio.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "internal.h"
> +
> +#define MAX_CH 8
> +typedef struct ChannelsContext {
> +    const AVClass   *class;
> +
> +    AVFilterFormats *formats;
> +    AVFilterFormats *sample_rates;

Those two are unused.

> +    AVFilterChannelLayouts *channel_layouts;
> +
> +    char *mapping_str;
> +    char *channel_layout_str;
> +    int map[MAX_CH];
> +    int nch;
> +    int output_layout;
> +} ChannelsContext;
> +
> +#define OFFSET(x) offsetof(ChannelsContext, x)
> +#define A AV_OPT_FLAG_AUDIO_PARAM
> +static const AVOption options[] = {
> +    { "mapping",        "A comma-separated list of input channel numbers in 
> the order to be outputted.",
> +          OFFSET(mapping_str),        AV_OPT_TYPE_STRING, .flags = A },
> +    { "channel_layout", "Output channel layout.",
> +          OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, .flags = A },
> +    { NULL },
> +};
> +
> +static const AVClass aformat_class = {
> +    .class_name = "channels filter",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> +    ChannelsContext *s = ctx->priv;
> +    int ret;
> +    const char *mapping;
> +    int nch = 0;
> +    int res;
> +
> +    if (!args) {
> +        av_log(ctx, AV_LOG_ERROR, "No parameters supplied.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    s->class = &aformat_class;
> +    av_opt_set_defaults(s);
> +
> +    if ((ret = av_set_options_string(s, args, "=", ":")) < 0) {
> +        av_log(ctx, AV_LOG_ERROR, "Error parsing options string '%s'.\n", 
> args);
> +        return ret;
> +    }
> +
> +    mapping = s->mapping_str;
> +    if (mapping) do {
> +        int ch = 0, n = 0;
> +        res = sscanf(mapping, "%d%n", &ch, &n);
> +        if (res > 0) {
> +            mapping += n;
> +            if (*mapping == ',')
> +                mapping++;
> +            s->map[nch++] = ch;
> +        }
> +    } while (res > 0 && *mapping && nch < MAX_CH);
> +    s->nch = nch;
> +    s->output_layout = av_get_default_channel_layout(nch);
> +
> +    if (s->channel_layout_str) {
> +        uint64_t fmt;
> +        if ((fmt = av_get_channel_layout(s->channel_layout_str)) == 0) {
> +            av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout: %s.\n",
> +                   s->channel_layout_str);
> +            ret = AVERROR(EINVAL);
> +            goto fail;
> +        }
> +        if (!s->nch) {
> +            int i;
> +            s->nch = av_get_channel_layout_nb_channels(fmt);
> +            for (i = 0; i < MAX_CH && i < s->nch; i++)
> +                s->map[i] = i;
> +        } else if (s->nch != av_get_channel_layout_nb_channels(fmt)) {
> +            av_log(ctx, AV_LOG_ERROR, "Output channel layout %s does not 
> match the number of channels mapped %d.\n",
> +                   s->channel_layout_str, s->nch);
> +            ret = AVERROR(EINVAL);
> +            goto fail;
> +        }
> +        s->output_layout = fmt;
> +    }
> +    ff_add_channel_layout(&s->channel_layouts, s->output_layout);
> +
> +fail:
> +    av_opt_free(s);
> +    return ret;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    ChannelsContext *s = ctx->priv;
> +
> +    ff_set_common_formats(ctx, ff_all_formats(AVMEDIA_TYPE_AUDIO));

Won't it be cleaner/simpler/better to support only planar formats and
leave it to libavresample to handle (de)interleaving?

> +    ff_set_common_samplerates(ctx, ff_all_samplerates());
> +    ff_channel_layouts_ref(ff_all_channel_layouts(), 
> &ctx->inputs[0]->out_channel_layouts);
> +    ff_channel_layouts_ref(s->channel_layouts,       
> &ctx->outputs[0]->in_channel_layouts);
> +
> +    return 0;
> +}
> +
> +#define COPY_INTERLEAVED(type) \
> +do { \
> +    const type *restrict in = (const type*)buf->extended_data[0]; \
> +    type *restrict out = (type*)buf_out->extended_data[0]; \
> +    for (ch = 0; ch < nch_out; ch++) { \
> +        for (i = 0; i < nb_samples; i++) { \
> +            out[i * nch_out + ch] = in[i * nch_in + map[ch]]; \
> +        }\
> +    }\
> +} while (0)
> +
> +static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *buf)
> +{
> +    AVFilterContext  *ctx = inlink->dst;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    AVFilterBufferRef *buf_out;
> +    int ch, i;
> +    const ChannelsContext *s = ctx->priv;
> +    const int nch_in = 
> av_get_channel_layout_nb_channels(inlink->channel_layout);
> +    const int nch_out = s->nch;
> +    const int nb_samples = buf->audio->nb_samples;
> +    const int *restrict map = s->map;
> +
> +    buf_out = ff_get_audio_buffer(outlink, AV_PERM_WRITE, 
> buf->audio->nb_samples);
> +
> +    if (buf_out->audio->planar) {
> +        for (ch = 0; ch < nch_out; ch++) {
> +            memcpy(buf_out->extended_data[ch], buf->extended_data[map[ch]], 
> buf->linesize[0]);

For planar formats you don't need to memcpy, just shuffle the pointers
around in your input buffer.

> +        }
> +    } else {
> +        switch (inlink->format) {
> +        case AV_SAMPLE_FMT_U8:  COPY_INTERLEAVED( uint8_t); break;
> +        case AV_SAMPLE_FMT_S16: COPY_INTERLEAVED(uint16_t); break;
> +        case AV_SAMPLE_FMT_FLT:
> +        case AV_SAMPLE_FMT_S32: COPY_INTERLEAVED(uint32_t); break;
> +        case AV_SAMPLE_FMT_DBL: COPY_INTERLEAVED(uint64_t); break;
> +        }
> +    }
> +
> +    if (buf->pts != AV_NOPTS_VALUE)
> +        buf_out->pts = av_rescale_q(buf->pts, inlink->time_base,
> +                                    outlink->time_base);
> +    else
> +        buf_out->pts = AV_NOPTS_VALUE;

You're not setting output timebase, so it's equal to input.
buf_out->pts = buf->pts is enough

> +
> +    ff_filter_samples(outlink, buf_out);
> +    avfilter_unref_buffer(buf);
> +}
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    ChannelsContext *s   = ctx->priv;
> +
> +    outlink->channel_layout = s->output_layout;
> +
> +    return 0;
> +}
> +
> +AVFilter avfilter_af_channels = {
> +    .name          = "channels",

Not sure just "channels" is a good name, it's a bit too generic.
channelmap might be better. Or does anyone have any other suggestions?

Also some documentation in doc/filters.texi is needed.

-- 
Anton Khirnov
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to