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

30 31
#include <stdexcept>

32
#include <assert.h>
33

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

38
struct notify audio_output_client_notify;
39

40
AudioOutputControl::AudioOutputControl(FilteredAudioOutput *_output,
41 42
				       AudioOutputClient &_client)
	:output(_output), client(_client),
43
	 thread(BIND_THIS_METHOD(Task))
44 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
const char *
56
AudioOutputControl::GetName() const noexcept
57 58 59 60
{
	return output->GetName();
}

61 62 63 64 65 66
const char *
AudioOutputControl::GetLogName() const noexcept
{
	return output->GetLogName();
}

67
Mixer *
68
AudioOutputControl::GetMixer() const noexcept
69 70 71 72 73
{
	return output->mixer;
}

bool
74
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
75 76 77
{
	const std::lock_guard<Mutex> protect(mutex);

78
	if (new_value == enabled)
79 80
		return false;

81
	enabled = new_value;
82 83 84 85
	return true;
}

bool
86
AudioOutputControl::LockToggleEnabled() noexcept
87 88
{
	const std::lock_guard<Mutex> protect(mutex);
89
	return enabled = !enabled;
90 91
}

92
void
93
AudioOutputControl::WaitForCommand() noexcept
94
{
95 96
	while (!IsCommandFinished()) {
		mutex.unlock();
Max Kellermann's avatar
Max Kellermann committed
97
		audio_output_client_notify.Wait();
98
		mutex.lock();
99 100 101
	}
}

102
void
103
AudioOutputControl::CommandAsync(Command cmd) noexcept
104
{
105 106 107 108
	assert(IsCommandFinished());

	command = cmd;
	cond.signal();
109
}
110

111
void
112
AudioOutputControl::CommandWait(Command cmd) noexcept
113
{
114 115
	CommandAsync(cmd);
	WaitForCommand();
116 117
}

118
void
119
AudioOutputControl::LockCommandWait(Command cmd) noexcept
120
{
121
	const std::lock_guard<Mutex> protect(mutex);
122
	CommandWait(cmd);
123 124
}

125
void
126
AudioOutputControl::EnableAsync()
127
{
128
	if (!thread.IsDefined()) {
129
		if (!output->SupportsEnableDisable()) {
130 131 132
			/* 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 */
133
			really_enabled = true;
134 135 136
			return;
		}

137
		StartThread();
138 139
	}

140
	CommandAsync(Command::ENABLE);
141 142 143
}

void
144
AudioOutputControl::DisableAsync() noexcept
145
{
146
	if (!thread.IsDefined()) {
147
		if (!output->SupportsEnableDisable())
148
			really_enabled = false;
149 150 151
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
152
			assert(!really_enabled);
153 154 155 156

		return;
	}

157
	CommandAsync(Command::DISABLE);
158 159
}

160 161 162
void
AudioOutputControl::EnableDisableAsync()
{
163
	if (enabled == really_enabled)
164
		return;
165

166
	if (enabled)
167 168 169
		EnableAsync();
	else
		DisableAsync();
170 171
}

172
inline bool
173 174
AudioOutputControl::Open(const AudioFormat audio_format,
			 const MusicPipe &mp) noexcept
175
{
176
	assert(allow_play);
177
	assert(audio_format.IsValid());
178

179
	fail_timer.Reset();
180

181
	if (open && audio_format == request.audio_format) {
182
		assert(request.pipe == &mp || (always_on && pause));
183

184
		if (!pause)
185 186 187
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
188 189
	}

190
	request.audio_format = audio_format;
191
	request.pipe = &mp;
192

193 194
	if (!thread.IsDefined())
		StartThread();
195

196
	CommandWait(Command::OPEN);
197
	const bool open2 = open;
198

199
	if (open2 && output->mixer != nullptr) {
200
		try {
201
			mixer_open(output->mixer);
202
		} catch (const std::runtime_error &e) {
203 204
			FormatError(e, "Failed to open mixer for '%s'",
				    GetName());
205
		}
206
	}
207

208
	return open2;
209 210
}

211
void
212
AudioOutputControl::CloseWait() noexcept
213
{
214
	assert(allow_play);
215

216 217
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
218

219
	assert(!open || !fail_timer.IsDefined());
220

221
	if (open)
222
		CommandWait(Command::CLOSE);
223
	else
224
		fail_timer.Reset();
225 226
}

227
bool
228 229
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
230
			       bool force) noexcept
231
{
232
	const std::lock_guard<Mutex> protect(mutex);
233

234
	if (enabled && really_enabled) {
235
		if (force || !fail_timer.IsDefined() ||
236
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
237
			return Open(audio_format, mp);
238
		}
239 240
	} else if (IsOpen())
		CloseWait();
241 242

	return false;
243 244
}

245
bool
246
AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
247
{
248
	if (!open)
249 250 251
		return true;

	return source.IsChunkConsumed(chunk);
252 253
}

254 255
bool
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
256
{
257 258
	const std::lock_guard<Mutex> protect(mutex);
	return IsChunkConsumed(chunk);
259 260
}

261
void
262
AudioOutputControl::LockPlay() noexcept
263
{
264
	const std::lock_guard<Mutex> protect(mutex);
265

266
	assert(allow_play);
267

268 269 270
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
		cond.signal();
271
	}
272 273
}

274
void
275
AudioOutputControl::LockPauseAsync() noexcept
276
{
277
	if (output->mixer != nullptr && !output->SupportsPause())
278 279 280
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
281
		mixer_auto_close(output->mixer);
282

283
	const std::lock_guard<Mutex> protect(mutex);
284

285 286
	assert(allow_play);
	if (IsOpen())
287
		CommandAsync(Command::PAUSE);
288 289
}

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

295 296
	assert(allow_play);
	if (IsOpen())
297
		CommandAsync(Command::DRAIN);
298 299
}

300
void
301
AudioOutputControl::LockCancelAsync() noexcept
302
{
303
	const std::lock_guard<Mutex> protect(mutex);
304

305 306
	if (IsOpen()) {
		allow_play = false;
307
		CommandAsync(Command::CANCEL);
308
	}
309 310 311
}

void
312
AudioOutputControl::LockAllowPlay() noexcept
313
{
314
	const std::lock_guard<Mutex> protect(mutex);
315

316 317 318
	allow_play = true;
	if (IsOpen())
		cond.signal();
319 320
}

321
void
322
AudioOutputControl::LockRelease() noexcept
323
{
324
	if (always_on)
325
		LockPauseAsync();
326
	else
327
		LockCloseWait();
328 329
}

330
void
331
AudioOutputControl::LockCloseWait() noexcept
332
{
333
	assert(!open || !fail_timer.IsDefined());
334

335
	const std::lock_guard<Mutex> protect(mutex);
336
	CloseWait();
337 338
}

339
void
340
AudioOutputControl::StopThread() noexcept
341
{
342 343
	assert(thread.IsDefined());
	assert(allow_play);
344

345
	LockCommandWait(Command::KILL);
346 347
	thread.Join();
}
348

349
void
350
AudioOutputControl::BeginDestroy() noexcept
351 352
{
	output->BeginDestroy();
353 354 355 356 357

	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
		CommandAsync(Command::KILL);
	}
358 359 360
}

void
361
AudioOutputControl::FinishDestroy() noexcept
362
{
363 364 365
	if (thread.IsDefined())
		thread.Join();

366 367 368
	output->FinishDestroy();
	output = nullptr;
}