Control.cxx 9.15 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 "Filtered.hxx"
22
#include "Client.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "mixer/MixerControl.hxx"
24
#include "config/Block.hxx"
25
#include "Log.hxx"
26

27
#include <cassert>
28

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

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

42 43
AudioOutputControl::~AudioOutputControl() noexcept
{
44
	StopThread();
45 46
}

47 48 49
void
AudioOutputControl::Configure(const ConfigBlock &block)
{
50 51 52
	tags = block.GetBlockValue("tags", true);
	always_on = block.GetBlockValue("always_on", false);
	enabled = block.GetBlockValue("enabled", true);
53 54
}

55 56 57 58 59 60 61 62 63 64 65 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
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();
}

93
const char *
94
AudioOutputControl::GetName() const noexcept
95
{
96
	return name.c_str();
97 98
}

99 100 101
const char *
AudioOutputControl::GetPluginName() const noexcept
{
102
	return output ? output->GetPluginName() : "dummy";
103 104
}

105 106 107
const char *
AudioOutputControl::GetLogName() const noexcept
{
108 109
	assert(!IsDummy());

110 111 112
	return output->GetLogName();
}

113
Mixer *
114
AudioOutputControl::GetMixer() const noexcept
115
{
116
	return output ? output->mixer : nullptr;
117 118
}

119
std::map<std::string, std::string>
120 121
AudioOutputControl::GetAttributes() const noexcept
{
122 123 124
	return output
		? output->GetAttributes()
		: std::map<std::string, std::string>{};
125 126 127
}

void
128 129
AudioOutputControl::SetAttribute(std::string &&attribute_name,
				 std::string &&value)
130
{
131 132 133 134
	if (!output)
		throw std::runtime_error("Cannot set attribute on dummy output");

	output->SetAttribute(std::move(attribute_name), std::move(value));
135 136
}

137
bool
138
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
139 140 141
{
	const std::lock_guard<Mutex> protect(mutex);

142
	if (new_value == enabled)
143 144
		return false;

145
	enabled = new_value;
146 147 148 149
	return true;
}

bool
150
AudioOutputControl::LockToggleEnabled() noexcept
151 152
{
	const std::lock_guard<Mutex> protect(mutex);
153
	return enabled = !enabled;
154 155
}

156
void
157
AudioOutputControl::WaitForCommand(std::unique_lock<Mutex> &lock) noexcept
158
{
159
	client_cond.wait(lock, [this]{ return IsCommandFinished(); });
160 161
}

162
void
163
AudioOutputControl::CommandAsync(Command cmd) noexcept
164
{
165 166 167
	assert(IsCommandFinished());

	command = cmd;
168
	wake_cond.notify_one();
169
}
170

171
void
172 173
AudioOutputControl::CommandWait(std::unique_lock<Mutex> &lock,
				Command cmd) noexcept
174
{
175
	CommandAsync(cmd);
176
	WaitForCommand(lock);
177 178
}

179
void
180
AudioOutputControl::LockCommandWait(Command cmd) noexcept
181
{
182 183
	std::unique_lock<Mutex> lock(mutex);
	CommandWait(lock, cmd);
184 185
}

186
void
187
AudioOutputControl::EnableAsync()
188
{
189 190 191
	if (!output)
		return;

192
	if (!thread.IsDefined()) {
193
		if (!output->SupportsEnableDisable()) {
194 195 196
			/* 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 */
197
			really_enabled = true;
198 199 200
			return;
		}

201
		StartThread();
202 203
	}

204
	CommandAsync(Command::ENABLE);
205 206 207
}

void
208
AudioOutputControl::DisableAsync() noexcept
209
{
210 211 212
	if (!output)
		return;

213
	if (!thread.IsDefined()) {
214
		if (!output->SupportsEnableDisable())
215
			really_enabled = false;
216 217 218
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
219
			assert(!really_enabled);
220 221 222 223

		return;
	}

224
	CommandAsync(Command::DISABLE);
225 226
}

227 228 229
void
AudioOutputControl::EnableDisableAsync()
{
230
	if (enabled == really_enabled)
231
		return;
232

233
	if (enabled)
234 235 236
		EnableAsync();
	else
		DisableAsync();
237 238
}

239
inline bool
240 241
AudioOutputControl::Open(std::unique_lock<Mutex> &lock,
			 const AudioFormat audio_format,
242
			 const MusicPipe &mp) noexcept
243
{
244
	assert(allow_play);
245
	assert(audio_format.IsValid());
246

247
	fail_timer.Reset();
248

249
	if (open && audio_format == request.audio_format) {
250
		assert(request.pipe == &mp || (always_on && pause));
251

252
		if (!pause)
253 254 255
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
256 257
	}

258
	request.audio_format = audio_format;
259
	request.pipe = &mp;
260

261 262 263 264 265 266 267 268
	if (!thread.IsDefined()) {
		try {
			StartThread();
		} catch (...) {
			LogError(std::current_exception());
			return false;
		}
	}
269

270
	CommandWait(lock, Command::OPEN);
271
	const bool open2 = open;
272

273
	if (open2 && output->mixer != nullptr) {
274
		const ScopeUnlock unlock(mutex);
275
		try {
276
			mixer_open(output->mixer);
277 278 279
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to open mixer for '%s'",
280
				    GetName());
281
		}
282
	}
283

284
	return open2;
285 286
}

287
void
288
AudioOutputControl::CloseWait(std::unique_lock<Mutex> &lock) noexcept
289
{
290
	assert(allow_play);
291

292 293 294
	if (IsDummy())
		return;

295 296
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
297

298
	assert(!open || !fail_timer.IsDefined());
299

300
	if (open)
301
		CommandWait(lock, Command::CLOSE);
302
	else
303
		fail_timer.Reset();
304 305
}

306
bool
307 308
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
309
			       bool force) noexcept
310
{
311
	std::unique_lock<Mutex> lock(mutex);
312

313
	if (enabled && really_enabled) {
314
		if (force || !fail_timer.IsDefined() ||
315
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
316
			return Open(lock, audio_format, mp);
317
		}
318
	} else if (IsOpen())
319
		CloseWait(lock);
320 321

	return false;
322 323
}

324
bool
325
AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
326
{
327
	if (!open)
328 329 330
		return true;

	return source.IsChunkConsumed(chunk);
331 332
}

333 334
bool
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
335
{
336 337
	const std::lock_guard<Mutex> protect(mutex);
	return IsChunkConsumed(chunk);
338 339
}

340
void
341
AudioOutputControl::LockPlay() noexcept
342
{
343
	const std::lock_guard<Mutex> protect(mutex);
344

345
	assert(allow_play);
346

347 348
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
349
		wake_cond.notify_one();
350
	}
351 352
}

353
void
354
AudioOutputControl::LockPauseAsync() noexcept
355
{
356
	if (output->mixer != nullptr && !output->SupportsPause())
357 358 359
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
360
		mixer_auto_close(output->mixer);
361

362 363 364
	if (output)
		output->Interrupt();

365
	const std::lock_guard<Mutex> protect(mutex);
366

367 368
	assert(allow_play);
	if (IsOpen())
369
		CommandAsync(Command::PAUSE);
370 371
}

372
void
373
AudioOutputControl::LockDrainAsync() noexcept
374
{
375
	const std::lock_guard<Mutex> protect(mutex);
376

377 378
	assert(allow_play);
	if (IsOpen())
379
		CommandAsync(Command::DRAIN);
380 381
}

382
void
383
AudioOutputControl::LockCancelAsync() noexcept
384
{
385 386 387
	if (output)
		output->Interrupt();

388
	const std::lock_guard<Mutex> protect(mutex);
389

390 391
	if (IsOpen()) {
		allow_play = false;
392
		CommandAsync(Command::CANCEL);
393
	}
394 395 396
}

void
397
AudioOutputControl::LockAllowPlay() noexcept
398
{
399
	const std::lock_guard<Mutex> protect(mutex);
400

401 402
	allow_play = true;
	if (IsOpen())
403
		wake_cond.notify_one();
404 405
}

406
void
407
AudioOutputControl::LockRelease() noexcept
408
{
409 410 411
	if (!output)
		return;

412 413
	output->Interrupt();

414 415 416 417 418 419 420
	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);

421
	std::unique_lock<Mutex> lock(mutex);
422 423 424 425 426

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

	if (IsOpen())
427
		CommandWait(lock, Command::RELEASE);
428
	else
429
		fail_timer.Reset();
430 431
}

432
void
433
AudioOutputControl::LockCloseWait() noexcept
434
{
435
	assert(!open || !fail_timer.IsDefined());
436

437 438 439
	if (output)
		output->Interrupt();

440 441
	std::unique_lock<Mutex> lock(mutex);
	CloseWait(lock);
442 443
}

444
void
445
AudioOutputControl::BeginDestroy() noexcept
446
{
447
	if (thread.IsDefined()) {
448 449 450
		if (output)
			output->Interrupt();

451
		const std::lock_guard<Mutex> protect(mutex);
452 453
		if (!killed) {
			killed = true;
454
			CommandAsync(Command::KILL);
455
		}
456
	}
457
}
458 459 460 461 462 463 464 465 466 467 468

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

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

	assert(IsCommandFinished());
}