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

28 29
#include <stdexcept>

30
#include <assert.h>
31

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

154
		StartThread();
155 156
	}

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

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

		return;
	}

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

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

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

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

196
	fail_timer.Reset();
197

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

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

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

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

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

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

233
	return open2;
234 235
}

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

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

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

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

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

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

	return false;
268 269
}

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

	return source.IsChunkConsumed(chunk);
277 278
}

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

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

291
	assert(allow_play);
292

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

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

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

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

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

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

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

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

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

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

346
void
347
AudioOutputControl::LockRelease() noexcept
348
{
349 350 351 352 353 354 355 356 357 358 359 360 361 362
	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);

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

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

	if (IsOpen())
		CommandWait(Command::RELEASE);
363
	else
364
		fail_timer.Reset();
365 366
}

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

372
	const std::lock_guard<Mutex> protect(mutex);
373
	CloseWait();
374 375
}

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