OutputControl.cxx 5.58 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 49
void
AudioOutput::CommandAsync(audio_output_command cmd)
50
{
51 52 53 54
	assert(IsCommandFinished());

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

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

64 65
void
AudioOutput::LockCommandWait(audio_output_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(AO_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(AO_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(AO_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 152
	CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
	const bool open2 = open;
153

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

161
	return open2;
162 163
}

164 165
void
AudioOutput::CloseWait()
166
{
167
	assert(allow_play);
168

169 170
	if (mixer != nullptr)
		mixer_auto_close(mixer);
171

172
	assert(!open || !fail_timer.IsDefined());
173

174 175
	if (open)
		CommandWait(AO_COMMAND_CLOSE);
176
	else
177
		fail_timer.Reset();
178 179
}

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

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

	return false;
194 195
}

196
void
197
AudioOutput::LockPlay()
198
{
199
	const ScopeLock protect(mutex);
200

201
	assert(allow_play);
202

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

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

218
	const ScopeLock protect(mutex);
219

220 221 222
	assert(allow_play);
	if (IsOpen())
		CommandAsync(AO_COMMAND_PAUSE);
223 224
}

225
void
226
AudioOutput::LockDrainAsync()
227
{
228
	const ScopeLock protect(mutex);
229

230 231 232
	assert(allow_play);
	if (IsOpen())
		CommandAsync(AO_COMMAND_DRAIN);
233 234
}

235 236
void
AudioOutput::LockCancelAsync()
237
{
238
	const ScopeLock protect(mutex);
239

240 241 242
	if (IsOpen()) {
		allow_play = false;
		CommandAsync(AO_COMMAND_CANCEL);
243
	}
244 245 246
}

void
247
AudioOutput::LockAllowPlay()
248
{
249
	const ScopeLock protect(mutex);
250

251 252 253
	allow_play = true;
	if (IsOpen())
		cond.signal();
254 255
}

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

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

270 271
	const ScopeLock protect(mutex);
	CloseWait();
272 273
}

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

280 281 282
	LockCommandWait(AO_COMMAND_KILL);
	thread.Join();
}
283

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

	assert(!fail_timer.IsDefined());

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

294
	audio_output_free(this);
295
}