Control.cxx 7.86 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2019 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 "Domain.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "mixer/MixerControl.hxx"
24
#include "filter/plugins/ReplayGainFilterPlugin.hxx"
25
#include "config/Block.hxx"
26
#include "Log.hxx"
27

28 29
#include <stdexcept>

30
#include <assert.h>
31

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

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

43 44
AudioOutputControl::~AudioOutputControl() noexcept
{
45 46
	BeginDestroy();

47 48
	if (thread.IsDefined())
		thread.Join();
49 50
}

51 52 53
void
AudioOutputControl::Configure(const ConfigBlock &block)
{
54 55 56
	tags = block.GetBlockValue("tags", true);
	always_on = block.GetBlockValue("always_on", false);
	enabled = block.GetBlockValue("enabled", true);
57 58
}

59
const char *
60
AudioOutputControl::GetName() const noexcept
61 62 63 64
{
	return output->GetName();
}

65 66 67 68 69 70
const char *
AudioOutputControl::GetPluginName() const noexcept
{
	return output->GetPluginName();
}

71 72 73 74 75 76
const char *
AudioOutputControl::GetLogName() const noexcept
{
	return output->GetLogName();
}

77
Mixer *
78
AudioOutputControl::GetMixer() const noexcept
79 80 81 82
{
	return output->mixer;
}

83 84 85 86 87 88 89 90 91 92 93 94
const std::map<std::string, std::string>
AudioOutputControl::GetAttributes() const noexcept
{
	return output->GetAttributes();
}

void
AudioOutputControl::SetAttribute(std::string &&name, std::string &&value)
{
	output->SetAttribute(std::move(name), std::move(value));
}

95
bool
96
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
97 98 99
{
	const std::lock_guard<Mutex> protect(mutex);

100
	if (new_value == enabled)
101 102
		return false;

103
	enabled = new_value;
104 105 106 107
	return true;
}

bool
108
AudioOutputControl::LockToggleEnabled() noexcept
109 110
{
	const std::lock_guard<Mutex> protect(mutex);
111
	return enabled = !enabled;
112 113
}

114
void
115
AudioOutputControl::WaitForCommand(std::unique_lock<Mutex> &lock) noexcept
116
{
117
	client_cond.wait(lock, [this]{ return IsCommandFinished(); });
118 119
}

120
void
121
AudioOutputControl::CommandAsync(Command cmd) noexcept
122
{
123 124 125
	assert(IsCommandFinished());

	command = cmd;
126
	wake_cond.notify_one();
127
}
128

129
void
130 131
AudioOutputControl::CommandWait(std::unique_lock<Mutex> &lock,
				Command cmd) noexcept
132
{
133
	CommandAsync(cmd);
134
	WaitForCommand(lock);
135 136
}

137
void
138
AudioOutputControl::LockCommandWait(Command cmd) noexcept
139
{
140 141
	std::unique_lock<Mutex> lock(mutex);
	CommandWait(lock, cmd);
142 143
}

144
void
145
AudioOutputControl::EnableAsync()
146
{
147
	if (!thread.IsDefined()) {
148
		if (!output->SupportsEnableDisable()) {
149 150 151
			/* 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 */
152
			really_enabled = true;
153 154 155
			return;
		}

156
		StartThread();
157 158
	}

159
	CommandAsync(Command::ENABLE);
160 161 162
}

void
163
AudioOutputControl::DisableAsync() noexcept
164
{
165
	if (!thread.IsDefined()) {
166
		if (!output->SupportsEnableDisable())
167
			really_enabled = false;
168 169 170
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
171
			assert(!really_enabled);
172 173 174 175

		return;
	}

176
	CommandAsync(Command::DISABLE);
177 178
}

179 180 181
void
AudioOutputControl::EnableDisableAsync()
{
182
	if (enabled == really_enabled)
183
		return;
184

185
	if (enabled)
186 187 188
		EnableAsync();
	else
		DisableAsync();
189 190
}

191
inline bool
192 193
AudioOutputControl::Open(std::unique_lock<Mutex> &lock,
			 const AudioFormat audio_format,
194
			 const MusicPipe &mp) noexcept
195
{
196
	assert(allow_play);
197
	assert(audio_format.IsValid());
198

199
	fail_timer.Reset();
200

201
	if (open && audio_format == request.audio_format) {
202
		assert(request.pipe == &mp || (always_on && pause));
203

204
		if (!pause)
205 206 207
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
208 209
	}

210
	request.audio_format = audio_format;
211
	request.pipe = &mp;
212

213 214 215 216 217 218 219 220
	if (!thread.IsDefined()) {
		try {
			StartThread();
		} catch (...) {
			LogError(std::current_exception());
			return false;
		}
	}
221

222
	CommandWait(lock, Command::OPEN);
223
	const bool open2 = open;
224

225
	if (open2 && output->mixer != nullptr) {
226
		const ScopeUnlock unlock(mutex);
227
		try {
228
			mixer_open(output->mixer);
229 230 231
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to open mixer for '%s'",
232
				    GetName());
233
		}
234
	}
235

236
	return open2;
237 238
}

239
void
240
AudioOutputControl::CloseWait(std::unique_lock<Mutex> &lock) noexcept
241
{
242
	assert(allow_play);
243

244 245
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
246

247
	assert(!open || !fail_timer.IsDefined());
248

249
	if (open)
250
		CommandWait(lock, Command::CLOSE);
251
	else
252
		fail_timer.Reset();
253 254
}

255
bool
256 257
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
258
			       bool force) noexcept
259
{
260
	std::unique_lock<Mutex> lock(mutex);
261

262
	if (enabled && really_enabled) {
263
		if (force || !fail_timer.IsDefined() ||
264
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
265
			return Open(lock, audio_format, mp);
266
		}
267
	} else if (IsOpen())
268
		CloseWait(lock);
269 270

	return false;
271 272
}

273
bool
274
AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
275
{
276
	if (!open)
277 278 279
		return true;

	return source.IsChunkConsumed(chunk);
280 281
}

282 283
bool
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
284
{
285 286
	const std::lock_guard<Mutex> protect(mutex);
	return IsChunkConsumed(chunk);
287 288
}

289
void
290
AudioOutputControl::LockPlay() noexcept
291
{
292
	const std::lock_guard<Mutex> protect(mutex);
293

294
	assert(allow_play);
295

296 297
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
298
		wake_cond.notify_one();
299
	}
300 301
}

302
void
303
AudioOutputControl::LockPauseAsync() noexcept
304
{
305
	if (output->mixer != nullptr && !output->SupportsPause())
306 307 308
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
309
		mixer_auto_close(output->mixer);
310

311
	const std::lock_guard<Mutex> protect(mutex);
312

313 314
	assert(allow_play);
	if (IsOpen())
315
		CommandAsync(Command::PAUSE);
316 317
}

318
void
319
AudioOutputControl::LockDrainAsync() noexcept
320
{
321
	const std::lock_guard<Mutex> protect(mutex);
322

323 324
	assert(allow_play);
	if (IsOpen())
325
		CommandAsync(Command::DRAIN);
326 327
}

328
void
329
AudioOutputControl::LockCancelAsync() noexcept
330
{
331
	const std::lock_guard<Mutex> protect(mutex);
332

333 334
	if (IsOpen()) {
		allow_play = false;
335
		CommandAsync(Command::CANCEL);
336
	}
337 338 339
}

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

344 345
	allow_play = true;
	if (IsOpen())
346
		wake_cond.notify_one();
347 348
}

349
void
350
AudioOutputControl::LockRelease() noexcept
351
{
352 353 354 355 356 357 358
	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);

359
	std::unique_lock<Mutex> lock(mutex);
360 361 362 363 364

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

	if (IsOpen())
365
		CommandWait(lock, Command::RELEASE);
366
	else
367
		fail_timer.Reset();
368 369
}

370
void
371
AudioOutputControl::LockCloseWait() noexcept
372
{
373
	assert(!open || !fail_timer.IsDefined());
374

375 376
	std::unique_lock<Mutex> lock(mutex);
	CloseWait(lock);
377 378
}

379
void
380
AudioOutputControl::BeginDestroy() noexcept
381
{
382 383
	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
384 385
		if (IsCommandFinished())
			CommandAsync(Command::KILL);
386
	}
387
}