vlc/src/audio_output/filters.c

871 lines
27 KiB
C

/*****************************************************************************
* 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;
}
static void aout_FilterDestroy(filter_t *filter)
{
filter_Close(filter);
module_unneed(filter, filter->p_module);
vlc_object_delete(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;
aout_FilterDestroy(p_filter);
if (tab[i].vout != NULL)
vout_Close(tab[i].vout);
if (tab[i].clock != NULL)
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);
vout = NULL;
vlc_clock_Delete(clock);
}
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 = sizeof (filters->tab) / sizeof (filters->tab[0]);
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);
aout_FilterDestroy(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);
}