Thread.cxx 10.6 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20
#include "Control.hxx"
21
#include "Error.hxx"
22
#include "Filtered.hxx"
23
#include "Client.hxx"
24
#include "Domain.hxx"
25
#include "thread/Util.hxx"
26
#include "thread/Slack.hxx"
27
#include "thread/Name.hxx"
28
#include "util/StringBuffer.hxx"
29
#include "util/ScopeExit.hxx"
30
#include "util/RuntimeError.hxx"
31
#include "Log.hxx"
32

33 34
#include <cassert>

Max Kellermann's avatar
Max Kellermann committed
35
#include <string.h>
36

37
void
38
AudioOutputControl::CommandFinished() noexcept
39
{
40 41
	assert(command != Command::NONE);
	command = Command::NONE;
42

43
	client_cond.notify_one();
44 45
}

46
inline void
47
AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
48
{
49
	assert(in_audio_format.IsValid());
50

51
	const auto cf = in_audio_format.WithMask(output->config_audio_format);
52

53
	if (open && cf != output->filter_audio_format)
54 55
		/* if the filter's output format changes, the output
		   must be reopened as well */
56
		InternalCloseOutput(true);
57

58
	output->filter_audio_format = cf;
59

60
	if (!open) {
61
		{
62
			const ScopeUnlock unlock(mutex);
63
			output->OpenOutputAndConvert(output->filter_audio_format);
64
		}
65 66

		open = true;
67
	} else if (in_audio_format != output->out_audio_format) {
68 69 70 71
		/* reconfigure the final ConvertFilter for its new
		   input AudioFormat */

		try {
72
			output->ConfigureConvertFilter();
73
		} catch (...) {
74
			open = false;
75 76 77

			{
				const ScopeUnlock unlock(mutex);
78
				output->CloseOutput(false);
79 80
			}

81
			throw;
82
		}
83
	}
84 85 86 87 88

	{
		const ScopeUnlock unlock(mutex);
		output->OpenSoftwareMixer();
	}
89 90
}

91 92 93
inline bool
AudioOutputControl::InternalEnable() noexcept
{
94 95 96 97
	if (really_enabled)
		/* already enabled */
		return true;

98 99 100
	last_error = nullptr;

	try {
101 102 103 104 105
		{
			const ScopeUnlock unlock(mutex);
			output->Enable();
		}

106
		really_enabled = true;
107
		return true;
108 109
	} catch (...) {
		LogError(std::current_exception());
110
		Failure(std::current_exception());
111 112 113 114
		return false;
	}
}

115 116 117
inline void
AudioOutputControl::InternalDisable() noexcept
{
118 119 120
	if (!really_enabled)
		return;

121
	InternalCheckClose(false);
122

123
	really_enabled = false;
124 125

	const ScopeUnlock unlock(mutex);
126 127 128
	output->Disable();
}

129
inline void
130
AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
131 132
				 const MusicPipe &pipe) noexcept
{
133 134 135 136
	/* enable the device (just in case the last enable has failed) */
	if (!InternalEnable())
		return;

137 138
	last_error = nullptr;
	fail_timer.Reset();
139
	caught_interrupted = false;
140
	skip_delay = true;
141

142 143
	AudioFormat f;

144
	try {
145 146
		try {
			f = source.Open(in_audio_format, pipe,
147 148
					output->prepared_replay_gain_filter.get(),
					output->prepared_other_replay_gain_filter.get(),
149
					*output->prepared_filter);
150
		} catch (...) {
151 152
			std::throw_with_nested(FormatRuntimeError("Failed to open filter for %s",
								  GetLogName()));
153 154 155
		}

		try {
156
			InternalOpen2(f);
157
		} catch (...) {
158
			source.Close();
159 160
			throw;
		}
161 162
	} catch (...) {
		LogError(std::current_exception());
163
		Failure(std::current_exception());
164
		return;
165
	}
166 167 168 169 170 171

	if (f != in_audio_format || f != output->out_audio_format)
		FormatDebug(output_domain, "converting in=%s -> f=%s -> out=%s",
			    ToString(in_audio_format).c_str(),
			    ToString(f).c_str(),
			    ToString(output->out_audio_format).c_str());
172 173
}

174 175 176 177 178 179 180 181 182 183 184
inline void
AudioOutputControl::InternalCloseOutput(bool drain) noexcept
{
	assert(IsOpen());

	open = false;

	const ScopeUnlock unlock(mutex);
	output->CloseOutput(drain);
}

185
inline void
186
AudioOutputControl::InternalClose(bool drain) noexcept
187
{
188
	assert(IsOpen());
189

190
	open = false;
191 192 193 194 195 196

	{
		const ScopeUnlock unlock(mutex);
		output->Close(drain);
	}

197
	source.Close();
198 199
}

200 201 202 203 204 205 206
inline void
AudioOutputControl::InternalCheckClose(bool drain) noexcept
{
	if (IsOpen())
		InternalClose(drain);
}

207 208 209 210 211 212
/**
 * Wait until the output's delay reaches zero.
 *
 * @return true if playback should be continued, false if a command
 * was issued
 */
213
inline bool
214
AudioOutputControl::WaitForDelay(std::unique_lock<Mutex> &lock) noexcept
215 216
{
	while (true) {
217
		const auto delay = output->Delay();
218
		if (delay <= std::chrono::steady_clock::duration::zero())
219 220
			return true;

221
		(void)wake_cond.wait_for(lock, delay);
222

223
		if (command != Command::NONE)
224 225 226 227
			return false;
	}
}

228
bool
229
AudioOutputControl::FillSourceOrClose() noexcept
230
try {
231
	return source.Fill(mutex);
232 233 234
} catch (...) {
	FormatError(std::current_exception(),
		    "Failed to filter for %s", GetLogName());
235
	InternalCloseError(std::current_exception());
236 237 238
	return false;
}

239
inline bool
240
AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept
241
{
242 243 244 245 246
	// ensure pending tags are flushed in all cases
	const auto *tag = source.ReadTag();
	if (tags && tag != nullptr) {
		const ScopeUnlock unlock(mutex);
		try {
247
			output->SendTag(*tag);
248 249 250
		} catch (AudioOutputInterrupted) {
			caught_interrupted = true;
			return false;
251 252 253
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to send tag to %s",
254
				    GetLogName());
255
		}
256 257
	}

258
	while (command == Command::NONE) {
259
		const auto data = source.PeekData();
260
		if (data.empty())
261
			break;
262

263 264
		if (skip_delay)
			skip_delay = false;
265
		else if (!WaitForDelay(lock))
266 267
			break;

268 269
		size_t nbytes;

270
		try {
271
			const ScopeUnlock unlock(mutex);
272
			nbytes = output->Play(data.data, data.size);
273
			assert(nbytes > 0);
274
			assert(nbytes <= data.size);
275 276 277
		} catch (AudioOutputInterrupted) {
			caught_interrupted = true;
			return false;
278 279 280
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to play on %s", GetLogName());
281
			InternalCloseError(std::current_exception());
282
			return false;
283 284
		}

285
		assert(nbytes % output->out_audio_format.GetFrameSize() == 0);
286

287
		source.ConsumeData(nbytes);
288 289
	}

290 291 292
	return true;
}

293
inline bool
294
AudioOutputControl::InternalPlay(std::unique_lock<Mutex> &lock) noexcept
295
{
296
	if (!FillSourceOrClose())
297 298
		/* no chunk available */
		return false;
299

300 301
	assert(!in_playback_loop);
	in_playback_loop = true;
302

303 304 305 306 307
	AtScopeExit(this) {
		assert(in_playback_loop);
		in_playback_loop = false;
	};

308 309
	unsigned n = 0;

310
	do {
311 312 313
		if (command != Command::NONE)
			return true;

314 315 316 317 318
		if (++n >= 64) {
			/* wake up the player every now and then to
			   give it a chance to refill the pipe before
			   it runs empty */
			const ScopeUnlock unlock(mutex);
319
			client.ChunksConsumed();
320 321 322
			n = 0;
		}

323
		if (!PlayChunk(lock))
324
			break;
325
	} while (FillSourceOrClose());
326

327
	const ScopeUnlock unlock(mutex);
328
	client.ChunksConsumed();
329 330

	return true;
331 332
}

333
inline void
334
AudioOutputControl::InternalPause(std::unique_lock<Mutex> &lock) noexcept
335
{
336 337 338 339 340
	{
		const ScopeUnlock unlock(mutex);
		output->BeginPause();
	}

341 342
	pause = true;

343
	CommandFinished();
344 345

	do {
346
		if (!WaitForDelay(lock))
347 348
			break;

349 350
		bool success = false;
		try {
351 352
			const ScopeUnlock unlock(mutex);
			success = output->IteratePause();
353
		} catch (AudioOutputInterrupted) {
354 355 356 357
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to pause %s",
				    GetLogName());
358 359 360
		}

		if (!success) {
361
			InternalClose(false);
362
			break;
363
		}
364
	} while (command == Command::NONE);
365

366
	pause = false;
367 368 369 370 371

	{
		const ScopeUnlock unlock(mutex);
		output->EndPause();
	}
372 373

	skip_delay = true;
374 375
}

376 377 378 379 380 381 382 383 384 385 386 387 388 389
static void
PlayFull(FilteredAudioOutput &output, ConstBuffer<void> _buffer)
{
	auto buffer = ConstBuffer<uint8_t>::FromVoid(_buffer);

	while (!buffer.empty()) {
		size_t nbytes = output.Play(buffer.data, buffer.size);
		assert(nbytes > 0);

		buffer.skip_front(nbytes);
	}

}

390 391 392
inline void
AudioOutputControl::InternalDrain() noexcept
{
393 394 395
	try {
		/* flush the filter and play its remaining output */

396 397
		const ScopeUnlock unlock(mutex);

398 399 400 401 402 403 404
		while (true) {
			auto buffer = source.Flush();
			if (buffer.IsNull())
				break;

			PlayFull(*output, buffer);
		}
405 406

		output->Drain();
407 408 409 410 411 412
	} catch (...) {
		FormatError(std::current_exception(),
			    "Failed to flush filter on %s", GetLogName());
		InternalCloseError(std::current_exception());
		return;
	}
413 414
}

415
void
416
AudioOutputControl::Task() noexcept
417
{
418
	FormatThreadName("output:%s", GetName());
419

420 421
	try {
		SetThreadRealtime();
422
	} catch (...) {
423 424
		Log(LogLevel::INFO, std::current_exception(),
		    "OutputThread could not get realtime scheduling, continuing anyway");
425
	}
426

427
	SetThreadTimerSlack(std::chrono::microseconds(100));
428

429
	std::unique_lock<Mutex> lock(mutex);
430

431
	while (true) {
432
		switch (command) {
433
		case Command::NONE:
434 435 436
			/* no pending command: play (or wait for a
			   command) */

437 438
			if (open && allow_play && !caught_interrupted &&
			    InternalPlay(lock))
439 440 441 442 443 444
				/* don't wait for an event if there
				   are more chunks in the pipe */
				continue;

			woken_for_play = false;
			wake_cond.wait(lock);
445 446
			break;

447
		case Command::ENABLE:
448
			InternalEnable();
449
			CommandFinished();
450 451
			break;

452
		case Command::DISABLE:
453
			InternalDisable();
454
			CommandFinished();
455 456
			break;

457
		case Command::OPEN:
458
			InternalOpen(request.audio_format, *request.pipe);
459
			CommandFinished();
460 461
			break;

462
		case Command::CLOSE:
463
			InternalCheckClose(false);
464
			CommandFinished();
465 466
			break;

467
		case Command::PAUSE:
468
			if (!open) {
469
				/* the output has failed after
470
				   the PAUSE command was submitted; bail
471
				   out */
472
				CommandFinished();
473 474 475
				break;
			}

476 477
			caught_interrupted = false;

478
			InternalPause(lock);
479
			break;
480

481 482 483
		case Command::RELEASE:
			if (!open) {
				/* the output has failed after
484
				   the RELEASE command was submitted; bail
485 486 487 488 489
				   out */
				CommandFinished();
				break;
			}

490 491
			caught_interrupted = false;

492 493
			if (always_on) {
				/* in "always_on" mode, the output is
494
				   paused instead of being closed;
495
				   however we need to flush the
496 497 498
				   AudioOutputSource because its data
				   have been invalidated by stopping
				   the actual playback */
499
				source.Cancel();
500
				InternalPause(lock);
501 502 503 504 505
			} else {
				InternalClose(false);
				CommandFinished();
			}

506
			break;
507

508
		case Command::DRAIN:
509 510
			if (open)
				InternalDrain();
511

512
			CommandFinished();
513
			break;
514

515
		case Command::CANCEL:
516 517
			caught_interrupted = false;

518
			source.Cancel();
519

520
			if (open) {
521
				const ScopeUnlock unlock(mutex);
522
				output->Cancel();
523 524
			}

525
			CommandFinished();
526
			break;
527

528
		case Command::KILL:
529
			InternalDisable();
530
			source.Cancel();
531
			CommandFinished();
532
			return;
533 534 535 536
		}
	}
}

537
void
538
AudioOutputControl::StartThread()
539
{
540
	assert(command == Command::NONE);
541

542 543
	killed = false;

544
	const ScopeUnlock unlock(mutex);
545
	thread.Start();
546
}