[PATCH] Add af_aconvert - sample fmt and channel layout conversion filter.

Stefano Sabatini stefano.sabatini-lala
Fri Oct 1 14:58:22 CEST 2010


Based on a patch by "S.N. Hemanth Meenakshisundaram" 5m33nak5 at uc5d.3du.
---
 libavfilter/Makefile      |    1 +
 libavfilter/af_aconvert.c |  465 +++++++++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c  |    1 +
 3 files changed, 467 insertions(+), 0 deletions(-)
 create mode 100644 libavfilter/af_aconvert.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index fdb181e..ad10bbd 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -13,6 +13,7 @@ OBJS = allfilters.o                                                     \
        formats.o                                                        \
        graphparser.o                                                    \
 
+OBJS-$(CONFIG_ACONVERT_FILTER)               += af_aconvert.o
 OBJS-$(CONFIG_ANULL_FILTER)                  += af_anull.o
 
 OBJS-$(CONFIG_ANULLSRC_FILTER)               += asrc_anullsrc.o
diff --git a/libavfilter/af_aconvert.c b/libavfilter/af_aconvert.c
new file mode 100644
index 0000000..10e734a
--- /dev/null
+++ b/libavfilter/af_aconvert.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2010 S.N. Hemanth Meenakshisundaram <smeenaks at ucsd.edu>
+ *
+ * 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
+ * Lesser 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
+ * sample format and channel layout conversion audio filter
+ * based on code in libavcodec/resample.c by Fabrice Bellard and
+ * libavcodec/audioconvert.c by Michael Neidermayer
+ */
+
+#include "avfilter.h"
+#include "libavcodec/audioconvert.h"
+
+typedef struct {
+    int reconfig_channel_layout;            ///< flag set when channel layout of incoming buffer changes
+    int reconfig_sample_fmt;                ///< flag set when sample format of incoming buffer changes
+
+    enum AVSampleFormat  in_sample_fmt;     ///< default incoming sample format expected
+    enum AVSampleFormat out_sample_fmt;     ///< output sample format
+    int64_t  in_channel_layout;             ///< default incoming channel layout expected
+    int64_t out_channel_layout;             ///< output channel layout
+
+    int in_nb_samples;                      ///< stores number of samples in previous incoming buffer
+    AVFilterBufferRef *s16_samples;         ///< stores temporary audio data in s16 sample format for channel layout conversions
+    AVFilterBufferRef *s16_samples_ptr;     ///< duplicate pointer to audio data in s16 sample format
+    AVFilterBufferRef *s16_mid_samples;     ///< stores temporary audio data in s16 sample format after channel layout conversions
+    AVFilterBufferRef *s16_mid_samples_ptr; ///< duplicate pointer to audio data after channel layout conversions
+    AVFilterBufferRef *out_samples;         ///< stores audio data after required sample format and channel layout conversions
+    AVFilterBufferRef *out_samples_ptr;     ///< duplicate pointer to audio data after required conversions
+
+    AVAudioConvert *convert_to_s16_ctx;     ///< audio convert context for conversion to s16 sample format
+    AVAudioConvert *convert_to_out_ctx;     ///< audio convert context for conversion to output sample format
+
+    /**
+     * channel conversion routine, point to one of the routines below
+     */
+    void (*channel_conversion) (uint8_t *out[], uint8_t *in[], int , int);
+} ConvertContext;
+
+/**
+ * All of the routines below are for packed audio data. SDL accepts packed data
+ * only and current ffplay also assumes packed data only at all times.
+ */
+
+/* Optimized stereo to mono and mono to stereo routines - common case */
+static void stereo_to_mono(uint8_t *out[], uint8_t *in[], int nb_samples, int in_channels)
+{
+    uint16_t *input  = (uint16_t *) in[0];
+    uint16_t *output = (uint16_t *) out[0];
+
+    while (nb_samples >= 4) {
+        output[0] = (input[0] + input[1]) >> 1;
+        output[1] = (input[2] + input[3]) >> 1;
+        output[2] = (input[4] + input[5]) >> 1;
+        output[3] = (input[6] + input[7]) >> 1;
+        output += 4;
+        input += 8;
+        nb_samples -= 4;
+    }
+    while (nb_samples > 0) {
+        output[0] = (input[0] + input[1]) >> 1;
+        output++;
+        input += 2;
+        nb_samples--;
+    }
+}
+
+static void mono_to_stereo(uint8_t *out[], uint8_t *in[], int nb_samples, int in_channels)
+{
+    int v;
+    uint16_t *input  = (uint16_t *) in[0];
+    uint16_t *output = (uint16_t *) out[0];
+
+    while (nb_samples >= 4) {
+        v = input[0]; output[0] = v; output[1] = v;
+        v = input[1]; output[2] = v; output[3] = v;
+        v = input[2]; output[4] = v; output[5] = v;
+        v = input[3]; output[6] = v; output[7] = v;
+        output += 8;
+        input += 4;
+        nb_samples -= 4;
+    }
+    while (nb_samples > 0) {
+        v = input[0]; output[0] = v; output[1] = v;
+        output += 2;
+        input += 1;
+        nb_samples--;
+    }
+}
+
+/**
+ * This is for when we have more than 2 input channels, need to downmix to
+ * stereo and do not have a conversion formula available.  We just use first
+ * two input channels - left and right. This is a placeholder until more
+ * conversion functions are written.
+ */
+static void stereo_downmix(uint8_t *out[], uint8_t *in[], int nb_samples, int in_channels)
+{
+    int i;
+    uint16_t *output = (uint16_t *)out[0];
+    uint16_t *input  = (uint16_t *)out[0];
+
+    for (i = 0; i < nb_samples; i++) {
+        *output++ = *input++;
+        *output++ = *input++;
+        input += in_channels-2;
+    }
+}
+
+/**
+ * This is for when we have more than 2 input channels, need to downmix to mono
+ * and do not have a conversion formula available.  We just use first two input
+ * channels - left and right. This is a placeholder until more conversion
+ * functions are written.
+ */
+static void mono_downmix(uint8_t *out[], uint8_t *in[], int nb_samples, int in_channels)
+{
+    int i;
+    uint16_t *input  = (short *) in[0];
+    uint16_t *output = (uint16_t *) out[0];
+    uint16_t left, right;
+
+    for (i = 0; i < nb_samples; i++) {
+        left = *input++;
+        right = *input++;
+        *output++ = (left+right)>>1;
+        input += in_channels-2;
+    }
+}
+
+/* Stereo to 5.1 output */
+static void ac3_5p1_mux(uint8_t *out[], uint8_t *in[], int nb_samples, int in_channels)
+{
+    int i;
+    uint16_t *output = (uint16_t *) out[0];
+    uint16_t *input = (uint16_t *) in[0];
+    uint16_t left, right;
+
+    for (i = 0; i < nb_samples; i++) {
+      left  = *input++;                 /* Grab next left sample */
+      right = *input++;                 /* Grab next right sample */
+      *output++ = left;                 /* left */
+      *output++ = right;                /* right */
+      *output++ = (left+right)>>1;      /* center */
+      *output++ = 0;                    /* low freq */
+      *output++ = 0;                    /* FIXME: left surround is either -3dB, -6dB or -9dB of stereo left */
+      *output++ = 0;                    /* FIXME: right surroud is either -3dB, -6dB or -9dB of stereo right */
+    }
+}
+
+static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
+{
+    ConvertContext *convert = ctx->priv;
+    char sample_fmt_str[16] = "", ch_layout_str[16] = "";
+
+    if (args)
+        sscanf(args, "%15[a-z0-9]:%15[a-z0-9]", sample_fmt_str, ch_layout_str);
+
+    convert->out_sample_fmt = av_get_sample_fmt(sample_fmt_str);
+
+    if (*sample_fmt_str && convert->out_sample_fmt == AV_SAMPLE_FMT_NONE) {
+        char *tail;
+        convert->out_sample_fmt = strtol(sample_fmt_str, &tail, 10);
+        if (*tail || (unsigned)convert->out_sample_fmt >= AV_SAMPLE_FMT_NB) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid sample format '%s'\n", sample_fmt_str);
+            return AVERROR(EINVAL);
+        }
+    }
+
+    convert->out_channel_layout = *ch_layout_str ?
+                                    av_get_channel_layout(ch_layout_str) : -1;
+
+    if (*ch_layout_str && convert->out_channel_layout < AV_CH_LAYOUT_STEREO) {
+        /**
+         * -1 is a valid value for out_channel_layout and indicates no change
+         * in channel layout.
+         */
+        char *tail;
+        convert->out_channel_layout = strtol(ch_layout_str, &tail, 10);
+        if (*tail || (convert->out_channel_layout < AV_CH_LAYOUT_STEREO &&
+                      convert->out_channel_layout != -1)) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid channel layout %s\n", ch_layout_str);
+            return AVERROR(EINVAL);
+        }
+    }
+
+    /* Set default values for expected incoming sample format and channel layout */
+    convert->in_channel_layout = AV_CH_LAYOUT_STEREO;
+    convert->in_sample_fmt     = AV_SAMPLE_FMT_S16;
+    convert->in_nb_samples     = 0;
+    /* We do not yet know the channel conversion function to be used */
+    convert->channel_conversion = NULL;
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    ConvertContext *convert = ctx->priv;
+    if (convert->s16_samples)
+        avfilter_unref_buffer(convert->s16_samples);
+    if (convert->s16_mid_samples)
+        avfilter_unref_buffer(convert->s16_mid_samples);
+    if (convert->out_samples)
+        avfilter_unref_buffer(convert->out_samples);
+    if (convert->convert_to_s16_ctx)
+        av_audio_convert_free(convert->convert_to_s16_ctx);
+    if (convert->convert_to_out_ctx)
+        av_audio_convert_free(convert->convert_to_out_ctx);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *formats;
+
+    if (ctx->inputs[0]) {
+        formats = NULL;
+        formats = avfilter_all_formats(AVMEDIA_TYPE_AUDIO);
+        avfilter_formats_ref(formats, &ctx->inputs[0]->out_formats);
+    }
+    if (ctx->outputs[0]) {
+        formats = NULL;
+        formats = avfilter_all_formats(AVMEDIA_TYPE_AUDIO);
+        avfilter_formats_ref(formats, &ctx->outputs[0]->in_formats);
+    }
+
+    return 0;
+}
+
+static int config_props(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    ConvertContext *convert = ctx->priv;
+    char buf1[128], buf2[128];
+
+    if (convert->out_channel_layout == -1)
+        convert->out_channel_layout = inlink->channel_layout;
+    if (convert->out_sample_fmt == -1)
+        convert->out_sample_fmt = inlink->format;
+
+    outlink->format         = convert->out_sample_fmt;
+    outlink->channel_layout = convert->out_channel_layout;
+
+    av_get_channel_layout_string(buf1, sizeof(buf1), -1, inlink ->channel_layout);
+    av_get_channel_layout_string(buf2, sizeof(buf2), -1, outlink->channel_layout);
+    av_log(ctx, AV_LOG_INFO, "fmt:%s cl:%s -> fmt:%s cl:%s\n",
+           av_get_sample_fmt_name(inlink ->format), buf1,
+           av_get_sample_fmt_name(outlink->format), buf2);
+    return 0;
+}
+
+static void convert_to_s16_format(AVFilterLink *inlink, AVFilterBufferRef *insamples)
+{
+    ConvertContext *convert = inlink->dst->priv;
+    AVFilterBufferRef *outsamples = convert->s16_samples;
+    int nb_outchannels, planar, nb_insamples;
+
+    /* Here, out_channels is same as input channels, we are only changing sample format */
+    nb_outchannels = av_get_channel_layout_nb_channels(insamples->audio->channel_layout);
+    planar = insamples->audio->planar;
+    nb_insamples = insamples->audio->samples_nb;
+
+    if (convert->reconfig_sample_fmt || !outsamples || !outsamples->audio->size) {
+        int outsamples_size = nb_outchannels * 2 * insamples->audio->samples_nb;
+
+        if (outsamples)
+            avfilter_unref_buffer(outsamples);
+        outsamples = avfilter_get_audio_buffer(inlink, AV_PERM_WRITE|AV_PERM_REUSE2,
+                                               AV_SAMPLE_FMT_S16, outsamples_size,
+                                               insamples->audio->channel_layout, 0);
+
+        if (convert->convert_to_s16_ctx)
+            av_audio_convert_free(convert->convert_to_s16_ctx);
+        convert->convert_to_s16_ctx =
+            av_audio_convert_alloc(AV_SAMPLE_FMT_S16, nb_outchannels,
+                                   insamples->format, nb_outchannels,
+                                   NULL, 0);
+    }
+
+    /* timestamp and sample rate can change even while sample format/channel layout remain the same */
+    outsamples->pts                 = insamples->pts;
+    outsamples->audio->sample_rate  = insamples->audio->sample_rate;
+
+    av_audio_convert(convert->convert_to_s16_ctx,
+                     (void * const *)      outsamples->data, outsamples->linesize,
+                     (const void * const *) insamples->data, insamples ->linesize, nb_insamples);
+
+    convert->s16_samples     = outsamples;
+    convert->s16_samples_ptr = outsamples;
+}
+
+static void convert_channel_layout(AVFilterLink *inlink)
+{
+    ConvertContext *convert = inlink->dst->priv;
+    AVFilterBufferRef *insamples = convert->s16_samples_ptr;
+    AVFilterBufferRef *outsamples = convert->s16_mid_samples;
+    unsigned int nb_inchannels = av_get_channel_layout_nb_channels(convert->in_channel_layout);
+
+    if (insamples)
+        convert->in_channel_layout = insamples->audio->channel_layout;
+
+    /* Init stage or input channels changed, so reconfigure conversion function pointer */
+    if (convert->reconfig_channel_layout || !convert->channel_conversion) {
+        int64_t  channel_inlayout = convert-> in_channel_layout;
+        int64_t channel_outlayout = convert->out_channel_layout;
+        int nb_outchannels  = av_get_channel_layout_nb_channels(convert->out_channel_layout);
+        int sample_size = av_get_bits_per_sample_fmt(insamples->format) >> 3;
+
+        int outsamples_size = nb_outchannels*sample_size*insamples->audio->samples_nb;
+
+        if (outsamples)
+            avfilter_unref_buffer(outsamples);
+        outsamples = avfilter_get_audio_buffer(inlink, AV_PERM_WRITE|AV_PERM_REUSE2,
+                                               insamples->format, outsamples_size,
+                                               nb_outchannels, 0);
+        /*
+         * Pick appropriate channel conversion function based on input-output channel layouts.
+         * If no suitable conversion function is available, downmix to stereo and set buffer
+         * channel layout to stereo.
+         *
+         * FIXME: Add error handling if channel conversion is unsupported, more channel conversion
+         * routines and finally the ability to handle various stride lengths (sample formats).
+         */
+        if      (channel_inlayout == AV_CH_LAYOUT_STEREO && channel_outlayout == AV_CH_LAYOUT_MONO) {
+            convert->channel_conversion = stereo_to_mono;
+        } else if (channel_inlayout == AV_CH_LAYOUT_MONO && channel_outlayout == AV_CH_LAYOUT_STEREO) {
+            convert->channel_conversion = mono_to_stereo;
+        } else if (channel_inlayout == AV_CH_LAYOUT_STEREO && channel_outlayout == AV_CH_LAYOUT_5POINT1) {
+            convert->channel_conversion = ac3_5p1_mux;
+        } else if (channel_outlayout == AV_CH_LAYOUT_MONO) {
+            convert->channel_conversion = mono_downmix;
+        } else {
+            convert->channel_conversion = stereo_downmix;
+            outsamples->audio->channel_layout = AV_CH_LAYOUT_STEREO;
+        }
+    }
+
+    if (outsamples && insamples)
+        convert->channel_conversion(outsamples->data, insamples->data,
+                                     outsamples->audio->samples_nb,
+                                     nb_inchannels);
+    convert->s16_mid_samples     = outsamples;
+    convert->s16_mid_samples_ptr = outsamples;
+}
+
+static void convert_sample_format(AVFilterLink *inlink)
+{
+    ConvertContext *convert = inlink->dst->priv;
+    AVFilterBufferRef *insamples = convert->s16_mid_samples_ptr;
+    AVFilterBufferRef *outsamples = convert->out_samples;
+    int nb_outchannels, outsample_size, planar, nb_insamples;
+
+    /* Here, nb_outchannels is same as input channels, we are only changing
+     * sample format. */
+    /* FIXME: Need to use hamming weight counting function instead once it is
+     * added to libavutil. */
+    nb_outchannels = av_get_channel_layout_nb_channels(insamples->audio->channel_layout);
+    outsample_size = av_get_bits_per_sample_fmt(convert->out_sample_fmt) >> 3;
+
+    planar       = insamples->audio->planar;
+    nb_insamples = insamples->audio->samples_nb;
+
+    if (convert->reconfig_sample_fmt || !outsamples || !outsamples->audio->size) {
+        int outsamples_size = nb_outchannels * outsample_size * insamples->audio->samples_nb;
+
+        if (outsamples)
+            avfilter_unref_buffer(outsamples);
+        outsamples = avfilter_get_audio_buffer(inlink, AV_PERM_WRITE|AV_PERM_REUSE2,
+                                               convert->out_sample_fmt, outsamples_size,
+                                               insamples->audio->channel_layout, 0);
+
+        if (convert->convert_to_out_ctx)
+            av_audio_convert_free(convert->convert_to_out_ctx);
+        convert->convert_to_out_ctx = av_audio_convert_alloc(convert->out_sample_fmt, nb_outchannels,
+                                                             insamples->format,       nb_outchannels, NULL, 0);
+    }
+
+    /* Timestamp and sample rate can change even while sample format/channel layout remain the same */
+    outsamples->pts                = insamples->pts;
+    outsamples->audio->sample_rate = insamples->audio->sample_rate;
+
+    av_audio_convert(convert->convert_to_out_ctx,
+                     (void * const *)     outsamples->data, outsamples->linesize,
+                     (const void * const *)insamples->data,  insamples->linesize,
+                     nb_insamples);
+
+    convert->out_samples     = outsamples;
+    convert->out_samples_ptr = outsamples;
+}
+
+static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *samplesref)
+{
+    ConvertContext *convert = inlink->dst->priv;
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    int nb_samples_changed = samplesref->audio->samples_nb != convert->in_nb_samples;
+
+    /* if input data of this buffer differs from the earlier buffer/s, set flag
+     * to reconfigure the channel layout and sample format conversions */
+    convert->in_nb_samples = samplesref->audio->samples_nb;
+    convert->reconfig_sample_fmt = (samplesref->format != convert->in_sample_fmt) || nb_samples_changed;
+    convert->in_sample_fmt = samplesref->format;
+    convert->reconfig_channel_layout = (samplesref->audio->channel_layout != convert->in_channel_layout) || nb_samples_changed;
+    convert->in_channel_layout = samplesref->audio->channel_layout;
+
+    /* convert to s16 sample format first... */
+    if (samplesref->format == AV_SAMPLE_FMT_S16)
+        convert->s16_samples_ptr = samplesref;
+    else
+        convert_to_s16_format(inlink, samplesref);
+
+    /* ...then to desired channel layout... */
+    if (samplesref->audio->channel_layout == convert->out_channel_layout)
+        convert->s16_mid_samples_ptr = convert->s16_samples_ptr;
+    else
+        convert_channel_layout(inlink);
+
+    /* ...and finally to desired sample format */
+    if (convert->out_sample_fmt == AV_SAMPLE_FMT_S16)
+        convert->out_samples_ptr = convert->s16_mid_samples_ptr;
+    else
+        convert_sample_format(inlink);
+
+    avfilter_filter_samples(outlink, avfilter_ref_buffer(convert->out_samples_ptr, ~0));
+    avfilter_unref_buffer(samplesref);
+}
+
+AVFilter avfilter_af_aconvert = {
+    .name        = "aconvert",
+    .description = NULL_IF_CONFIG_SMALL("Convert the input audio to sample_fmt:channel_layout."),
+
+    .init      = init,
+    .uninit    = uninit,
+
+    .query_formats = query_formats,
+
+    .priv_size = sizeof(ConvertContext),
+
+    .inputs    = (AVFilterPad[]) {{ .name             = "default",
+                                    .type             = AVMEDIA_TYPE_AUDIO,
+                                    .filter_samples   = filter_samples,
+                                    .config_props     = config_props,
+                                    .min_perms        = AV_PERM_READ, },
+                                  { .name = NULL}},
+    .outputs   = (AVFilterPad[]) {{ .name             = "default",
+                                    .type             = AVMEDIA_TYPE_AUDIO, },
+                                  { .name = NULL}},
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 1dffb80..e7d24c9 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -34,6 +34,7 @@ void avfilter_register_all(void)
         return;
     initialized = 1;
 
+    REGISTER_FILTER (ACONVERT,    aconvert,    af);
     REGISTER_FILTER (ANULL,       anull,       af);
 
     REGISTER_FILTER (ANULLSRC,    anullsrc,    asrc);
-- 
1.7.2.3


--BOKacYhQ+x31HxR3--



More information about the ffmpeg-devel mailing list