/* * Copyright (C) 2003-2013 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 "ConfigQuark.hxx" #include "audio_format.h" #include "audio_check.h" #include "FilterPlugin.hxx" #include "FilterInternal.hxx" #include "FilterRegistry.hxx" #include "pcm_buffer.h" #include #include #include 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; cb 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 = (signed char *) g_malloc(filter->min_output_channels * sizeof(signed char)); for (int i=0; imin_output_channels; ++i) filter->sources[i] = -1; // Run through the spec again, and save the // actual mapping output <- input for (int c=0; cb 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 = (const uint8_t *)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 = (uint8_t *) 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; smin_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 = { "route", route_filter_init, route_filter_finish, route_filter_open, route_filter_close, route_filter_filter, };