Control.cxx 7.32 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"
25
#include "filter/plugins/ReplayGainFilterPlugin.hxx"
26
#include "config/Block.hxx"
27
#include "Log.hxx"
28

29 30
#include <stdexcept>

31
#include <assert.h>
32

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

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

44 45
AudioOutputControl::~AudioOutputControl() noexcept
{
46 47
	if (thread.IsDefined())
		thread.Join();
48 49
}

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

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

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

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

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

82 83 84 85 86 87 88 89 90 91 92 93
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));
}

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

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

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

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

113
void
114
AudioOutputControl::WaitForCommand() noexcept
115
{
116 117
	while (!IsCommandFinished())
		client_cond.wait(mutex);
118 119
}

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

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

129
void
130
AudioOutputControl::CommandWait(Command cmd) noexcept
131
{
132 133
	CommandAsync(cmd);
	WaitForCommand();
134 135
}

136
void
137
AudioOutputControl::LockCommandWait(Command cmd) noexcept
138
{
139
	const std::lock_guard<Mutex> protect(mutex);
140
	CommandWait(cmd);
141 142
}

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

155
		StartThread();
156 157
	}

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

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

		return;
	}

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

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

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

190
inline bool
191 192
AudioOutputControl::Open(const AudioFormat audio_format,
			 const MusicPipe &mp) noexcept
193
{
194
	assert(allow_play);
195
	assert(audio_format.IsValid());
196

197
	fail_timer.Reset();
198

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

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

208
	request.audio_format = audio_format;
209
	request.pipe = &mp;
210

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

220
	CommandWait(Command::OPEN);
221
	const bool open2 = open;
222

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

234
	return open2;
235 236
}

237
void
238
AudioOutputControl::CloseWait() noexcept
239
{
240
	assert(allow_play);
241

242 243
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
244

245
	assert(!open || !fail_timer.IsDefined());
246

247
	if (open)
248
		CommandWait(Command::CLOSE);
249
	else
250
		fail_timer.Reset();
251 252
}

253
bool
254 255
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
256
			       bool force) noexcept
257
{
258
	const std::lock_guard<Mutex> protect(mutex);
259

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

	return false;
269 270
}

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

	return source.IsChunkConsumed(chunk);
278 279
}

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

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

292
	assert(allow_play);
293

294 295
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
296
		wake_cond.signal();
297
	}
298 299
}

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

309
	const std::lock_guard<Mutex> protect(mutex);
310

311 312
	assert(allow_play);
	if (IsOpen())
313
		CommandAsync(Command::PAUSE);
314 315
}

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

321 322
	assert(allow_play);
	if (IsOpen())
323
		CommandAsync(Command::DRAIN);
324 325
}

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

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

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

342 343
	allow_play = true;
	if (IsOpen())
344
		wake_cond.signal();
345 346
}

347
void
348
AudioOutputControl::LockRelease() noexcept
349
{
350
	if (always_on)
351
		LockPauseAsync();
352
	else
353
		LockCloseWait();
354 355
}

356
void
357
AudioOutputControl::LockCloseWait() noexcept
358
{
359
	assert(!open || !fail_timer.IsDefined());
360

361
	const std::lock_guard<Mutex> protect(mutex);
362
	CloseWait();
363 364
}

365
void
366
AudioOutputControl::BeginDestroy() noexcept
367
{
368 369 370 371
	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
		CommandAsync(Command::KILL);
	}
372
}