aboutsummaryrefslogtreecommitdiffstats
path: root/src/filter/plugins/RouteFilterPlugin.cxx
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2014-01-24 16:31:52 +0100
committerMax Kellermann <max@duempel.org>2014-01-24 16:43:57 +0100
commit7c52a1c04bccac68f4220c8bf3d3a59c16ed58db (patch)
tree6c1f8b99225343974584817b3278958599e2e12a /src/filter/plugins/RouteFilterPlugin.cxx
parent9d34fc394ce30a28ec0e43f2ad7172b8de8b3be6 (diff)
downloadmpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.tar.gz
mpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.tar.xz
mpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.zip
Filter*: move to filter/
Diffstat (limited to 'src/filter/plugins/RouteFilterPlugin.cxx')
-rw-r--r--src/filter/plugins/RouteFilterPlugin.cxx296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx
new file mode 100644
index 000000000..38c4ec43b
--- /dev/null
+++ b/src/filter/plugins/RouteFilterPlugin.cxx
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2003-2014 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 "config/ConfigError.hxx"
+#include "config/ConfigData.hxx"
+#include "AudioFormat.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+class RouteFilter final : public Filter {
+ /**
+ * The minimum number of channels we need for output
+ * to be able to perform all the copies the user has specified
+ */
+ unsigned 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 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"
+ */
+ int8_t sources[MAX_CHANNELS];
+
+ /**
+ * The actual input format of our signal, once opened
+ */
+ AudioFormat input_format;
+
+ /**
+ * The decided upon output format, once opened
+ */
+ AudioFormat 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.
+ */
+ PcmBuffer output_buffer;
+
+public:
+ /**
+ * 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
+ */
+ bool Configure(const config_param &param, Error &error);
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+bool
+RouteFilter::Configure(const config_param &param, Error &error) {
+
+ /* 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 realloc() instead of one count run and one malloc().
+ */
+
+ std::fill_n(sources, MAX_CHANNELS, -1);
+
+ min_input_channels = 0;
+ min_output_channels = 0;
+
+ // A cowardly default, just passthrough stereo
+ const char *routes = param.GetBlockValue("routes", "0>0, 1>1");
+ while (true) {
+ routes = strchug_fast(routes);
+
+ char *endptr;
+ const unsigned source = strtoul(routes, &endptr, 10);
+ endptr = strchug_fast(endptr);
+ if (endptr == routes || *endptr != '>') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (source >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid source channel number: %u",
+ source);
+ return false;
+ }
+
+ if (source >= min_input_channels)
+ min_input_channels = source + 1;
+
+ routes = strchug_fast(endptr + 1);
+
+ unsigned dest = strtoul(routes, &endptr, 10);
+ endptr = strchug_fast(endptr);
+ if (endptr == routes) {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (dest >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid destination channel number: %u",
+ dest);
+ return false;
+ }
+
+ if (dest >= min_output_channels)
+ min_output_channels = dest + 1;
+
+ sources[dest] = source;
+
+ routes = endptr;
+
+ if (*routes == 0)
+ break;
+
+ if (*routes != ',') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ ++routes;
+ }
+
+ return true;
+}
+
+static Filter *
+route_filter_init(const config_param &param, Error &error)
+{
+ RouteFilter *filter = new RouteFilter();
+ if (!filter->Configure(param, error)) {
+ delete filter;
+ return nullptr;
+ }
+
+ return filter;
+}
+
+AudioFormat
+RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ // Copy the input format for later reference
+ input_format = audio_format;
+ input_frame_size = input_format.GetFrameSize();
+
+ // Decide on an output format which has enough channels,
+ // and is otherwise identical
+ output_format = audio_format;
+ output_format.channels = min_output_channels;
+
+ // Precalculate this simple value, to speed up allocation later
+ output_frame_size = output_format.GetFrameSize();
+
+ return output_format;
+}
+
+void
+RouteFilter::Close()
+{
+ output_buffer.Clear();
+}
+
+const void *
+RouteFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ size_t number_of_frames = src_size / input_frame_size;
+
+ const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
+
+ // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
+ const uint8_t *base_source = (const uint8_t *)src;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ *dest_size_r = number_of_frames * output_frame_size;
+ void *const result = output_buffer.Get(*dest_size_r);
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination = (uint8_t *)result;
+
+ // 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<min_output_channels; ++c) {
+ if (sources[c] == -1 ||
+ (unsigned)sources[c] >= 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 +
+ (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 += input_frame_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return result;
+}
+
+const struct filter_plugin route_filter_plugin = {
+ "route",
+ route_filter_init,
+};