Control.cxx 7.77 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"
Max Kellermann's avatar
Max Kellermann committed
22
#include "mixer/MixerControl.hxx"
23
#include "config/Block.hxx"
24
#include "Log.hxx"
25

26
#include <assert.h>
27

28
/** after a failure, wait this duration before
29
    automatically reopening the device */
30
static constexpr PeriodClock::Duration REOPEN_AFTER = std::chrono::seconds(10);
31

32
AudioOutputControl::AudioOutputControl(std::unique_ptr<FilteredAudioOutput> _output,
33
				       AudioOutputClient &_client) noexcept
34
	:output(std::move(_output)), client(_client),
35
	 thread(BIND_THIS_METHOD(Task))
36 37 38
{
}

39 40
AudioOutputControl::~AudioOutputControl() noexcept
{
41 42
	BeginDestroy();

43 44
	if (thread.IsDefined())
		thread.Join();
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::GetPluginName() const noexcept
{
	return output->GetPluginName();
}

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

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

79 80 81 82 83 84 85 86 87 88 89 90
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));
}

91
bool
92
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
93 94 95
{
	const std::lock_guard<Mutex> protect(mutex);

96
	if (new_value == enabled)
97 98
		return false;

99
	enabled = new_value;
100 101 102 103
	return true;
}

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

110
void
111
AudioOutputControl::WaitForCommand(std::unique_lock<Mutex> &lock) noexcept
112
{
113
	client_cond.wait(lock, [this]{ return IsCommandFinished(); });
114 115
}

116
void
117
AudioOutputControl::CommandAsync(Command cmd) noexcept
118
{
119 120 121
	assert(IsCommandFinished());

	command = cmd;
122
	wake_cond.notify_one();
123
}
124

125
void
126 127
AudioOutputControl::CommandWait(std::unique_lock<Mutex> &lock,
				Command cmd) noexcept
128
{
129
	CommandAsync(cmd);
130
	WaitForCommand(lock);
131 132
}

133
void
134
AudioOutputControl::LockCommandWait(Command cmd) noexcept
135
{
136 137
	std::unique_lock<Mutex> lock(mutex);
	CommandWait(lock, cmd);
138 139
}

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

152
		StartThread();
153 154
	}

155
	CommandAsync(Command::ENABLE);
156 157 158
}

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

		return;
	}

172
	CommandAsync(Command::DISABLE);
173 174
}

175 176 177
void
AudioOutputControl::EnableDisableAsync()
{
178
	if (enabled == really_enabled)
179
		return;
180

181
	if (enabled)
182 183 184
		EnableAsync();
	else
		DisableAsync();
185 186
}

187
inline bool
188 189
AudioOutputControl::Open(std::unique_lock<Mutex> &lock,
			 const AudioFormat audio_format,
190
			 const MusicPipe &mp) noexcept
191
{
192
	assert(allow_play);
193
	assert(audio_format.IsValid());
194

195
	fail_timer.Reset();
196

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

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

206
	request.audio_format = audio_format;
207
	request.pipe = &mp;
208

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

218
	CommandWait(lock, Command::OPEN);
219
	const bool open2 = open;
220

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

232
	return open2;
233 234
}

235
void
236
AudioOutputControl::CloseWait(std::unique_lock<Mutex> &lock) noexcept
237
{
238
	assert(allow_play);
239

240 241
	if (output->mixer != nullptr)
		mixer_auto_close(output->mixer);
242

243
	assert(!open || !fail_timer.IsDefined());
244

245
	if (open)
246
		CommandWait(lock, Command::CLOSE);
247
	else
248
		fail_timer.Reset();
249 250
}

251
bool
252 253
AudioOutputControl::LockUpdate(const AudioFormat audio_format,
			       const MusicPipe &mp,
254
			       bool force) noexcept
255
{
256
	std::unique_lock<Mutex> lock(mutex);
257

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

	return false;
267 268
}

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

	return source.IsChunkConsumed(chunk);
276 277
}

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

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

290
	assert(allow_play);
291

292 293
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
294
		wake_cond.notify_one();
295
	}
296 297
}

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

307
	const std::lock_guard<Mutex> protect(mutex);
308

309 310
	assert(allow_play);
	if (IsOpen())
311
		CommandAsync(Command::PAUSE);
312 313
}

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

319 320
	assert(allow_play);
	if (IsOpen())
321
		CommandAsync(Command::DRAIN);
322 323
}

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

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

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

340 341
	allow_play = true;
	if (IsOpen())
342
		wake_cond.notify_one();
343 344
}

345
void
346
AudioOutputControl::LockRelease() noexcept
347
{
348 349 350 351 352 353 354
	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);

355
	std::unique_lock<Mutex> lock(mutex);
356 357 358 359 360

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

	if (IsOpen())
361
		CommandWait(lock, Command::RELEASE);
362
	else
363
		fail_timer.Reset();
364 365
}

366
void
367
AudioOutputControl::LockCloseWait() noexcept
368
{
369
	assert(!open || !fail_timer.IsDefined());
370

371 372
	std::unique_lock<Mutex> lock(mutex);
	CloseWait(lock);
373 374
}

375
void
376
AudioOutputControl::BeginDestroy() noexcept
377
{
378 379
	if (thread.IsDefined()) {
		const std::lock_guard<Mutex> protect(mutex);
380 381
		if (IsCommandFinished())
			CommandAsync(Command::KILL);
382
	}
383
}