/*****************************************************************************
 * filters.c : audio output filters management
 *****************************************************************************
 * Copyright (C) 2002-2007 VLC authors and VideoLAN
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <math.h>
#include <string.h>
#include <assert.h>

#include <vlc_common.h>
#include <vlc_configuration.h>
#include <vlc_dialog.h>
#include <vlc_modules.h>
#include <vlc_aout.h>
#include <vlc_filter.h>
#include "../libvlc.h"
#include "aout_internal.h"
#include "../video_output/vout_internal.h" /* for vout_Request */

struct aout_filter
{
    filter_t *f;
    vlc_clock_t *clock;
    vout_thread_t *vout;
};

static inline void aout_filter_Init(struct aout_filter *tab, filter_t *f)
{
    tab->f = f;
    tab->clock = NULL;
    tab->vout = NULL;
}

filter_t *aout_filter_Create(vlc_object_t *obj, const filter_owner_t *restrict owner,
                             const char *type, const char *name,
                             const audio_sample_format_t *infmt,
                             const audio_sample_format_t *outfmt,
                             config_chain_t *cfg, bool const_fmt)
{
    filter_t *filter = vlc_custom_create (obj, sizeof (*filter), type);
    if (unlikely(filter == NULL))
        return NULL;

    if (owner != NULL)
        filter->owner = *owner;
    filter->p_cfg = cfg;
    filter->fmt_in.audio = *infmt;
    filter->fmt_in.i_codec = infmt->i_format;
    filter->fmt_out.audio = *outfmt;
    filter->fmt_out.i_codec = outfmt->i_format;

#ifndef NDEBUG
    /* Assure that infmt/oufmt are well prepared and that channels
     * configurations are valid*/
    if( infmt->i_physical_channels != 0 )
        assert( aout_FormatNbChannels( infmt ) == infmt->i_channels );
    if( outfmt->i_physical_channels != 0 )
        assert( aout_FormatNbChannels( outfmt ) == outfmt->i_channels );
#endif

    filter->p_module = module_need (filter, type, name, false);

#ifndef NDEBUG
    if (filter->p_module == NULL || const_fmt)
    {
        /* If probing failed, formats shall not have been modified. */
        assert (AOUT_FMTS_IDENTICAL(&filter->fmt_in.audio, infmt));
        assert (AOUT_FMTS_IDENTICAL(&filter->fmt_out.audio, outfmt));
    }
#endif

    if (filter->p_module == NULL)
    {
        vlc_object_delete(filter);
        filter = NULL;
    }
    else
    {
        assert (filter->ops != NULL && filter->ops->filter_audio != NULL);
    }
    return filter;
}

static filter_t *FindConverter (vlc_object_t *obj,
                                const audio_sample_format_t *infmt,
                                const audio_sample_format_t *outfmt)
{
    return aout_filter_Create(obj, NULL, "audio converter", NULL, infmt, outfmt,
                              NULL, true);
}

static filter_t *FindResampler (vlc_object_t *obj,
                                const audio_sample_format_t *infmt,
                                const audio_sample_format_t *outfmt)
{
    char *modlist = var_InheritString(obj, "audio-resampler");
    filter_t *filter = aout_filter_Create(obj, NULL, "audio resampler", modlist,
                                          infmt, outfmt, NULL, true);
    free(modlist);
    return filter;
}

/**
 * Destroys a chain of audio filters.
 */
static void aout_FiltersPipelineDestroy(struct aout_filter *tab, unsigned n)
{
    for( unsigned i = 0; i < n; i++ )
    {
        filter_t *p_filter = tab[i].f;

        vlc_filter_Delete(p_filter);
        if (tab[i].vout != NULL)
            vout_Close(tab[i].vout);
        if (tab[i].clock != NULL)
        {
            vlc_clock_Lock(tab[i].clock);
            vlc_clock_Reset(tab[i].clock);
            vlc_clock_Unlock(tab[i].clock);
            vlc_clock_Delete(tab[i].clock);
        }
    }
}

static filter_t *TryFormat (vlc_object_t *obj, vlc_fourcc_t codec,
                            audio_sample_format_t *restrict fmt)
{
    audio_sample_format_t output = *fmt;

    assert (codec != fmt->i_format);
    output.i_format = codec;
    aout_FormatPrepare (&output);

    filter_t *filter = FindConverter (obj, fmt, &output);
    if (filter != NULL)
        *fmt = output;
    return filter;
}

/**
 * Allocates audio format conversion filters
 * @param obj parent VLC object for new filters
 * @param filters table of filters [IN/OUT]
 * @param count pointer to the number of filters in the table [IN/OUT]
 * @param max size of filters table [IN]
 * @param infmt input audio format
 * @param outfmt output audio format
 * @return 0 on success, -1 on failure
 */
static int aout_FiltersPipelineCreate(vlc_object_t *obj, struct aout_filter *filters,
                                      unsigned *count, unsigned max,
                                 const audio_sample_format_t *restrict infmt,
                                 const audio_sample_format_t *restrict outfmt)
{
    aout_FormatsPrint (obj, "conversion:", infmt, outfmt);
    max -= *count;
    filters += *count;

    /* There is a lot of second guessing on what the conversion plugins can
     * and cannot do. This seems hardly avoidable, the conversion problem need
     * to be reduced somehow. */
    audio_sample_format_t input = *infmt;
    unsigned n = 0;

    if (!AOUT_FMT_LINEAR(&input))
    {
        msg_Err(obj, "Can't convert non linear input");
        return -1;
    }

    /* Remix channels */
    if (infmt->i_physical_channels != outfmt->i_physical_channels
     || infmt->i_chan_mode != outfmt->i_chan_mode
     || infmt->channel_type != outfmt->channel_type)
    {   /* Remixing currently requires FL32... TODO: S16N */
        if (input.i_format != VLC_CODEC_FL32)
        {
            if (n == max)
                goto overflow;

            filter_t *f = TryFormat (obj, VLC_CODEC_FL32, &input);
            if (f == NULL)
            {
                msg_Err (obj, "cannot find %s for conversion pipeline",
                         "pre-mix converter");
                goto error;
            }

            aout_filter_Init(&filters[n++], f);
        }

        if (n == max)
            goto overflow;

        audio_sample_format_t output;
        output.i_format = input.i_format;
        output.i_rate = input.i_rate;
        output.i_physical_channels = outfmt->i_physical_channels;
        output.channel_type = outfmt->channel_type;
        output.i_chan_mode = outfmt->i_chan_mode;
        aout_FormatPrepare (&output);

        const char *filter_type =
            infmt->channel_type != outfmt->channel_type ?
            "audio renderer" : "audio converter";

        filter_t *f = aout_filter_Create(obj, NULL, filter_type, NULL,
                                         &input, &output, NULL, true);

        if (f == NULL)
        {
            msg_Err (obj, "cannot find %s for conversion pipeline",
                     "remixer");
            goto error;
        }

        input = output;
        aout_filter_Init(&filters[n++], f);
    }

    /* Resample */
    if (input.i_rate != outfmt->i_rate)
    {   /* Resampling works with any linear format, but may be ugly. */
        if (n == max)
            goto overflow;

        audio_sample_format_t output = input;
        output.i_rate = outfmt->i_rate;

        filter_t *f = FindConverter (obj, &input, &output);
        if (f == NULL)
        {
            msg_Err (obj, "cannot find %s for conversion pipeline",
                     "resampler");
            goto error;
        }

        input = output;
        aout_filter_Init(&filters[n++], f);
    }

    /* Format */
    if (input.i_format != outfmt->i_format)
    {
        if (max == 0)
            goto overflow;

        filter_t *f = TryFormat (obj, outfmt->i_format, &input);
        if (f == NULL)
        {
            msg_Err (obj, "cannot find %s for conversion pipeline",
                     "post-mix converter");
            goto error;
        }
        aout_filter_Init(&filters[n++], f);
    }

    msg_Dbg (obj, "conversion pipeline complete");
    *count += n;
    return 0;

overflow:
    msg_Err (obj, "maximum of %u conversion filters reached", max);
    vlc_dialog_display_error (obj, _("Audio filtering failed"),
        _("The maximum number of filters (%u) was reached."), max);
error:
    aout_FiltersPipelineDestroy (filters, n);
    return -1;
}

/**
 * Filters an audio buffer through a chain of filters.
 */
static block_t *aout_FiltersPipelinePlay(const struct aout_filter *tab,
                                         unsigned count, block_t *block)
{
    /* TODO: use filter chain */
    for (unsigned i = 0; (i < count) && (block != NULL); i++)
    {
        filter_t *filter = tab[i].f;

        /* Please note that p_block->i_nb_samples & i_buffer
         * shall be set by the filter plug-in. */
        block = filter->ops->filter_audio (filter, block);
    }
    return block;
}


/**
 * Drain the chain of filters.
 */
static block_t *aout_FiltersPipelineDrain(const struct aout_filter *tab,
                                          unsigned count)
{
    block_t *chain = NULL;

    for (unsigned i = 0; i < count; i++)
    {
        filter_t *filter = tab[i].f;

        block_t *block = filter_DrainAudio (filter);
        if (block)
        {
            /* If there is a drained block, filter it through the following
             * chain of filters  */
            if (i + 1 < count)
                block = aout_FiltersPipelinePlay (&tab[i + 1],
                                                  count - i - 1, block);
            if (block)
                block_ChainAppend (&chain, block);
        }
    }

    if (chain)
        return block_ChainGather(chain);
    else
        return NULL;
}

/**
 * Flush the chain of filters.
 */
static void aout_FiltersPipelineFlush(const struct aout_filter *tab,
                                      unsigned count)
{
    for (unsigned i = 0; i < count; i++)
        filter_Flush (tab[i].f);
}

static void aout_FiltersPipelineChangeViewpoint(const struct aout_filter *tab,
                                                unsigned count,
                                                const vlc_viewpoint_t *vp)
{
    for (unsigned i = 0; i < count; i++)
        filter_ChangeViewpoint (tab[i].f, vp);
}

#define AOUT_MAX_FILTERS 10

struct aout_filters
{
    filter_t *rate_filter; /**< The filter adjusting samples count
        (either the scaletempo filter or a resampler) */
    struct aout_filter resampler; /**< The resampler */
    int resampling; /**< Current resampling (Hz) */
    vlc_clock_t *clock_source;

    unsigned count; /**< Number of filters */
    struct aout_filter tab[AOUT_MAX_FILTERS]; /**< Configured user filters
        (e.g. equalization) and their conversions */
};

/** Callback for visualization selection */
static int VisualizationCallback (vlc_object_t *obj, const char *var,
                                  vlc_value_t oldval, vlc_value_t newval,
                                  void *data)
{
    const char *mode = newval.psz_string;

    if (!*mode)
        mode = "none";
    /* FIXME: This ugly hack enforced by visual effect-list, as is the need for
     * separate "visual" (external) and "audio-visual" (internal) variables...
     * The visual plugin should have one submodule per effect instead. */
    if (strcasecmp (mode, "none") && strcasecmp (mode, "goom")
     && strcasecmp (mode, "projectm") && strcasecmp (mode, "vsxu")
     && strcasecmp (mode, "glspectrum"))
    {
        var_Create (obj, "effect-list", VLC_VAR_STRING);
        var_SetString (obj, "effect-list", mode);
        mode = "visual";
    }

    var_SetString (obj, "audio-visual", mode);
    aout_InputRequestRestart ((audio_output_t *)obj);
    (void) var; (void) oldval; (void) data;
    return VLC_SUCCESS;
}

struct filter_owner_sys
{
    vlc_clock_t *clock_source;
    vlc_clock_t *clock;
    vout_thread_t *vout;
};

vout_thread_t *aout_filter_GetVout(filter_t *filter, const video_format_t *fmt)
{
    struct filter_owner_sys *owner_sys = filter->owner.sys;
    assert(owner_sys->clock_source != NULL);
    assert(owner_sys->clock == NULL);
    assert(owner_sys->vout == NULL);

    vlc_clock_Lock(owner_sys->clock_source);
    vlc_clock_t *clock = vlc_clock_CreateSlave(owner_sys->clock_source, AUDIO_ES);
    vlc_clock_Unlock(owner_sys->clock_source);
    if (clock == NULL)
        return NULL;

    vout_thread_t *vout = vout_Create(VLC_OBJECT(filter));
    if (unlikely(vout == NULL))
    {
        vlc_clock_Delete(clock);
        return NULL;
    }

    video_format_t adj_fmt = *fmt;
    vout_configuration_t cfg = {
        .vout = vout, .clock = clock, .fmt = &adj_fmt,
    };

    video_format_AdjustColorSpace(&adj_fmt);

    if (vout_Request(&cfg, NULL, NULL)) {
        vout_Close(vout);
        vlc_clock_Delete(clock);
        return NULL;
    }

    owner_sys->clock = clock;
    owner_sys->vout = vout;
    return vout;
}

static int AppendFilter(vlc_object_t *obj, const char *type, const char *name,
                        aout_filters_t *restrict filters,
                        audio_sample_format_t *restrict infmt,
                        const audio_sample_format_t *restrict outfmt,
                        config_chain_t *cfg)
{
    const unsigned max = ARRAY_SIZE(filters->tab);
    if (filters->count >= max)
    {
        msg_Err (obj, "maximum of %u filters reached", max);
        return -1;
    }

    struct filter_owner_sys owner_sys = {
        .clock_source = filters->clock_source,
        .clock = NULL,
        .vout = NULL,
    };
    const filter_owner_t owner = { .sys = &owner_sys };
    filter_t *filter = aout_filter_Create(obj, &owner, type, name,
                                          infmt, outfmt, cfg, false);
    if (filter == NULL)
    {
        msg_Err (obj, "cannot add user %s \"%s\" (skipped)", type, name);
        return -1;
    }

    /* convert to the filter input format if necessary */
    if (aout_FiltersPipelineCreate (obj, filters->tab, &filters->count,
                                    max - 1, infmt, &filter->fmt_in.audio))
    {
        msg_Err (filter, "cannot add user %s \"%s\" (skipped)", type, name);
        vlc_filter_Delete(filter);
        if (owner_sys.vout != NULL)
            vout_Close(owner_sys.vout);
        if (owner_sys.clock != NULL)
            vlc_clock_Delete(owner_sys.clock);
        return -1;
    }

    assert (filters->count < max);
    aout_filter_Init(&filters->tab[filters->count], filter);
    filters->tab[filters->count].clock = owner_sys.clock;
    filters->tab[filters->count].vout = owner_sys.vout;
    filters->count++;
    *infmt = filter->fmt_out.audio;
    return 0;
}

static int AppendRemapFilter(vlc_object_t *obj, aout_filters_t *restrict filters,
                             audio_sample_format_t *restrict infmt,
                             const audio_sample_format_t *restrict outfmt,
                             const int *wg4_remap)
{
    char *name;
    config_chain_t *cfg;

    /* The remap audio filter use a different order than wg4 */
    static const uint8_t wg4_to_remap[] = { 0, 2, 6, 7, 3, 5, 4, 1, 8 };
    int remap[AOUT_CHAN_MAX];
    bool needed = false;
    for (int i = 0; i < AOUT_CHAN_MAX; ++i)
    {
        if (wg4_remap[i] != i)
            needed = true;
        remap[i] = wg4_remap[i] >= 0 ? wg4_to_remap[wg4_remap[i]] : -1;
    }
    if (!needed)
        return 0;

    char *str;
    int ret = asprintf(&str, "remap{channel-left=%d,channel-right=%d,"
                       "channel-middleleft=%d,channel-middleright=%d,"
                       "channel-rearleft=%d,channel-rearright=%d,"
                       "channel-rearcenter=%d,channel-center=%d,"
                       "channel-lfe=%d,normalize=false}",
                       remap[0], remap[1], remap[2], remap[3], remap[4],
                       remap[5], remap[6], remap[7], remap[8]);
    if (ret == -1)
        return -1;

    free(config_ChainCreate(&name, &cfg, str));
    if (name != NULL && cfg != NULL)
        ret = AppendFilter(obj, "audio filter", name, filters,
                           infmt, outfmt, cfg);
    else
        ret = -1;

    free(str);
    free(name);
    if (cfg)
        config_ChainDestroy(cfg);
    return ret;
}

aout_filters_t *aout_FiltersNewWithClock(vlc_object_t *obj, vlc_clock_t *clock,
                                         const audio_sample_format_t *restrict infmt,
                                         const audio_sample_format_t *restrict outfmt,
                                         const aout_filters_cfg_t *cfg)
{
    aout_filters_t *filters = malloc (sizeof (*filters));
    if (unlikely(filters == NULL))
        return NULL;

    filters->rate_filter = NULL;
    aout_filter_Init(&filters->resampler, NULL);
    filters->resampling = 0;
    filters->count = 0;
    filters->clock_source = clock;

    /* Prepare format structure */
    aout_FormatPrint (obj, "input", infmt);
    audio_sample_format_t input_format = *infmt;
    audio_sample_format_t output_format = *outfmt;

    /* Callbacks (before reading values and also before return statement) */
    var_AddCallback (obj, "visual", VisualizationCallback, NULL);

    if (!AOUT_FMT_LINEAR(outfmt))
    {   /* Non-linear output: just convert formats, no filters/visu */
        if (!AOUT_FMTS_IDENTICAL(infmt, outfmt))
        {
            aout_FormatsPrint (obj, "pass-through:", infmt, outfmt);
            filter_t *f = FindConverter(obj, infmt, outfmt);
            if (f == NULL)
            {
                msg_Err (obj, "cannot setup pass-through");
                goto error;
            }
            aout_filter_Init(&filters->tab[filters->count++], f);
        }
        return filters;
    }
    if (aout_FormatNbChannels(outfmt) == 0)
    {
        msg_Warn (obj, "No output channel mask, cannot setup filters");
        goto error;
    }

    assert(output_format.channel_type == AUDIO_CHANNEL_TYPE_BITMAP);
    if (input_format.channel_type != output_format.channel_type)
    {
        /* Do the channel type conversion before any filters since audio
         * converters and filters handle only AUDIO_CHANNEL_TYPE_BITMAP */

        /* convert to the output format (minus resampling) if necessary */
        output_format.i_rate = input_format.i_rate;
        if (aout_FiltersPipelineCreate (obj, filters->tab, &filters->count,
                                  AOUT_MAX_FILTERS, &input_format, &output_format))
        {
            msg_Warn (obj, "cannot setup audio renderer pipeline");
            /* Fallback to bitmap without any conversions */
            input_format.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
            aout_FormatPrepare(&input_format);
        }
        else
            input_format = output_format;
    }

    if (aout_FormatNbChannels(&input_format) == 0)
    {
        /* The input channel map is unknown, use the WAVE one and add a
         * converter that will drop extra channels that are not handled by VLC
         * */
        msg_Info(obj, "unknown channel map, using the WAVE channel layout.");

        assert(input_format.i_channels > 0);
        audio_sample_format_t input_phys_format = input_format;
        aout_SetWavePhysicalChannels(&input_phys_format);

        filter_t *f = FindConverter (obj, &input_format, &input_phys_format);
        if (f == NULL)
        {
            msg_Err (obj, "cannot find channel converter");
            goto error;
        }

        input_format = input_phys_format;
        aout_filter_Init(&filters->tab[filters->count++], f);
    }

    assert(input_format.channel_type == AUDIO_CHANNEL_TYPE_BITMAP);

    /* parse user filter lists */
    if (var_InheritBool (obj, "audio-time-stretch"))
    {
        if (AppendFilter(obj, "audio filter", "scaletempo",
                         filters, &input_format, &output_format, NULL) == 0)
            filters->rate_filter = filters->tab[filters->count - 1].f;
    }

    if (cfg != NULL)
        AppendRemapFilter(obj, filters, &input_format, &output_format,
                          cfg->remap);

    /* Now add user filters */
    char *str = var_InheritString (obj, "audio-filter");
    if (str != NULL)
    {
        char *p = str, *name;
        while ((name = strsep (&p, " :")) != NULL)
        {
            AppendFilter(obj, "audio filter", name, filters,
                         &input_format, &output_format, NULL);
        }
        free (str);
    }

    char *visual = var_InheritString(obj, "audio-visual");
    if (visual != NULL && strcasecmp(visual, "none"))
        AppendFilter(obj, "visualization", visual, filters,
                     &input_format, &output_format, NULL);
    free(visual);

    /* convert to the output format (minus resampling) if necessary */
    output_format.i_rate = input_format.i_rate;
    if (aout_FiltersPipelineCreate (obj, filters->tab, &filters->count,
                              AOUT_MAX_FILTERS, &input_format, &output_format))
    {
        msg_Err (obj, "cannot setup filtering pipeline");
        goto error;
    }
    input_format = output_format;

    /* insert the resampler */
    output_format.i_rate = outfmt->i_rate;
    assert (AOUT_FMTS_IDENTICAL(&output_format, outfmt));
    filters->resampler.f = FindResampler(obj, &input_format,
                                         &output_format);
    if (filters->resampler.f == NULL && input_format.i_rate != outfmt->i_rate)
    {
        msg_Err (obj, "cannot setup a resampler");
        goto error;
    }
    if (filters->rate_filter == NULL)
        filters->rate_filter = filters->resampler.f;

    return filters;

error:
    aout_FiltersPipelineDestroy (filters->tab, filters->count);
    var_DelCallback(obj, "visual", VisualizationCallback, NULL);
    free (filters);
    return NULL;
}

static void aout_FiltersPipelineResetClock(const struct aout_filter *tab,
                                           unsigned count)
{
    for (unsigned i = 0; i < count; i++)
    {
        vlc_clock_t *clock = tab[i].clock;
        if (clock != NULL)
        {
            vlc_clock_Lock(clock);
            vlc_clock_Reset(clock);
            vlc_clock_Unlock(clock);
        }
    }
}

void aout_FiltersResetClock(aout_filters_t *filters)
{
    assert(filters->clock_source);
    aout_FiltersPipelineResetClock(filters->tab, filters->count);
}

static void aout_FiltersPipelineSetClockDelay(const struct aout_filter *tab,
                                              unsigned count, vlc_tick_t delay)
{
    for (unsigned i = 0; i < count; i++)
    {
        vlc_clock_t *clock = tab[i].clock;
        if (clock != NULL)
        {
            vlc_clock_Lock(clock);
            vlc_clock_SetDelay(clock, delay);
            vlc_clock_Unlock(clock);
        }
    }
}

void aout_FiltersSetClockDelay(aout_filters_t *filters, vlc_tick_t delay)
{
    assert(filters->clock_source);
    aout_FiltersPipelineSetClockDelay(filters->tab, filters->count, delay);
}

#undef aout_FiltersNew
/**
 * Sets a chain of audio filters up.
 * \param obj parent object for the filters
 * \param infmt chain input format [IN]
 * \param outfmt chain output format [IN]
 * \param cfg a valid aout_filters_cfg_t struct or NULL.
 * \return a filters chain or NULL on failure
 */
aout_filters_t *aout_FiltersNew(vlc_object_t *obj,
                                const audio_sample_format_t *restrict infmt,
                                const audio_sample_format_t *restrict outfmt,
                                const aout_filters_cfg_t *cfg)
{
    return aout_FiltersNewWithClock(obj, NULL, infmt, outfmt, cfg);
}

#undef aout_FiltersDelete
/**
 * Destroys a chain of audio filters.
 * \param obj object used with aout_FiltersNew()
 * \param filters chain to be destroyed
 */
void aout_FiltersDelete (vlc_object_t *obj, aout_filters_t *filters)
{
    if (filters->resampler.f != NULL)
        aout_FiltersPipelineDestroy(&filters->resampler, 1);
    aout_FiltersPipelineDestroy (filters->tab, filters->count);
    var_DelCallback(obj, "visual", VisualizationCallback, NULL);
    free (filters);
}

bool aout_FiltersCanResample (aout_filters_t *filters)
{
    return (filters->resampler.f != NULL);
}

bool aout_FiltersAdjustResampling (aout_filters_t *filters, int adjust)
{
    if (filters->resampler.f == NULL)
        return false;

    if (adjust)
        filters->resampling += adjust;
    else
        filters->resampling = 0;
    return filters->resampling != 0;
}

block_t *aout_FiltersPlay(aout_filters_t *filters, block_t *block, float rate)
{
    int nominal_rate = 0;

    if (rate != 1.f)
    {
        filter_t *rate_filter = filters->rate_filter;

        if (rate_filter == NULL)
            goto drop; /* Without linear, non-nominal rate is impossible. */

        /* Override input rate */
        nominal_rate = rate_filter->fmt_in.audio.i_rate;
        rate_filter->fmt_in.audio.i_rate = lroundf(nominal_rate * rate);
    }

    block = aout_FiltersPipelinePlay (filters->tab, filters->count, block);
    if (filters->resampler.f != NULL)
    {   /* NOTE: the resampler needs to run even if resampling is 0.
         * The decoder and output rates can still be different. */
        filters->resampler.f->fmt_in.audio.i_rate += filters->resampling;
        block = aout_FiltersPipelinePlay (&filters->resampler, 1, block);
        filters->resampler.f->fmt_in.audio.i_rate -= filters->resampling;
    }

    if (nominal_rate != 0)
    {   /* Restore input rate */
        assert (filters->rate_filter != NULL);
        filters->rate_filter->fmt_in.audio.i_rate = nominal_rate;
    }
    return block;

drop:
    block_Release (block);
    return NULL;
}

block_t *aout_FiltersDrain (aout_filters_t *filters)
{
    /* Drain the filters pipeline */
    block_t *block = aout_FiltersPipelineDrain (filters->tab, filters->count);

    if (filters->resampler.f != NULL)
    {
        block_t *chain = NULL;

        filters->resampler.f->fmt_in.audio.i_rate += filters->resampling;

        if (block)
        {
            /* Resample the drained block from the filters pipeline */
            block = aout_FiltersPipelinePlay (&filters->resampler, 1, block);
            if (block)
                block_ChainAppend (&chain, block);
        }

        /* Drain the resampler filter */
        block = aout_FiltersPipelineDrain (&filters->resampler, 1);
        if (block)
            block_ChainAppend (&chain, block);

        filters->resampler.f->fmt_in.audio.i_rate -= filters->resampling;

        return chain ? block_ChainGather (chain) : NULL;
    }
    else
        return block;
}

void aout_FiltersFlush (aout_filters_t *filters)
{
    aout_FiltersPipelineFlush (filters->tab, filters->count);

    if (filters->resampler.f != NULL)
        aout_FiltersPipelineFlush (&filters->resampler, 1);
}

void aout_FiltersChangeViewpoint (aout_filters_t *filters,
                                  const vlc_viewpoint_t *vp)
{
    aout_FiltersPipelineChangeViewpoint (filters->tab, filters->count, vp);
}
