OutputControl.cxx 5.53 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 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
#include <assert.h>
31

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

36
struct notify audio_output_client_notify;
37

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

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

	command = cmd;
	cond.signal();
55
}
56

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

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

71
void
72
AudioOutput::SetReplayGainMode(ReplayGainMode mode)
73
{
74 75 76 77
	if (replay_gain_filter != nullptr)
		replay_gain_filter_set_mode(replay_gain_filter, mode);
	if (other_replay_gain_filter != nullptr)
		replay_gain_filter_set_mode(other_replay_gain_filter, mode);
78 79
}

80
void
81
AudioOutput::LockEnableWait()
82
{
83 84
	if (!thread.IsDefined()) {
		if (plugin.enable == nullptr) {
85 86 87
			/* 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 */
88
			really_enabled = true;
89 90 91
			return;
		}

92
		StartThread();
93 94
	}

95
	LockCommandWait(Command::ENABLE);
96 97 98
}

void
99
AudioOutput::LockDisableWait()
100
{
101 102 103
	if (!thread.IsDefined()) {
		if (plugin.disable == nullptr)
			really_enabled = false;
104 105 106
		else
			/* if there's no thread yet, the device cannot
			   be enabled */
107
			assert(!really_enabled);
108 109 110 111

		return;
	}

112
	LockCommandWait(Command::DISABLE);
113 114
}

115 116
inline bool
AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
117
{
118
	assert(allow_play);
119
	assert(audio_format.IsValid());
120

121
	fail_timer.Reset();
122

123 124
	if (open && audio_format == in_audio_format) {
		assert(pipe == &mp || (always_on && pause));
125

126
		if (pause) {
127
			current_chunk = nullptr;
128
			pipe = &mp;
129

130 131 132 133 134 135 136
			/* 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 */
137
			CommandWait(Command::CANCEL);
138 139
		}

140
		return true;
141 142
	}

143
	in_audio_format = audio_format;
144
	current_chunk = nullptr;
145

146
	pipe = &mp;
147

148 149
	if (!thread.IsDefined())
		StartThread();
150

151
	CommandWait(open
152 153
		    ? Command::REOPEN
		    : Command::OPEN);
154
	const bool open2 = open;
155

156
	if (open2 && mixer != nullptr) {
157
		Error error;
158
		if (!mixer_open(mixer, error))
159
			FormatWarning(output_domain,
160
				      "Failed to open mixer for '%s'", name);
161
	}
162

163
	return open2;
164 165
}

166 167
void
AudioOutput::CloseWait()
168
{
169
	assert(allow_play);
170

171 172
	if (mixer != nullptr)
		mixer_auto_close(mixer);
173

174
	assert(!open || !fail_timer.IsDefined());
175

176
	if (open)
177
		CommandWait(Command::CLOSE);
178
	else
179
		fail_timer.Reset();
180 181
}

182
bool
183 184
AudioOutput::LockUpdate(const AudioFormat audio_format,
			const MusicPipe &mp)
185
{
186
	const ScopeLock protect(mutex);
187

188 189 190
	if (enabled && really_enabled) {
		if (fail_timer.Check(REOPEN_AFTER * 1000)) {
			return Open(audio_format, mp);
191
		}
192 193
	} else if (IsOpen())
		CloseWait();
194 195

	return false;
196 197
}

198
void
199
AudioOutput::LockPlay()
200
{
201
	const ScopeLock protect(mutex);
202

203
	assert(allow_play);
204

205 206 207
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
		cond.signal();
208
	}
209 210
}

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

220
	const ScopeLock protect(mutex);
221

222 223
	assert(allow_play);
	if (IsOpen())
224
		CommandAsync(Command::PAUSE);
225 226
}

227
void
228
AudioOutput::LockDrainAsync()
229
{
230
	const ScopeLock protect(mutex);
231

232 233
	assert(allow_play);
	if (IsOpen())
234
		CommandAsync(Command::DRAIN);
235 236
}

237 238
void
AudioOutput::LockCancelAsync()
239
{
240
	const ScopeLock protect(mutex);
241

242 243
	if (IsOpen()) {
		allow_play = false;
244
		CommandAsync(Command::CANCEL);
245
	}
246 247 248
}

void
249
AudioOutput::LockAllowPlay()
250
{
251
	const ScopeLock protect(mutex);
252

253 254 255
	allow_play = true;
	if (IsOpen())
		cond.signal();
256 257
}

258
void
259
AudioOutput::LockRelease()
260
{
261 262
	if (always_on)
		LockPauseAsync();
263
	else
264
		LockCloseWait();
265 266
}

267 268
void
AudioOutput::LockCloseWait()
269
{
270
	assert(!open || !fail_timer.IsDefined());
271

272 273
	const ScopeLock protect(mutex);
	CloseWait();
274 275
}

276 277
void
AudioOutput::StopThread()
278
{
279 280
	assert(thread.IsDefined());
	assert(allow_play);
281

282
	LockCommandWait(Command::KILL);
283 284
	thread.Join();
}
285

286 287 288 289 290 291 292 293 294
void
AudioOutput::Finish()
{
	LockCloseWait();

	assert(!fail_timer.IsDefined());

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

296
	audio_output_free(this);
297
}