aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile.am1
-rw-r--r--src/filter/route_filter_plugin.c345
-rw-r--r--src/filter_registry.c1
-rw-r--r--src/filter_registry.h1
4 files changed, 348 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 760e07765..a55a5e1a4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -703,6 +703,7 @@ FILTER_SRC = \
src/filter/null_filter_plugin.c \
src/filter/chain_filter_plugin.c \
src/filter/convert_filter_plugin.c \
+ src/filter/route_filter_plugin.c \
src/filter/volume_filter_plugin.c
diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c
new file mode 100644
index 000000000..673f32d5c
--- /dev/null
+++ b/src/filter/route_filter_plugin.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2003-2009 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 "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.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 sample in the
+ * input buffer
+ */
+ size_t input_sample_size;
+
+ /**
+ * The size, in bytes, of each multichannel sample in the
+ * output buffer
+ */
+ size_t output_sample_size;
+
+ /**
+ * The output buffer used last time around, can be reused if the size doesn't differ.
+ */
+ void *output_buffer;
+
+ /**
+ * The size in bytes of the currently allocated output buffer
+ */
+ size_t output_buffer_size;
+
+};
+
+/**
+ * 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
+ */
+static void
+route_filter_parse(const struct config_param *param, struct route_filter *filter) {
+
+ /* 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_error("Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ continue;
+ }
+
+ 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);
+ }
+
+ // 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_error("Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ continue;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ filter->sources[dest] = source;
+
+ g_strfreev(sd);
+ }
+
+ g_strfreev(tokens);
+}
+
+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);
+
+ 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,
+ const 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_sample_size =
+ audio_format_sample_size(&filter->input_format) *
+ filter->input_format.channels;
+
+ // 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_sample_size =
+ audio_format_sample_size(&filter->output_format) *
+ filter->output_format.channels;
+
+ // This buffer grows as needed
+ filter->output_buffer_size = filter->output_sample_size;
+ filter->output_buffer = g_malloc0(filter->output_buffer_size);
+
+ return &filter->output_format;
+}
+
+static void
+route_filter_close(struct filter *_filter)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ filter->output_buffer_size = 0;
+ g_free(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_samples = src_size / filter->input_sample_size;
+
+ size_t bytes_per_sample_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 sample
+ const uint8_t *base_source = src;
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled sample, in the output
+ uint8_t *chan_destination;
+
+ // Grow our reusable buffer, if needed
+ *dest_size_r = number_of_samples * filter->output_sample_size;
+ if (*dest_size_r > filter->output_buffer_size) {
+ filter->output_buffer_size = *dest_size_r;
+
+ filter->output_buffer =
+ g_realloc(filter->output_buffer,
+ filter->output_buffer_size);
+ }
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled sample, in the output
+ chan_destination = filter->output_buffer;
+
+ // Perform our copy operations, with N input channels and M output channels
+ for (unsigned int s=0; s<number_of_samples; ++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_sample_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_sample_per_channel);
+ g_memmove(chan_destination,
+ data,
+ bytes_per_sample_per_channel);
+ }
+ // Move on to the next output channel
+ chan_destination += bytes_per_sample_per_channel;
+ }
+
+
+ // Go on to the next N input samples
+ base_source += filter->input_sample_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return filter->output_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_registry.c b/src/filter_registry.c
index a6aaa2bfb..2685b3249 100644
--- a/src/filter_registry.c
+++ b/src/filter_registry.c
@@ -27,6 +27,7 @@
const struct filter_plugin *const filter_plugins[] = {
&null_filter_plugin,
&chain_filter_plugin,
+ &route_filter_plugin,
&volume_filter_plugin,
NULL,
};
diff --git a/src/filter_registry.h b/src/filter_registry.h
index 7eb7f7038..aab410581 100644
--- a/src/filter_registry.h
+++ b/src/filter_registry.h
@@ -29,6 +29,7 @@
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 volume_filter_plugin;
const struct filter_plugin *