aboutsummaryrefslogtreecommitdiffstats
path: root/src/filter
diff options
context:
space:
mode:
Diffstat (limited to 'src/filter')
-rw-r--r--src/filter/autoconvert_filter_plugin.c169
-rw-r--r--src/filter/autoconvert_filter_plugin.h34
-rw-r--r--src/filter/chain_filter_plugin.c213
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c147
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/normalize_filter_plugin.c113
-rw-r--r--src/filter/null_filter_plugin.c93
-rw-r--r--src/filter/replay_gain_filter_plugin.c239
-rw-r--r--src/filter/replay_gain_filter_plugin.h50
-rw-r--r--src/filter/route_filter_plugin.c348
-rw-r--r--src/filter/volume_filter_plugin.c161
-rw-r--r--src/filter/volume_filter_plugin.h31
13 files changed, 1682 insertions, 0 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/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h
new file mode 100644
index 000000000..730db197d
--- /dev/null
+++ b/src/filter/autoconvert_filter_plugin.h
@@ -0,0 +1,34 @@
+/*
+ * 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 AUTOCONVERT_FILTER_PLUGIN_H
+#define AUTOCONVERT_FILTER_PLUGIN_H
+
+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.
+ */
+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..4d6080b73
--- /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) {
+ float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode],
+ replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit);
+ 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_get_real_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;
+ enum replay_gain_mode rg_mode;
+
+ /* check if the mode has been changed since the last call */
+ rg_mode = replay_gain_get_real_mode();
+
+ if (filter->mode != rg_mode) {
+ g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode);
+ filter->mode = rg_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);
+
+ 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..6b9aa2a2f
--- /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 ||
+ (unsigned)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/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
new file mode 100644
index 000000000..ad3b2c6f1
--- /dev/null
+++ b/src/filter/volume_filter_plugin.h
@@ -0,0 +1,31 @@
+/*
+ * 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 VOLUME_FILTER_PLUGIN_H
+#define VOLUME_FILTER_PLUGIN_H
+
+struct filter;
+
+unsigned
+volume_filter_get(const struct filter *filter);
+
+void
+volume_filter_set(struct filter *filter, unsigned volume);
+
+#endif