diff options
Diffstat (limited to '')
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | src/filter/route_filter_plugin.c | 345 | ||||
-rw-r--r-- | src/filter_registry.c | 1 | ||||
-rw-r--r-- | src/filter_registry.h | 1 |
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 * |