OutputControl.cxx 5.33 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 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 "Internal.hxx"
Max Kellermann's avatar
Max Kellermann committed
22
#include "OutputPlugin.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 "util/Error.hxx"
28
#include "Log.hxx"
29

30 31
#include <stdexcept>

32
#include <assert.h>
33

34 35 36
/** after a failure, wait this number of seconds before
    automatically reopening the device */
static constexpr unsigned REOPEN_AFTER = 10;
37

38
struct notify audio_output_client_notify;
39

40 41
void
AudioOutput::WaitForCommand()
42
{
43 44
	while (!IsCommandFinished()) {
		mutex.unlock();
Max Kellermann's avatar
Max Kellermann committed
45
		audio_output_client_notify.Wait();
46
		mutex.lock();
47 48 49
	}
}

50
void
51
AudioOutput::CommandAsync(Command cmd)
52
{
53 54 55 56
	assert(IsCommandFinished());

	command = cmd;
	cond.signal();
57
}
58

59
void
60
AudioOutput::CommandWait(Command cmd)
61
{
62 63
	CommandAsync(cmd);
	WaitForCommand();
64 65
}

66
void
67
AudioOutput::LockCommandWait(Command cmd)
68
{
69 70
	const ScopeLock protect(mutex);
	CommandWait(cmd);
71 72
}

73
void
74
AudioOutput::LockEnableWait()
75
{
76 77
	if (!thread.IsDefined()) {
		if (plugin.enable == nullptr) {
78 79 80
			/* 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 */
81
			really_enabled = true;
82 83 84
			return;
		}

85
		StartThread();
86 87
	}

88
	LockCommandWait(Command::ENABLE);
89 90 91
}

void
92
AudioOutput::LockDisableWait()
93
{
94 95 96
	if (!thread.IsDefined()) {
		if (plugin.disable == nullptr)
			really_enabled = false;
97 98 99
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
100
			assert(!really_enabled);
101 102 103 104

		return;
	}

105
	LockCommandWait(Command::DISABLE);
106 107
}

108 109
inline bool
AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
110
{
111
	assert(allow_play);
112
	assert(audio_format.IsValid());
113

114
	fail_timer.Reset();
115

116 117
	if (open && audio_format == in_audio_format) {
		assert(pipe == &mp || (always_on && pause));
118

119
		if (pause) {
120
			current_chunk = nullptr;
121
			pipe = &mp;
122

123 124 125 126 127 128 129
			/* unpause with the CANCEL command; this is a
			   hack, but suits well for forcing the thread
			   to leave the ao_pause() thread, and we need
			   to flush the device buffer anyway */

			/* we're not using audio_output_cancel() here,
			   because that function is asynchronous */
130
			CommandWait(Command::CANCEL);
131 132
		}

133
		return true;
134 135
	}

136
	in_audio_format = audio_format;
137
	current_chunk = nullptr;
138

139
	pipe = &mp;
140

141 142
	if (!thread.IsDefined())
		StartThread();
143

144
	CommandWait(open
145 146
		    ? Command::REOPEN
		    : Command::OPEN);
147
	const bool open2 = open;
148

149
	if (open2 && mixer != nullptr) {
150 151 152 153 154
		try {
			mixer_open(mixer);
		} catch (const std::runtime_error &e) {
			FormatError(e, "Failed to open mixer for '%s'", name);
		}
155
	}
156

157
	return open2;
158 159
}

160 161
void
AudioOutput::CloseWait()
162
{
163
	assert(allow_play);
164

165 166
	if (mixer != nullptr)
		mixer_auto_close(mixer);
167

168
	assert(!open || !fail_timer.IsDefined());
169

170
	if (open)
171
		CommandWait(Command::CLOSE);
172
	else
173
		fail_timer.Reset();
174 175
}

176
bool
177 178
AudioOutput::LockUpdate(const AudioFormat audio_format,
			const MusicPipe &mp)
179
{
180
	const ScopeLock protect(mutex);
181

182
	if (enabled && really_enabled) {
183 184
		if (!fail_timer.IsDefined() ||
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
185
			return Open(audio_format, mp);
186
		}
187 188
	} else if (IsOpen())
		CloseWait();
189 190

	return false;
191 192
}

193
void
194
AudioOutput::LockPlay()
195
{
196
	const ScopeLock protect(mutex);
197

198
	assert(allow_play);
199

200 201 202
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
		cond.signal();
203
	}
204 205
}

206 207
void
AudioOutput::LockPauseAsync()
208
{
209
	if (mixer != nullptr && plugin.pause == nullptr)
210 211 212
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
213
		mixer_auto_close(mixer);
214

215
	const ScopeLock protect(mutex);
216

217 218
	assert(allow_play);
	if (IsOpen())
219
		CommandAsync(Command::PAUSE);
220 221
}

222
void
223
AudioOutput::LockDrainAsync()
224
{
225
	const ScopeLock protect(mutex);
226

227 228
	assert(allow_play);
	if (IsOpen())
229
		CommandAsync(Command::DRAIN);
230 231
}

232 233
void
AudioOutput::LockCancelAsync()
234
{
235
	const ScopeLock protect(mutex);
236

237 238
	if (IsOpen()) {
		allow_play = false;
239
		CommandAsync(Command::CANCEL);
240
	}
241 242 243
}

void
244
AudioOutput::LockAllowPlay()
245
{
246
	const ScopeLock protect(mutex);
247

248 249 250
	allow_play = true;
	if (IsOpen())
		cond.signal();
251 252
}

253
void
254
AudioOutput::LockRelease()
255
{
256 257
	if (always_on)
		LockPauseAsync();
258
	else
259
		LockCloseWait();
260 261
}

262 263
void
AudioOutput::LockCloseWait()
264
{
265
	assert(!open || !fail_timer.IsDefined());
266

267 268
	const ScopeLock protect(mutex);
	CloseWait();
269 270
}

271 272
void
AudioOutput::StopThread()
273
{
274 275
	assert(thread.IsDefined());
	assert(allow_play);
276

277
	LockCommandWait(Command::KILL);
278 279
	thread.Join();
}
280

281 282 283 284 285 286 287 288 289
void
AudioOutput::Finish()
{
	LockCloseWait();

	assert(!fail_timer.IsDefined());

	if (thread.IsDefined())
		StopThread();
290

291
	audio_output_free(this);
292
}