Control.cxx 9.54 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "Filtered.hxx"
22
#include "Client.hxx"
23 24
#include "Domain.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "mixer/MixerControl.hxx"
26
#include "config/Block.hxx"
27
#include "Log.hxx"
28

29
#include <cassert>
30

31
/** after a failure, wait this duration before
32
    automatically reopening the device */
33
static constexpr PeriodClock::Duration REOPEN_AFTER = std::chrono::seconds(10);
34

35
AudioOutputControl::AudioOutputControl(std::unique_ptr<FilteredAudioOutput> _output,
36
				       AudioOutputClient &_client) noexcept
37 38 39
	:output(std::move(_output)),
	 name(output->GetName()),
	 client(_client),
40
	 thread(BIND_THIS_METHOD(Task))
41 42 43
{
}

44 45
AudioOutputControl::AudioOutputControl(AudioOutputControl *_output,
				       AudioOutputClient &_client) noexcept
46
	:output(_output->Steal()),
47 48 49 50 51 52 53 54
	 name(output->GetName()),
	 client(_client),
	 thread(BIND_THIS_METHOD(Task))
{
     tags =_output->tags;
	 always_on=_output->always_on;
}

55 56
AudioOutputControl::~AudioOutputControl() noexcept
{
57
	StopThread();
58 59
}

60 61 62
void
AudioOutputControl::Configure(const ConfigBlock &block)
{
63 64 65
	tags = block.GetBlockValue("tags", true);
	always_on = block.GetBlockValue("always_on", false);
	enabled = block.GetBlockValue("enabled", true);
66 67
}

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
std::unique_ptr<FilteredAudioOutput>
AudioOutputControl::Steal() noexcept
{
	assert(!IsDummy());

	/* close and disable the output */
	{
		std::unique_lock<Mutex> lock(mutex);
		if (really_enabled && output->SupportsEnableDisable())
			CommandWait(lock, Command::DISABLE);

		enabled = really_enabled = false;
	}

	/* stop the thread */
	StopThread();

	/* now we can finally remove it */
	const std::lock_guard<Mutex> protect(mutex);
	return std::exchange(output, nullptr);
}

void
AudioOutputControl::ReplaceDummy(std::unique_ptr<FilteredAudioOutput> new_output,
				 bool _enabled) noexcept
{
	assert(IsDummy());
	assert(new_output);

	{
		const std::lock_guard<Mutex> protect(mutex);
		output = std::move(new_output);
		enabled = _enabled;
	}

	client.ApplyEnabled();
}

106
const char *
107
AudioOutputControl::GetName() const noexcept
108
{
109
	return name.c_str();
110 111
}

112 113 114
const char *
AudioOutputControl::GetPluginName() const noexcept
{
115
	return output ? output->GetPluginName() : "dummy";
116 117
}

118 119 120
const char *
AudioOutputControl::GetLogName() const noexcept
{
121 122
	assert(!IsDummy());

123
	return output ? output->GetLogName() : name.c_str();
124 125
}

126
Mixer *
127
AudioOutputControl::GetMixer() const noexcept
128
{
129
	return output ? output->mixer : nullptr;
130 131
}

132
std::map<std::string, std::string>
133 134
AudioOutputControl::GetAttributes() const noexcept
{
135 136 137
	return output
		? output->GetAttributes()
		: std::map<std::string, std::string>{};
138 139 140
}

void
141 142
AudioOutputControl::SetAttribute(std::string &&attribute_name,
				 std::string &&value)
143
{
144 145 146 147
	if (!output)
		throw std::runtime_error("Cannot set attribute on dummy output");

	output->SetAttribute(std::move(attribute_name), std::move(value));
148 149
}

150
bool
151
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
152 153 154
{
	const std::lock_guard<Mutex> protect(mutex);

155
	if (new_value == enabled)
156 157
		return false;

158
	enabled = new_value;
159 160 161 162
	return true;
}

bool
163
AudioOutputControl::LockToggleEnabled() noexcept
164 165
{
	const std::lock_guard<Mutex> protect(mutex);
166
	return enabled = !enabled;
167 168
}

169
void
170
AudioOutputControl::WaitForCommand(std::unique_lock<Mutex> &lock) noexcept
171
{
172
	client_cond.wait(lock, [this]{ return IsCommandFinished(); });
173 174
}

175
void
176
AudioOutputControl::CommandAsync(Command cmd) noexcept
177
{
178 179 180
	assert(IsCommandFinished());

	command = cmd;
181
	wake_cond.notify_one();
182
}
183

184
void
185 186
AudioOutputControl::CommandWait(std::unique_lock<Mutex> &lock,
				Command cmd) noexcept
187
{
188
	CommandAsync(cmd);
189
	WaitForCommand(lock);
190 191
}

192
void
193
AudioOutputControl::LockCommandWait(Command cmd) noexcept
194
{
195 196
	std::unique_lock<Mutex> lock(mutex);
	CommandWait(lock, cmd);
197 198
}

199
void
200
AudioOutputControl::EnableAsync()
201
{
202 203 204
	if (!output)
		return;

205
	if (!thread.IsDefined()) {
206
		if (!output->SupportsEnableDisable()) {
207 208 209
			/* don't bother to start the thread now if the
			   device doesn't even have a enable() method;
			   just assign the variable and we're done */
210
			really_enabled = true;
211 212 213
			return;
		}

214
		StartThread();
215 216
	}

217
	CommandAsync(Command::ENABLE);
218 219 220
}

void
221
AudioOutputControl::DisableAsync() noexcept
222
{
223 224 225
	if (!output)
		return;

226
	if (!thread.IsDefined()) {
227
		if (!output->SupportsEnableDisable())
228
			really_enabled = false;
229 230 231
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
232
			assert(!really_enabled);
233 234 235 236

		return;
	}

237
	CommandAsync(Command::DISABLE);
238 239
}

240 241 242
void
AudioOutputControl::EnableDisableAsync()
{
243
	if (enabled == really_enabled)
244
		return;
245

246
	if (enabled)
247 248 249
		EnableAsync();
	else
		DisableAsync();
250 251
}

252
inline bool
253 254
AudioOutputControl::Open(std::unique_lock<Mutex> &lock,
			 const AudioFormat audio_format,
255
			 const MusicPipe &mp) noexcept
256
{
257
	assert(allow_play);
258
	assert(audio_format.IsValid());
259

260
	fail_timer.Reset();
261

262
	if (open && audio_format == request.audio_format) {
263
		assert(request.pipe == &mp || (always_on && pause));
264

265
		if (!pause)
266 267 268
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
269 270
	}

271
	request.audio_format = audio_format;
272
	request.pipe = &mp;
273

274 275 276 277 278 279 280 281
	if (!thread.IsDefined()) {
		try {
			StartThread();
		} catch (...) {
			LogError(std::current_exception());
			return false;
		}
	}
282

283
	CommandWait(lock, Command::OPEN);
284
	const bool open2 = open;
285

286
	if (open2 && output->mixer != nullptr) {
287
		const ScopeUnlock unlock(mutex);
288
		try {
289
			mixer_open(output->mixer);
290
		} catch (...) {
291 292 293
			FmtError(output_domain,
				 "Failed to open mixer for '{}': {}",
				 GetName(), std::current_exception());
294
		}
295
	}
296

297
	return open2;
298 299
}

300
void
301
AudioOutputControl::CloseWait(std::unique_lock<Mutex> &lock) noexcept
302
{
303
	assert(allow_play);
304

305 306 307
	if (IsDummy())
		return;

308 309
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
310

311
	assert(!open || !fail_timer.IsDefined());
312

313
	if (open)
314
		CommandWait(lock, Command::CLOSE);
315
	else
316
		fail_timer.Reset();
317 318
}

319
bool
320 321
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
322
			       bool force) noexcept
323
{
324
	std::unique_lock<Mutex> lock(mutex);
325

326
	if (enabled && really_enabled) {
327
		if (force || !fail_timer.IsDefined() ||
328
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
329
			return Open(lock, audio_format, mp);
330
		}
331
	} else if (IsOpen())
332
		CloseWait(lock);
333 334

	return false;
335 336
}

337
bool
338
AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
339
{
340
	if (!open)
341 342 343
		return true;

	return source.IsChunkConsumed(chunk);
344 345
}

346 347
bool
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
348
{
349 350
	const std::lock_guard<Mutex> protect(mutex);
	return IsChunkConsumed(chunk);
351 352
}

353
void
354
AudioOutputControl::LockPlay() noexcept
355
{
356
	const std::lock_guard<Mutex> protect(mutex);
357

358
	assert(allow_play);
359

360 361
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
362
		wake_cond.notify_one();
363
	}
364 365
}

366
void
367
AudioOutputControl::LockPauseAsync() noexcept
368
{
369
	if (output && output->mixer != nullptr && !output->SupportsPause())
370 371 372
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
373
		mixer_auto_close(output->mixer);
374

375 376 377
	if (output)
		output->Interrupt();

378
	const std::lock_guard<Mutex> protect(mutex);
379

380 381
	assert(allow_play);
	if (IsOpen())
382
		CommandAsync(Command::PAUSE);
383 384
}

385
void
386
AudioOutputControl::LockDrainAsync() noexcept
387
{
388
	const std::lock_guard<Mutex> protect(mutex);
389

390 391
	assert(allow_play);
	if (IsOpen())
392
		CommandAsync(Command::DRAIN);
393 394
}

395
void
396
AudioOutputControl::LockCancelAsync() noexcept
397
{
398 399 400
	if (output)
		output->Interrupt();

401
	const std::lock_guard<Mutex> protect(mutex);
402

403 404
	if (IsOpen()) {
		allow_play = false;
405
		CommandAsync(Command::CANCEL);
406
	}
407 408 409
}

void
410
AudioOutputControl::LockAllowPlay() noexcept
411
{
412
	const std::lock_guard<Mutex> protect(mutex);
413

414 415
	allow_play = true;
	if (IsOpen())
416
		wake_cond.notify_one();
417 418
}

419
void
420
AudioOutputControl::LockRelease() noexcept
421
{
422 423 424
	if (!output)
		return;

425 426
	output->Interrupt();

427 428 429 430 431 432 433
	if (output->mixer != nullptr &&
	    (!always_on || !output->SupportsPause()))
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
		mixer_auto_close(output->mixer);

434
	std::unique_lock<Mutex> lock(mutex);
435 436 437 438 439

	assert(!open || !fail_timer.IsDefined());
	assert(allow_play);

	if (IsOpen())
440
		CommandWait(lock, Command::RELEASE);
441
	else
442
		fail_timer.Reset();
443 444
}

445
void
446
AudioOutputControl::LockCloseWait() noexcept
447
{
448
	assert(!open || !fail_timer.IsDefined());
449

450 451 452
	if (output)
		output->Interrupt();

453 454
	std::unique_lock<Mutex> lock(mutex);
	CloseWait(lock);
455 456
}

457
void
458
AudioOutputControl::BeginDestroy() noexcept
459
{
460
	if (thread.IsDefined()) {
461 462 463
		if (output)
			output->Interrupt();

464
		const std::lock_guard<Mutex> protect(mutex);
465 466
		if (!killed) {
			killed = true;
467
			CommandAsync(Command::KILL);
468
		}
469
	}
470
}
471 472 473 474 475 476 477 478 479 480 481

void
AudioOutputControl::StopThread() noexcept
{
	BeginDestroy();

	if (thread.IsDefined())
		thread.Join();

	assert(IsCommandFinished());
}