MultipleOutputs.cxx 9.49 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
 * 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 "config.h"
#include "MultipleOutputs.hxx"
22
#include "Filtered.hxx"
23
#include "Domain.hxx"
24 25 26
#include "MusicBuffer.hxx"
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
27
#include "config/Block.hxx"
28 29 30
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "notify.hxx"
31
#include "util/RuntimeError.hxx"
32

33 34
#include <stdexcept>

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

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

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

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

68 69 70 71 72 73 74 75
static AudioOutputControl *
LoadOutputControl(EventLoop &event_loop,
		  const ReplayGainConfig &replay_gain_config,
		  MixerListener &mixer_listener,
		  AudioOutputClient &client, const ConfigBlock &block)
{
	auto *output = LoadOutput(event_loop, replay_gain_config,
				  mixer_listener,
76 77
				  block);
	auto *control = new AudioOutputControl(output, client);
78 79 80 81 82 83 84 85 86 87 88

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

	return control;
89 90
}

91
void
92 93
MultipleOutputs::Configure(EventLoop &event_loop,
			   const ReplayGainConfig &replay_gain_config,
94
			   AudioOutputClient &client)
95
{
96
	for (const auto *param = config_get_block(ConfigBlockOption::AUDIO_OUTPUT);
97
	     param != nullptr; param = param->next) {
98 99 100 101
		auto *output = LoadOutputControl(event_loop,
						 replay_gain_config,
						 mixer_listener,
						 client, *param);
102
		if (FindByName(output->GetName()) != nullptr)
103
			throw FormatRuntimeError("output devices with identical "
104
						 "names: %s", output->GetName());
105

106
		outputs.push_back(output);
107 108 109 110
	}

	if (outputs.empty()) {
		/* auto-detect device */
111
		const ConfigBlock empty;
112 113 114 115 116
		auto *output = LoadOutputControl(event_loop,
						 replay_gain_config,
						 mixer_listener,
						 client, empty);
		outputs.push_back(output);
117 118 119
	}
}

120 121 122 123 124 125 126 127
void
MultipleOutputs::AddNullOutput(EventLoop &event_loop,
			       const ReplayGainConfig &replay_gain_config,
			       AudioOutputClient &client)
{
	ConfigBlock block;
	block.AddBlockParam("type", "null");

128 129 130 131
	auto *output = LoadOutputControl(event_loop, replay_gain_config,
					 mixer_listener,
					 client, block);
	outputs.push_back(output);
132 133
}

134
AudioOutputControl *
Max Kellermann's avatar
Max Kellermann committed
135
MultipleOutputs::FindByName(const char *name) noexcept
136
{
137
	for (auto *i : outputs)
138
		if (strcmp(i->GetName(), name) == 0)
139 140 141 142 143 144 145 146
			return i;

	return nullptr;
}

void
MultipleOutputs::EnableDisable()
{
147 148
	/* parallel execution */

149
	for (auto *ao : outputs) {
150
		const std::lock_guard<Mutex> lock(ao->mutex);
151 152 153
		ao->EnableDisableAsync();
	}

154
	for (auto *ao : outputs) {
155
		const std::lock_guard<Mutex> lock(ao->mutex);
156
		ao->WaitForCommand();
157 158 159 160
	}
}

bool
161
MultipleOutputs::AllFinished() const noexcept
162
{
163
	for (auto *ao : outputs) {
164
		const std::lock_guard<Mutex> protect(ao->mutex);
165
		if (ao->IsBusy())
166 167 168 169 170 171 172
			return false;
	}

	return true;
}

void
173
MultipleOutputs::WaitAll() noexcept
174 175 176 177 178 179
{
	while (!AllFinished())
		audio_output_client_notify.Wait();
}

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

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

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

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

	return ret;
}

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

208 209
void
MultipleOutputs::Play(MusicChunk *chunk)
210 211 212 213 214 215
{
	assert(buffer != nullptr);
	assert(pipe != nullptr);
	assert(chunk != nullptr);
	assert(chunk->CheckFormat(input_audio_format));

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

	pipe->Push(chunk);

222
	for (auto *ao : outputs)
223
		ao->LockPlay();
224 225
}

226
void
227
MultipleOutputs::Open(const AudioFormat audio_format,
228
		      MusicBuffer &_buffer)
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
{
	bool ret = false, enabled = false;

	assert(buffer == nullptr || buffer == &_buffer);
	assert((pipe == nullptr) == (buffer == nullptr));

	buffer = &_buffer;

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

	if (pipe == nullptr)
		pipe = new MusicPipe();
	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();
251
	Update(true);
252

253 254
	std::exception_ptr first_error;

255
	for (auto *ao : outputs) {
256
		const std::lock_guard<Mutex> lock(ao->mutex);
257

258
		if (ao->IsEnabled())
259 260
			enabled = true;

261
		if (ao->IsOpen())
262
			ret = true;
263 264
		else if (!first_error)
			first_error = ao->GetLastError();
265 266
	}

267
	if (!enabled) {
268 269
		/* close all devices if there was an error */
		Close();
270 271 272 273
		throw std::runtime_error("All audio outputs are disabled");
	} else if (!ret) {
		/* close all devices if there was an error */
		Close();
274 275 276 277 278 279

		if (first_error)
			/* we have details, so throw that */
			std::rethrow_exception(first_error);
		else
			throw std::runtime_error("Failed to open audio output");
280
	}
281 282 283
}

bool
284
MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const noexcept
285
{
286
	for (auto *ao : outputs)
287
		if (!ao->LockIsChunkConsumed(*chunk))
288 289 290 291 292 293
			return false;

	return true;
}

inline void
294
MultipleOutputs::ClearTailChunk(const MusicChunk *chunk,
295
				bool *locked) noexcept
296 297 298 299 300
{
	assert(chunk->next == nullptr);
	assert(pipe->Contains(chunk));

	for (unsigned i = 0, n = outputs.size(); i != n; ++i) {
301
		auto *ao = outputs[i];
302 303 304 305

		/* this mutex will be unlocked by the caller when it's
		   ready */
		ao->mutex.lock();
306
		locked[i] = ao->IsOpen();
307 308 309 310 311 312

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

313
		ao->ClearTailChunk(*chunk);
314 315 316 317
	}
}

unsigned
318
MultipleOutputs::CheckPipe() noexcept
319
{
320
	const MusicChunk *chunk;
321
	bool is_tail;
322
	MusicChunk *shifted;
323 324 325 326 327 328 329 330 331 332 333 334 335
	bool locked[outputs.size()];

	assert(buffer != nullptr);
	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();

336
		if (chunk->length > 0 && !chunk->time.IsNegative())
337 338
			/* only update elapsed_time if the chunk
			   provides a defined value */
339
			elapsed_time = chunk->time;
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365

		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 */
		shifted = pipe->Shift();
		assert(shifted == chunk);

		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();

		/* return the chunk to the buffer */
		buffer->Return(shifted);
	}

	return 0;
}

void
366
MultipleOutputs::Pause() noexcept
367
{
368
	Update(false);
369

370
	for (auto *ao : outputs)
371
		ao->LockPauseAsync();
372 373 374 375 376

	WaitAll();
}

void
377
MultipleOutputs::Drain() noexcept
378
{
379
	for (auto *ao : outputs)
380
		ao->LockDrainAsync();
381 382 383 384 385

	WaitAll();
}

void
386
MultipleOutputs::Cancel() noexcept
387 388 389
{
	/* send the cancel() command to all audio outputs */

390
	for (auto *ao : outputs)
391
		ao->LockCancelAsync();
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406

	WaitAll();

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

	if (pipe != nullptr)
		pipe->Clear(*buffer);

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

	AllowPlay();

	/* invalidate elapsed_time */

407
	elapsed_time = SignedSongTime::Negative();
408 409 410
}

void
411
MultipleOutputs::Close() noexcept
412
{
413
	for (auto *ao : outputs)
414
		ao->LockCloseWait();
415 416 417 418 419 420 421 422 423 424 425 426 427

	if (pipe != nullptr) {
		assert(buffer != nullptr);

		pipe->Clear(*buffer);
		delete pipe;
		pipe = nullptr;
	}

	buffer = nullptr;

	input_audio_format.Clear();

428
	elapsed_time = SignedSongTime::Negative();
429 430 431
}

void
432
MultipleOutputs::Release() noexcept
433
{
434
	for (auto *ao : outputs)
435
		ao->LockRelease();
436 437 438 439 440 441 442 443 444 445 446 447 448

	if (pipe != nullptr) {
		assert(buffer != nullptr);

		pipe->Clear(*buffer);
		delete pipe;
		pipe = nullptr;
	}

	buffer = nullptr;

	input_audio_format.Clear();

449
	elapsed_time = SignedSongTime::Negative();
450 451 452
}

void
453
MultipleOutputs::SongBorder() noexcept
454 455 456
{
	/* clear the elapsed_time pointer at the beginning of a new
	   song */
457
	elapsed_time = SignedSongTime::zero();
458
}