Control.cxx 7.42 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
AudioOutputControl::~AudioOutputControl() noexcept
{
49 50
	if (thread.IsDefined())
		thread.Join();
51 52
}

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

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

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

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

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

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

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

102
	if (new_value == enabled)
103 104
		return false;

105
	enabled = new_value;
106 107 108 109
	return true;
}

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

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

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

	command = cmd;
	cond.signal();
132
}
133

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

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

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

160
		StartThread();
161 162
	}

163
	CommandAsync(Command::ENABLE);
164 165 166
}

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

		return;
	}

180
	CommandAsync(Command::DISABLE);
181 182
}

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

189
	if (enabled)
190 191 192
		EnableAsync();
	else
		DisableAsync();
193 194
}

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

202
	fail_timer.Reset();
203

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

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

213
	request.audio_format = audio_format;
214
	request.pipe = &mp;
215

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

225
	CommandWait(Command::OPEN);
226
	const bool open2 = open;
227

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

239
	return open2;
240 241
}

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

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

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

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

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

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

	return false;
274 275
}

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

	return source.IsChunkConsumed(chunk);
283 284
}

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

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

297
	assert(allow_play);
298

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

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

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

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

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

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

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

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

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

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

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

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

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

370
void
371
AudioOutputControl::BeginDestroy() noexcept
372
{
373 374 375 376
	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
		CommandAsync(Command::KILL);
	}
377
}