Control.cxx 9.43 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"
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(AudioOutputControl *_output,
				       AudioOutputClient &_client) noexcept
44
	:output(_output->Steal()),
45 46 47 48 49 50 51 52
	 name(output->GetName()),
	 client(_client),
	 thread(BIND_THIS_METHOD(Task))
{
     tags =_output->tags;
	 always_on=_output->always_on;
}

53 54
AudioOutputControl::~AudioOutputControl() noexcept
{
55
	StopThread();
56 57
}

58 59 60
void
AudioOutputControl::Configure(const ConfigBlock &block)
{
61 62 63
	tags = block.GetBlockValue("tags", true);
	always_on = block.GetBlockValue("always_on", false);
	enabled = block.GetBlockValue("enabled", true);
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 93 94 95 96 97 98 99 100 101 102 103
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();
}

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

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

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

121 122 123
	return output->GetLogName();
}

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

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

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

	output->SetAttribute(std::move(attribute_name), std::move(value));
146 147
}

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

153
	if (new_value == enabled)
154 155
		return false;

156
	enabled = new_value;
157 158 159 160
	return true;
}

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

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

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

	command = cmd;
179
	wake_cond.notify_one();
180
}
181

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

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

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

203
	if (!thread.IsDefined()) {
204
		if (!output->SupportsEnableDisable()) {
205 206 207
			/* 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 */
208
			really_enabled = true;
209 210 211
			return;
		}

212
		StartThread();
213 214
	}

215
	CommandAsync(Command::ENABLE);
216 217 218
}

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

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

		return;
	}

235
	CommandAsync(Command::DISABLE);
236 237
}

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

244
	if (enabled)
245 246 247
		EnableAsync();
	else
		DisableAsync();
248 249
}

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

258
	fail_timer.Reset();
259

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

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

269
	request.audio_format = audio_format;
270
	request.pipe = &mp;
271

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

281
	CommandWait(lock, Command::OPEN);
282
	const bool open2 = open;
283

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

295
	return open2;
296 297
}

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

303 304 305
	if (IsDummy())
		return;

306 307
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
308

309
	assert(!open || !fail_timer.IsDefined());
310

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

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

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

	return false;
333 334
}

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

	return source.IsChunkConsumed(chunk);
342 343
}

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

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

356
	assert(allow_play);
357

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

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

373 374 375
	if (output)
		output->Interrupt();

376
	const std::lock_guard<Mutex> protect(mutex);
377

378 379
	assert(allow_play);
	if (IsOpen())
380
		CommandAsync(Command::PAUSE);
381 382
}

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

388 389
	assert(allow_play);
	if (IsOpen())
390
		CommandAsync(Command::DRAIN);
391 392
}

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

399
	const std::lock_guard<Mutex> protect(mutex);
400

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

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

412 413
	allow_play = true;
	if (IsOpen())
414
		wake_cond.notify_one();
415 416
}

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

423 424
	output->Interrupt();

425 426 427 428 429 430 431
	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);

432
	std::unique_lock<Mutex> lock(mutex);
433 434 435 436 437

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

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

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

448 449 450
	if (output)
		output->Interrupt();

451 452
	std::unique_lock<Mutex> lock(mutex);
	CloseWait(lock);
453 454
}

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

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

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

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

	assert(IsCommandFinished());
}