diff options
Diffstat (limited to '')
-rw-r--r-- | src/filter/autoconvert_filter_plugin.c | 169 | ||||
-rw-r--r-- | src/filter/autoconvert_filter_plugin.h (renamed from src/buffer2array.h) | 21 | ||||
-rw-r--r-- | src/filter/chain_filter_plugin.c | 213 | ||||
-rw-r--r-- | src/filter/chain_filter_plugin.h | 48 | ||||
-rw-r--r-- | src/filter/convert_filter_plugin.c | 147 | ||||
-rw-r--r-- | src/filter/convert_filter_plugin.h | 36 | ||||
-rw-r--r-- | src/filter/normalize_filter_plugin.c | 113 | ||||
-rw-r--r-- | src/filter/null_filter_plugin.c | 93 | ||||
-rw-r--r-- | src/filter/replay_gain_filter_plugin.c | 239 | ||||
-rw-r--r-- | src/filter/replay_gain_filter_plugin.h | 50 | ||||
-rw-r--r-- | src/filter/route_filter_plugin.c | 348 | ||||
-rw-r--r-- | src/filter/volume_filter_plugin.c | 161 | ||||
-rw-r--r-- | src/filter/volume_filter_plugin.h (renamed from src/normalize.h) | 21 | ||||
-rw-r--r-- | src/filter_config.c | 119 | ||||
-rw-r--r-- | src/filter_config.h | 47 | ||||
-rw-r--r-- | src/filter_internal.h | 38 | ||||
-rw-r--r-- | src/filter_plugin.c | 116 | ||||
-rw-r--r-- | src/filter_plugin.h | 150 | ||||
-rw-r--r-- | src/filter_registry.c | 44 | ||||
-rw-r--r-- | src/filter_registry.h | 40 |
20 files changed, 2192 insertions, 21 deletions
diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c new file mode 100644 index 000000000..9e197a5f6 --- /dev/null +++ b/src/filter/autoconvert_filter_plugin.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter/autoconvert_filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct autoconvert_filter { + struct filter base; + + /** + * The audio format being fed to the underlying filter. This + * plugin actually doesn't need this variable, we have it here + * just so our open() method doesn't return a stack pointer. + */ + struct audio_format in_audio_format; + + /** + * The underlying filter. + */ + struct filter *filter; + + /** + * A convert_filter, just in case conversion is needed. NULL + * if unused. + */ + struct filter *convert; +}; + +static void +autoconvert_filter_finish(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + filter_free(filter->filter); + g_free(filter); +} + +static const struct audio_format * +autoconvert_filter_open(struct filter *_filter, + struct audio_format *in_audio_format, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + const struct audio_format *out_audio_format; + + assert(audio_format_valid(in_audio_format)); + + /* open the "real" filter */ + + filter->in_audio_format = *in_audio_format; + + out_audio_format = filter_open(filter->filter, + &filter->in_audio_format, error_r); + if (out_audio_format == NULL) + return NULL; + + /* need to convert? */ + + if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) { + /* yes - create a convert_filter */ + struct audio_format audio_format2 = *in_audio_format; + const struct audio_format *audio_format3; + + filter->convert = filter_new(&convert_filter_plugin, NULL, + error_r); + if (filter->convert == NULL) { + filter_close(filter->filter); + return NULL; + } + + audio_format3 = filter_open(filter->convert, &audio_format2, + error_r); + if (audio_format3 == NULL) { + filter_free(filter->convert); + filter_close(filter->filter); + return NULL; + } + + assert(audio_format_equals(&audio_format2, in_audio_format)); + + convert_filter_set(filter->convert, &filter->in_audio_format); + } else + /* no */ + filter->convert = NULL; + + return out_audio_format; +} + +static void +autoconvert_filter_close(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + filter_close(filter->convert); + filter_free(filter->convert); + } + + filter_close(filter->filter); +} + +static const void * +autoconvert_filter_filter(struct filter *_filter, const void *src, + size_t src_size, size_t *dest_size_r, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + src = filter_filter(filter->convert, src, src_size, &src_size, + error_r); + if (src == NULL) + return NULL; + } + + return filter_filter(filter->filter, src, src_size, dest_size_r, + error_r); +} + +static const struct filter_plugin autoconvert_filter_plugin = { + .name = "convert", + .finish = autoconvert_filter_finish, + .open = autoconvert_filter_open, + .close = autoconvert_filter_close, + .filter = autoconvert_filter_filter, +}; + +struct filter * +autoconvert_filter_new(struct filter *_filter) +{ + struct autoconvert_filter *filter = + g_new(struct autoconvert_filter, 1); + + filter_init(&filter->base, &autoconvert_filter_plugin); + filter->filter = _filter; + + return &filter->base; +} diff --git a/src/buffer2array.h b/src/filter/autoconvert_filter_plugin.h index bed23a29f..730db197d 100644 --- a/src/buffer2array.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_BUFFER_2_ARRAY_H -#define MPD_BUFFER_2_ARRAY_H +#ifndef AUTOCONVERT_FILTER_PLUGIN_H +#define AUTOCONVERT_FILTER_PLUGIN_H -/* tokenizes up to max elements in buffer (a null-terminated string) and - * stores the result in array (which must be capable of holding up to - * max elements). Tokenization is based on C string quoting rules. - * The arguments buffer and array are modified. - * Returns the number of elements tokenized. +struct filter; + +/** + * Creates a new "autoconvert" filter. When opened, it ensures that + * the input audio format isn't changed. If the underlying filter + * requests a different format, it automatically creates a + * convert_filter. */ -int buffer2array(char *buffer, char *array[], const int max); +struct filter * +autoconvert_filter_new(struct filter *filter); #endif diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c new file mode 100644 index 000000000..06d4d0e6b --- /dev/null +++ b/src/filter/chain_filter_plugin.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" + +#include <assert.h> + +struct filter_chain { + /** the base class */ + struct filter base; + + GSList *children; +}; + +static inline GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +static struct filter * +chain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct filter_chain *chain = g_new(struct filter_chain, 1); + + filter_init(&chain->base, &chain_filter_plugin); + chain->children = NULL; + + return &chain->base; +} + +static void +chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct filter *filter = data; + + filter_free(filter); +} + +static void +chain_filter_finish(struct filter *_filter) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + g_slist_foreach(chain->children, chain_free_child, NULL); + g_slist_free(chain->children); + + g_free(chain); +} + +/** + * Close all filters in the chain until #until is reached. #until + * itself is not closed. + */ +static void +chain_close_until(struct filter_chain *chain, const struct filter *until) +{ + GSList *i = chain->children; + struct filter *filter; + + while (true) { + /* this assertion fails if #until does not exist + (anymore) */ + assert(i != NULL); + + if (i->data == until) + /* don't close this filter */ + break; + + /* close this filter */ + filter = i->data; + filter_close(filter); + + i = g_slist_next(i); + } +} + +static const struct audio_format * +chain_open_child(struct filter *filter, + const struct audio_format *prev_audio_format, + GError **error_r) +{ + struct audio_format conv_audio_format = *prev_audio_format; + const struct audio_format *next_audio_format; + + next_audio_format = filter_open(filter, &conv_audio_format, error_r); + if (next_audio_format == NULL) + return NULL; + + if (!audio_format_equals(&conv_audio_format, prev_audio_format)) { + struct audio_format_string s; + + filter_close(filter); + g_set_error(error_r, filter_quark(), 0, + "Audio format not supported by filter '%s': %s", + filter->plugin->name, + audio_format_to_string(prev_audio_format, &s)); + return NULL; + } + + return next_audio_format; +} + +static const struct audio_format * +chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format, + GError **error_r) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + const struct audio_format *audio_format = in_audio_format; + + for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { + struct filter *filter = i->data; + + audio_format = chain_open_child(filter, audio_format, error_r); + if (audio_format == NULL) { + /* rollback, close all children */ + chain_close_until(chain, filter); + return NULL; + } + } + + /* return the output format of the last filter */ + return audio_format; +} + +static void +chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct filter *filter = data; + + filter_close(filter); +} + +static void +chain_filter_close(struct filter *_filter) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + g_slist_foreach(chain->children, chain_close_child, NULL); +} + +static const void * +chain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { + struct filter *filter = i->data; + + /* feed the output of the previous filter as input + into the current one */ + src = filter_filter(filter, src, src_size, &src_size, error_r); + if (src == NULL) + return NULL; + } + + /* return the output of the last filter */ + *dest_size_r = src_size; + return src; +} + +const struct filter_plugin chain_filter_plugin = { + .name = "chain", + .init = chain_filter_init, + .finish = chain_filter_finish, + .open = chain_filter_open, + .close = chain_filter_close, + .filter = chain_filter_filter, +}; + +struct filter * +filter_chain_new(void) +{ + struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL); + /* chain_filter_init() never fails */ + assert(filter != NULL); + + return filter; +} + +void +filter_chain_append(struct filter *_chain, struct filter *filter) +{ + struct filter_chain *chain = (struct filter_chain *)_chain; + + chain->children = g_slist_append(chain->children, filter); +} + diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h new file mode 100644 index 000000000..42c6a9b78 --- /dev/null +++ b/src/filter/chain_filter_plugin.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * A filter chain is a container for several filters. They are + * chained together, i.e. called in a row, one filter passing its + * output to the next one. + */ + +#ifndef MPD_FILTER_CHAIN_H +#define MPD_FILTER_CHAIN_H + +struct filter; + +/** + * Creates a new filter chain. + */ +struct filter * +filter_chain_new(void); + +/** + * Appends a new filter at the end of the filter chain. You must call + * this function before the first filter_open() call. + * + * @param chain the filter chain created with filter_chain_new() + * @param filter the filter to be appended to #chain + */ +void +filter_chain_append(struct filter *chain, struct filter *filter); + +#endif diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c new file mode 100644 index 000000000..cb9e0940a --- /dev/null +++ b/src/filter/convert_filter_plugin.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct convert_filter { + struct filter base; + + /** + * The current convert, from 0 to #PCM_CONVERT_1. + */ + unsigned convert; + + /** + * The input audio format; PCM data is passed to the filter() + * method in this format. + */ + struct audio_format in_audio_format; + + /** + * The output audio format; the consumer of this plugin + * expects PCM data in this format. This defaults to + * #in_audio_format, and can be set with convert_filter_set(). + */ + struct audio_format out_audio_format; + + struct pcm_convert_state state; +}; + +static struct filter * +convert_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct convert_filter *filter = g_new(struct convert_filter, 1); + + filter_init(&filter->base, &convert_filter_plugin); + return &filter->base; +} + +static void +convert_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +convert_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + assert(audio_format_valid(audio_format)); + + filter->in_audio_format = filter->out_audio_format = *audio_format; + pcm_convert_init(&filter->state); + + return &filter->in_audio_format; +} + +static void +convert_filter_close(struct filter *_filter) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + pcm_convert_deinit(&filter->state); + + poison_undefined(&filter->in_audio_format, + sizeof(filter->in_audio_format)); + poison_undefined(&filter->out_audio_format, + sizeof(filter->out_audio_format)); +} + +static const void * +convert_filter_filter(struct filter *_filter, const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + const void *dest; + + if (audio_format_equals(&filter->in_audio_format, + &filter->out_audio_format)) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + dest = pcm_convert(&filter->state, &filter->in_audio_format, + src, src_size, + &filter->out_audio_format, dest_size_r, + error_r); + if (dest == NULL) + return NULL; + + return dest; +} + +const struct filter_plugin convert_filter_plugin = { + .name = "convert", + .init = convert_filter_init, + .finish = convert_filter_finish, + .open = convert_filter_open, + .close = convert_filter_close, + .filter = convert_filter_filter, +}; + +void +convert_filter_set(struct filter *_filter, + const struct audio_format *out_audio_format) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + assert(filter != NULL); + assert(audio_format_valid(&filter->in_audio_format)); + assert(audio_format_valid(&filter->out_audio_format)); + assert(out_audio_format != NULL); + assert(audio_format_valid(out_audio_format)); + assert(filter->in_audio_format.reverse_endian == 0); + + filter->out_audio_format = *out_audio_format; +} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h new file mode 100644 index 000000000..ba9180e64 --- /dev/null +++ b/src/filter/convert_filter_plugin.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#ifndef CONVERT_FILTER_PLUGIN_H +#define CONVERT_FILTER_PLUGIN_H + +struct filter; +struct audio_format; + +/** + * Sets the output audio format for the specified filter. You must + * call this after the filter has been opened. Since this audio + * format switch is a violation of the filter API, this filter must be + * the last in a chain. + */ +void +convert_filter_set(struct filter *filter, + const struct audio_format *out_audio_format); + +#endif diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c new file mode 100644 index 000000000..63bbb6e4f --- /dev/null +++ b/src/filter/normalize_filter_plugin.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" +#include "audio_format.h" +#include "AudioCompress/compress.h" + +#include <assert.h> +#include <string.h> + +struct normalize_filter { + struct filter filter; + + struct Compressor *compressor; + + struct pcm_buffer buffer; +}; + +static inline GQuark +normalize_quark(void) +{ + return g_quark_from_static_string("normalize"); +} + +static struct filter * +normalize_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = g_new(struct normalize_filter, 1); + + filter_init(&filter->filter, &normalize_filter_plugin); + + return &filter->filter; +} + +static void +normalize_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +normalize_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->reverse_endian = false; + + filter->compressor = Compressor_new(0); + + pcm_buffer_init(&filter->buffer); + + return audio_format; +} + +static void +normalize_filter_close(struct filter *_filter) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); + Compressor_delete(filter->compressor); +} + +static const void * +normalize_filter_filter(struct filter *_filter, + const void *src, size_t src_size, size_t *dest_size_r, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + void *dest; + + dest = pcm_buffer_get(&filter->buffer, src_size); + + memcpy(dest, src, src_size); + + Compressor_Process_int16(filter->compressor, dest, src_size / 2); + + *dest_size_r = src_size; + return dest; +} + +const struct filter_plugin normalize_filter_plugin = { + .name = "normalize", + .init = normalize_filter_init, + .finish = normalize_filter_finish, + .open = normalize_filter_open, + .close = normalize_filter_close, + .filter = normalize_filter_filter, +}; diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c new file mode 100644 index 000000000..650f95bc4 --- /dev/null +++ b/src/filter/null_filter_plugin.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * This filter plugin does nothing. That is not quite useful, except + * for testing the filter core, or as a template for new filter + * plugins. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <assert.h> + +struct null_filter { + struct filter filter; +}; + +static struct filter * +null_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = g_new(struct null_filter, 1); + + filter_init(&filter->filter, &null_filter_plugin); + return &filter->filter; +} + +static void +null_filter_finish(struct filter *_filter) +{ + struct null_filter *filter = (struct null_filter *)_filter; + + g_free(filter); +} + +static const struct audio_format * +null_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; + + return audio_format; +} + +static void +null_filter_close(struct filter *_filter) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; +} + +static const void * +null_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; + + /* return the unmodified source buffer */ + *dest_size_r = src_size; + return src; +} + +const struct filter_plugin null_filter_plugin = { + .name = "null", + .init = null_filter_init, + .finish = null_filter_finish, + .open = null_filter_open, + .close = null_filter_close, + .filter = null_filter_filter, +}; diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c new file mode 100644 index 000000000..c8e74c7fe --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter/replay_gain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "replay_gain_info.h" +#include "replay_gain_config.h" +#include "mixer_control.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "replay_gain" + +struct replay_gain_filter { + struct filter filter; + + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + struct mixer *mixer; + + /** + * The base volume level for scale=1.0, between 1 and 100 + * (including). + */ + unsigned base; + + enum replay_gain_mode mode; + + struct replay_gain_info info; + + /** + * The current volume, between 0 and #PCM_VOLUME_1 (both + * including). + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +replay_gain_quark(void) +{ + return g_quark_from_static_string("replay_gain"); +} + +/** + * Recalculates the new volume after a property was changed. + */ +static void +replay_gain_filter_update(struct replay_gain_filter *filter) +{ + if (filter->mode != REPLAY_GAIN_OFF) { + const struct replay_gain_tuple *tuple = + &filter->info.tuples[filter->mode]; + float scale = replay_gain_tuple_defined(tuple) + ? replay_gain_tuple_scale(tuple, replay_gain_preamp) + : replay_gain_missing_preamp; + g_debug("scale=%f\n", (double)scale); + + filter->volume = pcm_float_to_volume(scale); + } else + filter->volume = PCM_VOLUME_1; + + if (filter->mixer != NULL) { + /* update the hardware mixer volume */ + + unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1; + if (volume > 100) + volume = 100; + + GError *error = NULL; + if (!mixer_set_volume(filter->mixer, volume, &error)) { + g_warning("Failed to update hardware mixer: %s", + error->message); + g_error_free(error); + } + } +} + +static struct filter * +replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); + + filter_init(&filter->filter, &replay_gain_filter_plugin); + filter->mixer = NULL; + + filter->mode = replay_gain_mode; + replay_gain_info_init(&filter->info); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +replay_gain_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +replay_gain_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +replay_gain_filter_close(struct filter *_filter) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +replay_gain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + bool success; + void *dest; + + /* check if the mode has been changed since the last call */ + if (filter->mode != replay_gain_mode) { + filter->mode = replay_gain_mode; + replay_gain_filter_update(filter); + } + + *dest_size_r = src_size; + + if (filter->volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, src_size); + *dest_size_r = src_size; + + if (filter->volume <= 0) { + /* optimized special case: 0% volume = memset(0) */ + /* XXX is this valid for all sample formats? What + about floating point? */ + memset(dest, 0, src_size); + return dest; + } + + memcpy(dest, src, src_size); + + success = pcm_volume(dest, src_size, &filter->audio_format, + filter->volume); + if (!success) { + g_set_error(error_r, replay_gain_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + .name = "replay_gain", + .init = replay_gain_filter_init, + .finish = replay_gain_filter_finish, + .open = replay_gain_filter_open, + .close = replay_gain_filter_close, + .filter = replay_gain_filter_filter, +}; + +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + assert(mixer == NULL || (base > 0 && base <= 100)); + + filter->mixer = mixer; + filter->base = base; + + replay_gain_filter_update(filter); +} + +void +replay_gain_filter_set_info(struct filter *_filter, + const struct replay_gain_info *info) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + if (info != NULL) { + filter->info = *info; + replay_gain_info_complete(&filter->info); + } else + replay_gain_info_init(&filter->info); + + replay_gain_filter_update(filter); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h new file mode 100644 index 000000000..348b4f50c --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#ifndef REPLAY_GAIN_FILTER_PLUGIN_H +#define REPLAY_GAIN_FILTER_PLUGIN_H + +#include "replay_gain_info.h" + +struct filter; +struct mixer; + +/** + * Enables or disables the hardware mixer for applying replay gain. + * + * @param mixer the hardware mixer, or NULL to fall back to software + * volume + * @param base the base volume level for scale=1.0, between 1 and 100 + * (including). + */ +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base); + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or NULL if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(struct filter *filter, + const struct replay_gain_info *info); + +#endif diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c new file mode 100644 index 000000000..4f8f4cde1 --- /dev/null +++ b/src/filter/route_filter_plugin.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * This filter copies audio data between channels. Useful for + * upmixing mono/stereo audio to surround speaker configurations. + * + * Its configuration consists of a "filter" section with a single + * "routes" entry, formatted as: \\ + * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ + * where each pair of numbers signifies a set of channels. + * Each source>dest pair leads to the data from channel #source + * being copied to channel #dest in the output. + * + * Example: \\ + * routes "0>0, 1>1, 0>2, 1>3"\\ + * upmixes stereo audio to a 4-speaker system, copying the front-left + * (0) to front left (0) and rear left (2), copying front-right (1) to + * front-right (1) and rear-right (3). + * + * If multiple sources are copied to the same destination channel, only + * one of them takes effect. + */ + +#include "config.h" +#include "conf.h" +#include "audio_format.h" +#include "audio_check.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + + +struct route_filter { + + /** + * Inherit (and support cast to/from) filter + */ + struct filter base; + + /** + * The minimum number of channels we need for output + * to be able to perform all the copies the user has specified + */ + unsigned char min_output_channels; + + /** + * The minimum number of input channels we need to + * copy all the data the user has requested. If fewer + * than this many are supplied by the input, undefined + * copy operations are given zeroed sources in stead. + */ + unsigned char min_input_channels; + + /** + * The set of copy operations to perform on each sample + * The index is an output channel to use, the value is + * a corresponding input channel from which to take the + * data. A -1 means "no source" + */ + signed char* sources; + + /** + * The actual input format of our signal, once opened + */ + struct audio_format input_format; + + /** + * The decided upon output format, once opened + */ + struct audio_format output_format; + + /** + * The size, in bytes, of each multichannel frame in the + * input buffer + */ + size_t input_frame_size; + + /** + * The size, in bytes, of each multichannel frame in the + * output buffer + */ + size_t output_frame_size; + + /** + * The output buffer used last time around, can be reused if the size doesn't differ. + */ + struct pcm_buffer output_buffer; + +}; + +/** + * Parse the "routes" section, a string on the form + * a>b, c>d, e>f, ... + * where a... are non-unique, non-negative integers + * and input channel a gets copied to output channel b, etc. + * @param param the configuration block to read + * @param filter a route_filter whose min_channels and sources[] to set + * @return true on success, false on error + */ +static bool +route_filter_parse(const struct config_param *param, + struct route_filter *filter, + GError **error_r) { + + /* TODO: + * With a more clever way of marking "don't copy to output N", + * This could easily be merged into a single loop with some + * dynamic g_realloc() instead of one count run and one g_malloc(). + */ + + gchar **tokens; + int number_of_copies; + + // A cowardly default, just passthrough stereo + const char *routes = + config_get_block_string(param, "routes", "0>0, 1>1"); + + filter->min_input_channels = 0; + filter->min_output_channels = 0; + + tokens = g_strsplit(routes, ",", 255); + number_of_copies = g_strv_length(tokens); + + // Start by figuring out a few basic things about the routing set + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Squeeze whitespace + g_strstrip(tokens[c]); + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + // Keep track of the highest channel numbers seen + // as either in- or outputs + if (source >= filter->min_input_channels) + filter->min_input_channels = source + 1; + if (dest >= filter->min_output_channels) + filter->min_output_channels = dest + 1; + + g_strfreev(sd); + } + + if (!audio_valid_channel_count(filter->min_output_channels)) { + g_strfreev(tokens); + g_set_error(error_r, audio_format_quark(), 0, + "Invalid number of output channels requested: %d", + filter->min_output_channels); + return false; + } + + // Allocate a map of "copy nothing to me" + filter->sources = + g_malloc(filter->min_output_channels * sizeof(signed char)); + + for (int i=0; i<filter->min_output_channels; ++i) + filter->sources[i] = -1; + + // Run through the spec again, and save the + // actual mapping output <- input + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + filter->sources[dest] = source; + + g_strfreev(sd); + } + + g_strfreev(tokens); + + return true; +} + +static struct filter * +route_filter_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = g_new(struct route_filter, 1); + filter_init(&filter->base, &route_filter_plugin); + + // Allocate and set the filter->sources[] array + route_filter_parse(param, filter, error_r); + + return &filter->base; +} + +static void +route_filter_finish(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + g_free(filter->sources); + g_free(filter); +} + +static const struct audio_format * +route_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + // Copy the input format for later reference + filter->input_format = *audio_format; + filter->input_frame_size = + audio_format_frame_size(&filter->input_format); + + // Decide on an output format which has enough channels, + // and is otherwise identical + filter->output_format = *audio_format; + filter->output_format.channels = filter->min_output_channels; + + // Precalculate this simple value, to speed up allocation later + filter->output_frame_size = + audio_format_frame_size(&filter->output_format); + + // This buffer grows as needed + pcm_buffer_init(&filter->output_buffer); + + return &filter->output_format; +} + +static void +route_filter_close(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + pcm_buffer_deinit(&filter->output_buffer); +} + +static const void * +route_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + size_t number_of_frames = src_size / filter->input_frame_size; + + size_t bytes_per_frame_per_channel = + audio_format_sample_size(&filter->input_format); + + // A moving pointer that always refers to channel 0 in the input, at the currently handled frame + const uint8_t *base_source = src; + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination; + + // Grow our reusable buffer, if needed, and set the moving pointer + *dest_size_r = number_of_frames * filter->output_frame_size; + chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r); + + + // Perform our copy operations, with N input channels and M output channels + for (unsigned int s=0; s<number_of_frames; ++s) { + + // Need to perform one copy per output channel + for (unsigned int c=0; c<filter->min_output_channels; ++c) { + if (filter->sources[c] == -1 || + filter->sources[c] >= filter->input_format.channels) { + // No source for this destination output, + // give it zeroes as input + memset(chan_destination, + 0x00, + bytes_per_frame_per_channel); + } else { + // Get the data from channel sources[c] + // and copy it to the output + const uint8_t *data = base_source + + (filter->sources[c] * bytes_per_frame_per_channel); + memcpy(chan_destination, + data, + bytes_per_frame_per_channel); + } + // Move on to the next output channel + chan_destination += bytes_per_frame_per_channel; + } + + + // Go on to the next N input samples + base_source += filter->input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return (void *) filter->output_buffer.buffer; +} + +const struct filter_plugin route_filter_plugin = { + .name = "route", + .init = route_filter_init, + .finish = route_filter_finish, + .open = route_filter_open, + .close = route_filter_close, + .filter = route_filter_filter, +}; diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c new file mode 100644 index 000000000..42311ca5e --- /dev/null +++ b/src/filter/volume_filter_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter/volume_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "audio_format.h" +#include "player_control.h" + +#include <assert.h> +#include <string.h> + +struct volume_filter { + struct filter filter; + + /** + * The current volume, from 0 to #PCM_VOLUME_1. + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +volume_quark(void) +{ + return g_quark_from_static_string("pcm_volume"); +} + +static struct filter * +volume_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = g_new(struct volume_filter, 1); + + filter_init(&filter->filter, &volume_filter_plugin); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +volume_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +volume_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +volume_filter_close(struct filter *_filter) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +volume_filter_filter(struct filter *_filter, const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + bool success; + void *dest; + + *dest_size_r = src_size; + + if (filter->volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, src_size); + + if (filter->volume <= 0) { + /* optimized special case: 0% volume = memset(0) */ + /* XXX is this valid for all sample formats? What + about floating point? */ + memset(dest, 0, src_size); + return dest; + } + + memcpy(dest, src, src_size); + + success = pcm_volume(dest, src_size, &filter->audio_format, + filter->volume); + if (!success) { + g_set_error(error_r, volume_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin volume_filter_plugin = { + .name = "volume", + .init = volume_filter_init, + .finish = volume_filter_finish, + .open = volume_filter_open, + .close = volume_filter_close, + .filter = volume_filter_filter, +}; + +unsigned +volume_filter_get(const struct filter *_filter) +{ + const struct volume_filter *filter = + (const struct volume_filter *)_filter; + + assert(filter->filter.plugin == &volume_filter_plugin); + assert(filter->volume <= PCM_VOLUME_1); + + return filter->volume; +} + +void +volume_filter_set(struct filter *_filter, unsigned volume) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + assert(filter->filter.plugin == &volume_filter_plugin); + assert(volume <= PCM_VOLUME_1); + + filter->volume = volume; +} + diff --git a/src/normalize.h b/src/filter/volume_filter_plugin.h index a8144951d..ad3b2c6f1 100644 --- a/src/normalize.h +++ b/src/filter/volume_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NORMALIZE_H -#define MPD_NORMALIZE_H +#ifndef VOLUME_FILTER_PLUGIN_H +#define VOLUME_FILTER_PLUGIN_H -struct audio_format; +struct filter; -extern int normalizationEnabled; +unsigned +volume_filter_get(const struct filter *filter); -void initNormalization(void); +void +volume_filter_set(struct filter *filter, unsigned volume); -void finishNormalization(void); - -void normalizeData(char *buffer, int bufferSize, - const struct audio_format *format); - -#endif /* !NORMALIZE_H */ +#endif diff --git a/src/filter_config.c b/src/filter_config.c new file mode 100644 index 000000000..90de199b7 --- /dev/null +++ b/src/filter_config.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "filter_config.h" +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <string.h> + + +static GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +/** + * Find the "filter" configuration block for the specified name. + * + * @param filter_template_name the name of the filter template + * @param error_r space to return an error description + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +filter_plugin_config(const char *filter_template_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) { + g_set_error(error_r, filter_quark(), 1, + "filter configuration without 'name' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, filter_template_name) == 0) + return param; + } + + g_set_error(error_r, filter_quark(), 1, + "filter template not found: %s", + filter_template_name); + + return NULL; +} + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r) +{ + + // Split on comma + gchar** tokens = g_strsplit_set(spec, ",", 255); + + int added_filters = 0; + + // Add each name to the filter chain by instantiating an actual filter + char **template_names = tokens; + while (*template_names != NULL) { + struct filter *f; + const struct config_param *cfg; + + // Squeeze whitespace + g_strstrip(*template_names); + + cfg = filter_plugin_config(*template_names, error_r); + if (cfg == NULL) { + // The error has already been set, just stop. + break; + } + + // Instantiate one of those filter plugins with the template name as a hint + f = filter_configured_new(cfg, error_r); + if (f == NULL) { + // The error has already been set, just stop. + break; + } + + filter_chain_append(chain, f); + ++added_filters; + + ++template_names; + } + + g_strfreev(tokens); + + return added_filters; +} diff --git a/src/filter_config.h b/src/filter_config.h new file mode 100644 index 000000000..9ed4d204b --- /dev/null +++ b/src/filter_config.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * Utility functions for filter configuration + */ + +#ifndef MPD_FILTER_CONFIG_H +#define MPD_FILTER_CONFIG_H + +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r); + +#endif diff --git a/src/filter_internal.h b/src/filter_internal.h new file mode 100644 index 000000000..8dd6da491 --- /dev/null +++ b/src/filter_internal.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * Internal stuff for the filter core and filter plugins. + */ + +#ifndef MPD_FILTER_INTERNAL_H +#define MPD_FILTER_INTERNAL_H + +struct filter { + const struct filter_plugin *plugin; +}; + +static inline void +filter_init(struct filter *filter, const struct filter_plugin *plugin) +{ + filter->plugin = plugin; +} + +#endif diff --git a/src/filter_plugin.c b/src/filter_plugin.c new file mode 100644 index 000000000..492d703ac --- /dev/null +++ b/src/filter_plugin.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" + +#ifndef NDEBUG +#include "audio_format.h" +#endif + +#include <assert.h> + +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(plugin != NULL); + assert(error_r == NULL || *error_r == NULL); + + return plugin->init(param, error_r); +} + +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r) +{ + const char *plugin_name; + const struct filter_plugin *plugin; + + assert(param != NULL); + assert(error_r == NULL || *error_r == NULL); + + plugin_name = config_get_block_string(param, "plugin", NULL); + if (plugin_name == NULL) { + g_set_error(error_r, config_quark(), 0, + "No filter plugin specified"); + return NULL; + } + + plugin = filter_plugin_by_name(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, config_quark(), 0, + "No such filter plugin: %s", plugin_name); + return NULL; + } + + return filter_new(plugin, param, error_r); +} + +void +filter_free(struct filter *filter) +{ + assert(filter != NULL); + + filter->plugin->finish(filter); +} + +const struct audio_format * +filter_open(struct filter *filter, struct audio_format *audio_format, + GError **error_r) +{ + const struct audio_format *out_audio_format; + + assert(filter != NULL); + assert(audio_format != NULL); + assert(audio_format_valid(audio_format)); + assert(error_r == NULL || *error_r == NULL); + + out_audio_format = filter->plugin->open(filter, audio_format, error_r); + + assert(out_audio_format == NULL || audio_format_valid(audio_format)); + assert(out_audio_format == NULL || + audio_format_valid(out_audio_format)); + + return out_audio_format; +} + +void +filter_close(struct filter *filter) +{ + assert(filter != NULL); + + filter->plugin->close(filter); +} + +const void * +filter_filter(struct filter *filter, const void *src, size_t src_size, + size_t *dest_size_r, + GError **error_r) +{ + assert(filter != NULL); + assert(src != NULL); + assert(src_size > 0); + assert(dest_size_r != NULL); + assert(error_r == NULL || *error_r == NULL); + + return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r); +} diff --git a/src/filter_plugin.h b/src/filter_plugin.h new file mode 100644 index 000000000..ac6b34522 --- /dev/null +++ b/src/filter_plugin.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * This header declares the filter_plugin class. It describes a + * plugin API for objects which filter raw PCM data. + */ + +#ifndef MPD_FILTER_PLUGIN_H +#define MPD_FILTER_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> +#include <stddef.h> + +struct config_param; +struct filter; + +struct filter_plugin { + const char *name; + + /** + * Allocates and configures a filter. + */ + struct filter *(*init)(const struct config_param *param, + GError **error_r); + + /** + * Free instance data. + */ + void (*finish)(struct filter *filter); + + /** + * Opens a filter. + * + * @param audio_format the audio format of incoming data; the + * plugin may modify the object to enforce another input + * format + */ + const struct audio_format * + (*open)(struct filter *filter, + struct audio_format *audio_format, + GError **error_r); + + /** + * Closes a filter. + */ + void (*close)(struct filter *filter); + + /** + * Filters a block of PCM data. + */ + const void *(*filter)(struct filter *filter, + const void *src, size_t src_size, + size_t *dest_buffer_r, + GError **error_r); +}; + +/** + * Creates a new instance of the specified filter plugin. + * + * @param plugin the filter plugin + * @param param optional configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r); + +/** + * Creates a new filter, loads configuration and the plugin name from + * the specified configuration section. + * + * @param param the configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r); + +/** + * Deletes a filter. It must be closed prior to calling this + * function, see filter_close(). + * + * @param filter the filter object + */ +void +filter_free(struct filter *filter); + +/** + * Opens the filter, preparing it for filter_filter(). + * + * @param filter the filter object + * @param audio_format the audio format of incoming data; the plugin + * may modify the object to enforce another input format + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return the format of outgoing data + */ +const struct audio_format * +filter_open(struct filter *filter, struct audio_format *audio_format, + GError **error_r); + +/** + * Closes the filter. After that, you may call filter_open() again. + * + * @param filter the filter object + */ +void +filter_close(struct filter *filter); + +/** + * Filters a block of PCM data. + * + * @param filter the filter object + * @param src the input buffer + * @param src_size the size of #src_buffer in bytes + * @param dest_size_r the size of the returned buffer + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return the destination buffer on success (will be invalidated by + * filter_close() or filter_filter()), NULL on error + */ +const void * +filter_filter(struct filter *filter, const void *src, size_t src_size, + size_t *dest_size_r, + GError **error_r); + +#endif diff --git a/src/filter_registry.c b/src/filter_registry.c new file mode 100644 index 000000000..150043cc5 --- /dev/null +++ b/src/filter_registry.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#include "config.h" +#include "filter_registry.h" +#include "filter_plugin.h" + +#include <stddef.h> +#include <string.h> + +const struct filter_plugin *const filter_plugins[] = { + &null_filter_plugin, + &route_filter_plugin, + &normalize_filter_plugin, + &volume_filter_plugin, + &replay_gain_filter_plugin, + NULL, +}; + +const struct filter_plugin * +filter_plugin_by_name(const char *name) +{ + for (unsigned i = 0; filter_plugins[i] != NULL; ++i) + if (strcmp(filter_plugins[i]->name, name) == 0) + return filter_plugins[i]; + + return NULL; +} diff --git a/src/filter_registry.h b/src/filter_registry.h new file mode 100644 index 000000000..551a7afa1 --- /dev/null +++ b/src/filter_registry.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/** \file + * + * This library manages all filter plugins which are enabled at + * compile time. + */ + +#ifndef MPD_FILTER_REGISTRY_H +#define MPD_FILTER_REGISTRY_H + +extern const struct filter_plugin null_filter_plugin; +extern const struct filter_plugin chain_filter_plugin; +extern const struct filter_plugin convert_filter_plugin; +extern const struct filter_plugin route_filter_plugin; +extern const struct filter_plugin normalize_filter_plugin; +extern const struct filter_plugin volume_filter_plugin; +extern const struct filter_plugin replay_gain_filter_plugin; + +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif |