Control.cxx 7.58 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(std::unique_ptr<FilteredAudioOutput> _output,
41
				       AudioOutputClient &_client) noexcept
42
	:output(std::move(_output)), client(_client),
43
	 thread(BIND_THIS_METHOD(Task))
44 45 46
{
}

47 48 49 50 51 52 53 54
AudioOutputControl::~AudioOutputControl() noexcept
{
	assert(!fail_timer.IsDefined());
	assert(!thread.IsDefined());
	assert(output == nullptr);
	assert(!open);
}

55 56 57
void
AudioOutputControl::Configure(const ConfigBlock &block)
{
58 59 60
	tags = block.GetBlockValue("tags", true);
	always_on = block.GetBlockValue("always_on", false);
	enabled = block.GetBlockValue("enabled", true);
61 62
}

63
const char *
64
AudioOutputControl::GetName() const noexcept
65 66 67 68
{
	return output->GetName();
}

69 70 71 72 73 74
const char *
AudioOutputControl::GetPluginName() const noexcept
{
	return output->GetPluginName();
}

75 76 77 78 79 80
const char *
AudioOutputControl::GetLogName() const noexcept
{
	return output->GetLogName();
}

81
Mixer *
82
AudioOutputControl::GetMixer() const noexcept
83 84 85 86
{
	return output->mixer;
}

87 88 89 90 91 92 93 94 95 96 97 98
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));
}

99
bool
100
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
101 102 103
{
	const std::lock_guard<Mutex> protect(mutex);

104
	if (new_value == enabled)
105 106
		return false;

107
	enabled = new_value;
108 109 110 111
	return true;
}

bool
112
AudioOutputControl::LockToggleEnabled() noexcept
113 114
{
	const std::lock_guard<Mutex> protect(mutex);
115
	return enabled = !enabled;
116 117
}

118
void
119
AudioOutputControl::WaitForCommand() noexcept
120
{
121
	while (!IsCommandFinished()) {
122
		const ScopeUnlock unlock(mutex);
Max Kellermann's avatar
Max Kellermann committed
123
		audio_output_client_notify.Wait();
124 125 126
	}
}

127
void
128
AudioOutputControl::CommandAsync(Command cmd) noexcept
129
{
130 131 132 133
	assert(IsCommandFinished());

	command = cmd;
	cond.signal();
134
}
135

136
void
137
AudioOutputControl::CommandWait(Command cmd) noexcept
138
{
139 140
	CommandAsync(cmd);
	WaitForCommand();
141 142
}

143
void
144
AudioOutputControl::LockCommandWait(Command cmd) noexcept
145
{
146
	const std::lock_guard<Mutex> protect(mutex);
147
	CommandWait(cmd);
148 149
}

150
void
151
AudioOutputControl::EnableAsync()
152
{
153
	if (!thread.IsDefined()) {
154
		if (!output->SupportsEnableDisable()) {
155 156 157
			/* 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 */
158
			really_enabled = true;
159 160 161
			return;
		}

162
		StartThread();
163 164
	}

165
	CommandAsync(Command::ENABLE);
166 167 168
}

void
169
AudioOutputControl::DisableAsync() noexcept
170
{
171
	if (!thread.IsDefined()) {
172
		if (!output->SupportsEnableDisable())
173
			really_enabled = false;
174 175 176
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
177
			assert(!really_enabled);
178 179 180 181

		return;
	}

182
	CommandAsync(Command::DISABLE);
183 184
}

185 186 187
void
AudioOutputControl::EnableDisableAsync()
{
188
	if (enabled == really_enabled)
189
		return;
190

191
	if (enabled)
192 193 194
		EnableAsync();
	else
		DisableAsync();
195 196
}

197
inline bool
198 199
AudioOutputControl::Open(const AudioFormat audio_format,
			 const MusicPipe &mp) noexcept
200
{
201
	assert(allow_play);
202
	assert(audio_format.IsValid());
203

204
	fail_timer.Reset();
205

206
	if (open && audio_format == request.audio_format) {
207
		assert(request.pipe == &mp || (always_on && pause));
208

209
		if (!pause)
210 211 212
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
213 214
	}

215
	request.audio_format = audio_format;
216
	request.pipe = &mp;
217

218 219 220 221 222 223 224 225
	if (!thread.IsDefined()) {
		try {
			StartThread();
		} catch (...) {
			LogError(std::current_exception());
			return false;
		}
	}
226

227
	CommandWait(Command::OPEN);
228
	const bool open2 = open;
229

230
	if (open2 && output->mixer != nullptr) {
231
		try {
232
			mixer_open(output->mixer);
233 234 235
		} catch (...) {
			FormatError(std::current_exception(),
				    "Failed to open mixer for '%s'",
236
				    GetName());
237
		}
238
	}
239

240
	return open2;
241 242
}

243
void
244
AudioOutputControl::CloseWait() noexcept
245
{
246
	assert(allow_play);
247

248 249
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
250

251
	assert(!open || !fail_timer.IsDefined());
252

253
	if (open)
254
		CommandWait(Command::CLOSE);
255
	else
256
		fail_timer.Reset();
257 258
}

259
bool
260 261
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
262
			       bool force) noexcept
263
{
264
	const std::lock_guard<Mutex> protect(mutex);
265

266
	if (enabled && really_enabled) {
267
		if (force || !fail_timer.IsDefined() ||
268
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
269
			return Open(audio_format, mp);
270
		}
271 272
	} else if (IsOpen())
		CloseWait();
273 274

	return false;
275 276
}

277
bool
278
AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
279
{
280
	if (!open)
281 282 283
		return true;

	return source.IsChunkConsumed(chunk);
284 285
}

286 287
bool
AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
288
{
289 290
	const std::lock_guard<Mutex> protect(mutex);
	return IsChunkConsumed(chunk);
291 292
}

293
void
294
AudioOutputControl::LockPlay() noexcept
295
{
296
	const std::lock_guard<Mutex> protect(mutex);
297

298
	assert(allow_play);
299

300 301 302
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
		cond.signal();
303
	}
304 305
}

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

315
	const std::lock_guard<Mutex> protect(mutex);
316

317 318
	assert(allow_play);
	if (IsOpen())
319
		CommandAsync(Command::PAUSE);
320 321
}

322
void
323
AudioOutputControl::LockDrainAsync() noexcept
324
{
325
	const std::lock_guard<Mutex> protect(mutex);
326

327 328
	assert(allow_play);
	if (IsOpen())
329
		CommandAsync(Command::DRAIN);
330 331
}

332
void
333
AudioOutputControl::LockCancelAsync() noexcept
334
{
335
	const std::lock_guard<Mutex> protect(mutex);
336

337 338
	if (IsOpen()) {
		allow_play = false;
339
		CommandAsync(Command::CANCEL);
340
	}
341 342 343
}

void
344
AudioOutputControl::LockAllowPlay() noexcept
345
{
346
	const std::lock_guard<Mutex> protect(mutex);
347

348 349 350
	allow_play = true;
	if (IsOpen())
		cond.signal();
351 352
}

353
void
354
AudioOutputControl::LockRelease() noexcept
355
{
356
	if (always_on)
357
		LockPauseAsync();
358
	else
359
		LockCloseWait();
360 361
}

362
void
363
AudioOutputControl::LockCloseWait() noexcept
364
{
365
	assert(!open || !fail_timer.IsDefined());
366

367
	const std::lock_guard<Mutex> protect(mutex);
368
	CloseWait();
369 370
}

371
void
372
AudioOutputControl::BeginDestroy() noexcept
373 374
{
	output->BeginDestroy();
375 376 377 378 379

	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
		CommandAsync(Command::KILL);
	}
380 381 382
}

void
383
AudioOutputControl::FinishDestroy() noexcept
384
{
385 386 387
	if (thread.IsDefined())
		thread.Join();

388
	output.reset();
389
}