RouteFilterPlugin.cxx 8.28 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
 * 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"
43
#include "config/ConfigError.hxx"
44
#include "config/Block.hxx"
45
#include "AudioFormat.hxx"
46
#include "filter/FilterPlugin.hxx"
47 48
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
49
#include "filter/FilterRegistry.hxx"
50
#include "pcm/PcmBuffer.hxx"
51
#include "pcm/Silence.hxx"
52
#include "util/StringStrip.hxx"
53
#include "util/RuntimeError.hxx"
54
#include "util/ConstBuffer.hxx"
55
#include "util/WritableBuffer.hxx"
56

57
#include <algorithm>
58
#include <array>
59

60
#include <string.h>
61
#include <stdint.h>
62
#include <stdlib.h>
63

64
class RouteFilter final : public Filter {
65 66 67 68 69 70
	/**
	 * 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"
	 */
71
	const std::array<int8_t, MAX_CHANNELS> sources;
72 73 74 75

	/**
	 * The actual input format of our signal, once opened
	 */
76
	const AudioFormat input_format;
77 78

	/**
79
	 * The size, in bytes, of each multichannel frame in the
80 81
	 * input buffer
	 */
82
	const size_t input_frame_size;
83 84

	/**
85
	 * The size, in bytes, of each multichannel frame in the
86 87
	 * output buffer
	 */
88
	size_t output_frame_size;
89 90 91 92

	/**
	 * The output buffer used last time around, can be reused if the size doesn't differ.
	 */
93
	PcmBuffer output_buffer;
94

95 96 97 98 99
public:
	RouteFilter(const AudioFormat &audio_format, unsigned out_channels,
		    const std::array<int8_t, MAX_CHANNELS> &_sources);

	/* virtual methods from class Filter */
100
	ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
};

class PreparedRouteFilter final : public PreparedFilter {
	/**
	 * 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"
	 */
	std::array<int8_t, MAX_CHANNELS> sources;

126 127 128 129 130 131
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.
132
	 * @param block the configuration block to read
133 134
	 * @param filter a route_filter whose min_channels and sources[] to set
	 */
135
	PreparedRouteFilter(const ConfigBlock &block);
136

137
	/* virtual methods from class PreparedFilter */
138
	std::unique_ptr<Filter> Open(AudioFormat &af) override;
139 140
};

141
PreparedRouteFilter::PreparedRouteFilter(const ConfigBlock &block)
142
{
143 144 145
	/* 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
146
	 * dynamic realloc() instead of one count run and one malloc().
147 148
	 */

149
	sources.fill(-1);
150

151 152
	min_input_channels = 0;
	min_output_channels = 0;
153

154
	// A cowardly default, just passthrough stereo
155
	const char *routes = block.GetBlockValue("routes", "0>0, 1>1");
156
	while (true) {
157
		routes = StripLeft(routes);
158 159 160

		char *endptr;
		const unsigned source = strtoul(routes, &endptr, 10);
161
		endptr = StripLeft(endptr);
162 163 164 165 166 167
		if (endptr == routes || *endptr != '>')
			throw std::runtime_error("Malformed 'routes' specification");

		if (source >= MAX_CHANNELS)
			throw FormatRuntimeError("Invalid source channel number: %u",
						 source);
168

169 170
		if (source >= min_input_channels)
			min_input_channels = source + 1;
171

172
		routes = StripLeft(endptr + 1);
173

174
		unsigned dest = strtoul(routes, &endptr, 10);
175
		endptr = StripLeft(endptr);
176 177 178 179 180 181
		if (endptr == routes)
			throw std::runtime_error("Malformed 'routes' specification");

		if (dest >= MAX_CHANNELS)
			throw FormatRuntimeError("Invalid destination channel number: %u",
						 dest);
182

183 184
		if (dest >= min_output_channels)
			min_output_channels = dest + 1;
185

186
		sources[dest] = source;
187

188 189 190 191 192
		routes = endptr;

		if (*routes == 0)
			break;

193 194
		if (*routes != ',')
			throw std::runtime_error("Malformed 'routes' specification");
195

196 197
		++routes;
	}
198 199
}

200
static std::unique_ptr<PreparedFilter>
201
route_filter_init(const ConfigBlock &block)
202
{
203
	return std::make_unique<PreparedRouteFilter>(block);
204 205
}

206 207 208 209 210
RouteFilter::RouteFilter(const AudioFormat &audio_format,
			 unsigned out_channels,
			 const std::array<int8_t, MAX_CHANNELS> &_sources)
	:Filter(audio_format), sources(_sources), input_format(audio_format),
	 input_frame_size(input_format.GetFrameSize())
211 212 213
{
	// Decide on an output format which has enough channels,
	// and is otherwise identical
214
	out_audio_format.channels = out_channels;
215 216

	// Precalculate this simple value, to speed up allocation later
217
	output_frame_size = out_audio_format.GetFrameSize();
218 219
}

220
std::unique_ptr<Filter>
221
PreparedRouteFilter::Open(AudioFormat &audio_format)
222
{
223 224
	return std::make_unique<RouteFilter>(audio_format, min_output_channels,
					     sources);
225 226
}

227
ConstBuffer<void>
228
RouteFilter::FilterPCM(ConstBuffer<void> src)
229
{
230
	size_t number_of_frames = src.size / input_frame_size;
231

232
	const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
233

234
	// A moving pointer that always refers to channel 0 in the input, at the currently handled frame
235
	const uint8_t *base_source = (const uint8_t *)src.data;
236

237
	// Grow our reusable buffer, if needed, and set the moving pointer
238 239
	const size_t result_size = number_of_frames * output_frame_size;
	void *const result = output_buffer.Get(result_size);
240 241 242

	// 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;
243 244

	// Perform our copy operations, with N input channels and M output channels
245
	for (unsigned int s=0; s<number_of_frames; ++s) {
246 247

		// Need to perform one copy per output channel
248
		for (unsigned c = 0; c < out_audio_format.channels; ++c) {
249 250
			if (sources[c] == -1 ||
			    (unsigned)sources[c] >= input_format.channels) {
251 252
				// No source for this destination output,
				// give it zeroes as input
253 254
				PcmSilence({chan_destination, bytes_per_frame_per_channel},
					   input_format.format);
255 256 257 258
			} else {
				// Get the data from channel sources[c]
				// and copy it to the output
				const uint8_t *data = base_source +
259
					(sources[c] * bytes_per_frame_per_channel);
260
				memcpy(chan_destination,
261
					  data,
262
					  bytes_per_frame_per_channel);
263 264
			}
			// Move on to the next output channel
265
			chan_destination += bytes_per_frame_per_channel;
266 267 268 269
		}


		// Go on to the next N input samples
270
		base_source += input_frame_size;
271 272 273
	}

	// Here it is, ladies and gentlemen! Rerouted data!
274
	return { result, result_size };
275 276
}

277
const FilterPlugin route_filter_plugin = {
Max Kellermann's avatar
Max Kellermann committed
278 279
	"route",
	route_filter_init,
280
};