MultipleOutputs.cxx 9.22 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * 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.
 */

#include "MultipleOutputs.hxx"
21
#include "Filtered.hxx"
22
#include "Defaults.hxx"
23
#include "Domain.hxx"
24 25
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
26
#include "filter/Factory.hxx"
27
#include "config/Block.hxx"
28
#include "config/Data.hxx"
29
#include "config/Option.hxx"
30
#include "util/RuntimeError.hxx"
31

32 33
#include <stdexcept>

34 35 36
#include <assert.h>
#include <string.h>

37
MultipleOutputs::MultipleOutputs(MixerListener &_mixer_listener) noexcept
38
	:mixer_listener(_mixer_listener)
39 40 41
{
}

42
MultipleOutputs::~MultipleOutputs() noexcept
43
{
44
	/* parallel destruction */
45
	for (auto *i : outputs)
46
		i->BeginDestroy();
47 48
	for (auto *i : outputs)
		delete i;
49 50
}

51
static std::unique_ptr<FilteredAudioOutput>
52 53 54
LoadOutput(EventLoop &event_loop,
	   const ReplayGainConfig &replay_gain_config,
	   MixerListener &mixer_listener,
55
	   const ConfigBlock &block,
56
	   const AudioOutputDefaults &defaults,
57
	   FilterFactory *filter_factory)
58
try {
59
	return audio_output_new(event_loop, replay_gain_config, block,
60
				defaults,
61
				filter_factory,
62
				mixer_listener);
63
} catch (...) {
64
	if (block.line > 0)
65 66
		std::throw_with_nested(FormatRuntimeError("Failed to configure output in line %i",
							  block.line));
67
	else
68
		throw;
69 70
}

71 72 73 74
static AudioOutputControl *
LoadOutputControl(EventLoop &event_loop,
		  const ReplayGainConfig &replay_gain_config,
		  MixerListener &mixer_listener,
75
		  AudioOutputClient &client, const ConfigBlock &block,
76
		  const AudioOutputDefaults &defaults,
77
		  FilterFactory *filter_factory)
78
{
79 80
	auto output = LoadOutput(event_loop, replay_gain_config,
				 mixer_listener,
81
				 block, defaults, filter_factory);
82
	auto *control = new AudioOutputControl(std::move(output), client);
83 84 85 86 87 88 89 90 91 92

	try {
		control->Configure(block);
	} catch (...) {
		control->BeginDestroy();
		delete control;
		throw;
	}

	return control;
93 94
}

95
void
96
MultipleOutputs::Configure(EventLoop &event_loop,
97
			   const ConfigData &config,
98
			   const ReplayGainConfig &replay_gain_config,
99
			   AudioOutputClient &client)
100
{
101
	const AudioOutputDefaults defaults(config);
102 103
	FilterFactory filter_factory(config);

104 105
	for (const auto &block : config.GetBlockList(ConfigBlockOption::AUDIO_OUTPUT)) {
		block.SetUsed();
106 107 108
		auto *output = LoadOutputControl(event_loop,
						 replay_gain_config,
						 mixer_listener,
109 110
						 client, block, defaults,
						 &filter_factory);
111
		if (FindByName(output->GetName()) != nullptr)
112
			throw FormatRuntimeError("output devices with identical "
113
						 "names: %s", output->GetName());
114

115
		outputs.push_back(output);
116 117 118 119
	}

	if (outputs.empty()) {
		/* auto-detect device */
120
		const ConfigBlock empty;
121 122 123
		auto *output = LoadOutputControl(event_loop,
						 replay_gain_config,
						 mixer_listener,
124
						 client, empty, defaults,
125
						 nullptr);
126
		outputs.push_back(output);
127 128 129
	}
}

130 131 132 133 134
void
MultipleOutputs::AddNullOutput(EventLoop &event_loop,
			       const ReplayGainConfig &replay_gain_config,
			       AudioOutputClient &client)
{
135 136
	const AudioOutputDefaults defaults;

137 138 139
	ConfigBlock block;
	block.AddBlockParam("type", "null");

140 141
	auto *output = LoadOutputControl(event_loop, replay_gain_config,
					 mixer_listener,
142
					 client, block, defaults, nullptr);
143
	outputs.push_back(output);
144 145
}

146
AudioOutputControl *
Max Kellermann's avatar
Max Kellermann committed
147
MultipleOutputs::FindByName(const char *name) noexcept
148
{
149
	for (auto *i : outputs)
150
		if (strcmp(i->GetName(), name) == 0)
151 152 153 154 155 156 157 158
			return i;

	return nullptr;
}

void
MultipleOutputs::EnableDisable()
{
159 160
	/* parallel execution */

161
	for (auto *ao : outputs) {
162
		const std::lock_guard<Mutex> lock(ao->mutex);
163 164 165
		ao->EnableDisableAsync();
	}

166
	WaitAll();
167 168
}

169 170
void
MultipleOutputs::WaitAll() noexcept
171
{
172
	for (auto *ao : outputs) {
173
		const std::lock_guard<Mutex> protect(ao->mutex);
174
		ao->WaitForCommand();
175 176 177 178
	}
}

void
179
MultipleOutputs::AllowPlay() noexcept
180
{
181
	for (auto *ao : outputs)
182
		ao->LockAllowPlay();
183 184 185
}

bool
186
MultipleOutputs::Update(bool force) noexcept
187 188 189 190 191 192
{
	bool ret = false;

	if (!input_audio_format.IsDefined())
		return false;

193
	for (auto *ao : outputs)
194
		ret = ao->LockUpdate(input_audio_format, *pipe, force)
195 196 197 198 199 200
			|| ret;

	return ret;
}

void
201
MultipleOutputs::SetReplayGainMode(ReplayGainMode mode) noexcept
202
{
203
	for (auto *ao : outputs)
204
		ao->SetReplayGainMode(mode);
205 206
}

207
void
208
MultipleOutputs::Play(MusicChunkPtr chunk)
209 210 211 212 213
{
	assert(pipe != nullptr);
	assert(chunk != nullptr);
	assert(chunk->CheckFormat(input_audio_format));

214
	if (!Update(false))
215
		/* TODO: obtain real error */
216
		throw std::runtime_error("Failed to open audio output");
217

218
	pipe->Push(std::move(chunk));
219

220
	for (auto *ao : outputs)
221
		ao->LockPlay();
222 223
}

224
void
225
MultipleOutputs::Open(const AudioFormat audio_format)
226 227 228 229 230 231 232 233
{
	bool ret = false, enabled = false;

	/* the audio format must be the same as existing chunks in the
	   pipe */
	assert(pipe == nullptr || pipe->CheckFormat(audio_format));

	if (pipe == nullptr)
234
		pipe = std::make_unique<MusicPipe>();
235 236 237 238 239 240 241 242
	else
		/* if the pipe hasn't been cleared, the the audio
		   format must not have changed */
		assert(pipe->IsEmpty() || audio_format == input_audio_format);

	input_audio_format = audio_format;

	EnableDisable();
243
	Update(true);
244

245 246
	std::exception_ptr first_error;

247
	for (auto *ao : outputs) {
248
		const std::lock_guard<Mutex> lock(ao->mutex);
249

250
		if (ao->IsEnabled())
251 252
			enabled = true;

253
		if (ao->IsOpen())
254
			ret = true;
255 256
		else if (!first_error)
			first_error = ao->GetLastError();
257 258
	}

259
	if (!enabled) {
260 261
		/* close all devices if there was an error */
		Close();
262 263 264 265
		throw std::runtime_error("All audio outputs are disabled");
	} else if (!ret) {
		/* close all devices if there was an error */
		Close();
266 267 268 269 270 271

		if (first_error)
			/* we have details, so throw that */
			std::rethrow_exception(first_error);
		else
			throw std::runtime_error("Failed to open audio output");
272
	}
273 274 275
}

bool
276
MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const noexcept
277
{
278
	for (auto *ao : outputs)
279
		if (!ao->LockIsChunkConsumed(*chunk))
280 281 282 283 284 285
			return false;

	return true;
}

inline void
286
MultipleOutputs::ClearTailChunk(const MusicChunk *chunk,
287
				bool *locked) noexcept
288 289 290 291 292
{
	assert(chunk->next == nullptr);
	assert(pipe->Contains(chunk));

	for (unsigned i = 0, n = outputs.size(); i != n; ++i) {
293
		auto *ao = outputs[i];
294 295 296 297

		/* this mutex will be unlocked by the caller when it's
		   ready */
		ao->mutex.lock();
298
		locked[i] = ao->IsOpen();
299 300 301 302 303 304

		if (!locked[i]) {
			ao->mutex.unlock();
			continue;
		}

305
		ao->ClearTailChunk(*chunk);
306 307 308 309
	}
}

unsigned
310
MultipleOutputs::CheckPipe() noexcept
311
{
312
	const MusicChunk *chunk;
313 314 315 316 317 318 319 320 321 322 323 324 325
	bool is_tail;
	bool locked[outputs.size()];

	assert(pipe != nullptr);

	while ((chunk = pipe->Peek()) != nullptr) {
		assert(!pipe->IsEmpty());

		if (!IsChunkConsumed(chunk))
			/* at least one output is not finished playing
			   this chunk */
			return pipe->GetSize();

326
		if (chunk->length > 0 && !chunk->time.IsNegative())
327 328
			/* only update elapsed_time if the chunk
			   provides a defined value */
329
			elapsed_time = chunk->time;
330 331 332 333 334 335 336 337

		is_tail = chunk->next == nullptr;
		if (is_tail)
			/* this is the tail of the pipe - clear the
			   chunk reference in all outputs */
			ClearTailChunk(chunk, locked);

		/* remove the chunk from the pipe */
338 339
		const auto shifted = pipe->Shift();
		assert(shifted.get() == chunk);
340 341 342 343 344 345 346 347

		if (is_tail)
			/* unlock all audio outputs which were locked
			   by clear_tail_chunk() */
			for (unsigned i = 0, n = outputs.size(); i != n; ++i)
				if (locked[i])
					outputs[i]->mutex.unlock();

348 349
		/* chunk is automatically returned to the buffer by
		   ~MusicChunkPtr() */
350 351 352 353 354 355
	}

	return 0;
}

void
356
MultipleOutputs::Pause() noexcept
357
{
358
	Update(false);
359

360
	for (auto *ao : outputs)
361
		ao->LockPauseAsync();
362 363 364 365 366

	WaitAll();
}

void
367
MultipleOutputs::Drain() noexcept
368
{
369
	for (auto *ao : outputs)
370
		ao->LockDrainAsync();
371 372 373 374 375

	WaitAll();
}

void
376
MultipleOutputs::Cancel() noexcept
377 378 379
{
	/* send the cancel() command to all audio outputs */

380
	for (auto *ao : outputs)
381
		ao->LockCancelAsync();
382 383 384 385 386 387

	WaitAll();

	/* clear the music pipe and return all chunks to the buffer */

	if (pipe != nullptr)
388
		pipe->Clear();
389 390 391 392 393 394 395 396

	/* the audio outputs are now waiting for a signal, to
	   synchronize the cleared music pipe */

	AllowPlay();

	/* invalidate elapsed_time */

397
	elapsed_time = SignedSongTime::Negative();
398 399 400
}

void
401
MultipleOutputs::Close() noexcept
402
{
403
	for (auto *ao : outputs)
404
		ao->LockCloseWait();
405

406
	pipe.reset();
407 408 409

	input_audio_format.Clear();

410
	elapsed_time = SignedSongTime::Negative();
411 412 413
}

void
414
MultipleOutputs::Release() noexcept
415
{
416
	for (auto *ao : outputs)
417
		ao->LockRelease();
418

419
	pipe.reset();
420 421 422

	input_audio_format.Clear();

423
	elapsed_time = SignedSongTime::Negative();
424 425 426
}

void
427
MultipleOutputs::SongBorder() noexcept
428 429 430
{
	/* clear the elapsed_time pointer at the beginning of a new
	   song */
431
	elapsed_time = SignedSongTime::zero();
432
}