OutputControl.cxx 5.28 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "Log.hxx"
28

29 30
#include <stdexcept>

31
#include <assert.h>
32

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

37
struct notify audio_output_client_notify;
38

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

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

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

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

65
void
66
AudioOutput::LockCommandWait(Command cmd)
67
{
68
	const std::lock_guard<Mutex> protect(mutex);
69
	CommandWait(cmd);
70 71
}

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

84
		StartThread();
85 86
	}

87
	CommandAsync(Command::ENABLE);
88 89 90
}

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

		return;
	}

104
	CommandAsync(Command::DISABLE);
105 106
}

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

113
	fail_timer.Reset();
114

115
	if (open && audio_format == request.audio_format) {
116
		assert(request.pipe == &mp || (always_on && pause));
117

118 119 120 121
		if (!pause)
			/* already open, already the right parameters
			   - nothing needs to be done */
			return true;
122 123
	}

124
	request.audio_format = audio_format;
125
	request.pipe = &mp;
126

127 128
	if (!thread.IsDefined())
		StartThread();
129

130
	CommandWait(Command::OPEN);
131
	const bool open2 = open;
132

133
	if (open2 && mixer != nullptr) {
134 135 136 137 138
		try {
			mixer_open(mixer);
		} catch (const std::runtime_error &e) {
			FormatError(e, "Failed to open mixer for '%s'", name);
		}
139
	}
140

141
	return open2;
142 143
}

144 145
void
AudioOutput::CloseWait()
146
{
147
	assert(allow_play);
148

149 150
	if (mixer != nullptr)
		mixer_auto_close(mixer);
151

152
	assert(!open || !fail_timer.IsDefined());
153

154
	if (open)
155
		CommandWait(Command::CLOSE);
156
	else
157
		fail_timer.Reset();
158 159
}

160
bool
161
AudioOutput::LockUpdate(const AudioFormat audio_format,
162 163
			const MusicPipe &mp,
			bool force)
164
{
165
	const std::lock_guard<Mutex> protect(mutex);
166

167
	if (enabled && really_enabled) {
168
		if (force || !fail_timer.IsDefined() ||
169
		    fail_timer.Check(REOPEN_AFTER * 1000)) {
170
			return Open(audio_format, mp);
171
		}
172 173
	} else if (IsOpen())
		CloseWait();
174 175

	return false;
176 177
}

178
void
179
AudioOutput::LockPlay()
180
{
181
	const std::lock_guard<Mutex> protect(mutex);
182

183
	assert(allow_play);
184

185 186 187
	if (IsOpen() && !in_playback_loop && !woken_for_play) {
		woken_for_play = true;
		cond.signal();
188
	}
189 190
}

191 192
void
AudioOutput::LockPauseAsync()
193
{
194
	if (mixer != nullptr && plugin.pause == nullptr)
195 196 197
		/* the device has no pause mode: close the mixer,
		   unless its "global" flag is set (checked by
		   mixer_auto_close()) */
198
		mixer_auto_close(mixer);
199

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

202 203
	assert(allow_play);
	if (IsOpen())
204
		CommandAsync(Command::PAUSE);
205 206
}

207
void
208
AudioOutput::LockDrainAsync()
209
{
210
	const std::lock_guard<Mutex> protect(mutex);
211

212 213
	assert(allow_play);
	if (IsOpen())
214
		CommandAsync(Command::DRAIN);
215 216
}

217 218
void
AudioOutput::LockCancelAsync()
219
{
220
	const std::lock_guard<Mutex> protect(mutex);
221

222 223
	if (IsOpen()) {
		allow_play = false;
224
		CommandAsync(Command::CANCEL);
225
	}
226 227 228
}

void
229
AudioOutput::LockAllowPlay()
230
{
231
	const std::lock_guard<Mutex> protect(mutex);
232

233 234 235
	allow_play = true;
	if (IsOpen())
		cond.signal();
236 237
}

238
void
239
AudioOutput::LockRelease()
240
{
241 242
	if (always_on)
		LockPauseAsync();
243
	else
244
		LockCloseWait();
245 246
}

247 248
void
AudioOutput::LockCloseWait()
249
{
250
	assert(!open || !fail_timer.IsDefined());
251

252
	const std::lock_guard<Mutex> protect(mutex);
253
	CloseWait();
254 255
}

256 257
void
AudioOutput::StopThread()
258
{
259 260
	assert(thread.IsDefined());
	assert(allow_play);
261

262
	LockCommandWait(Command::KILL);
263 264
	thread.Join();
}
265

266
void
267
AudioOutput::BeginDestroy()
268
{
269 270
	if (mixer != nullptr)
		mixer_auto_close(mixer);
271

272
	if (thread.IsDefined()) {
273
		const std::lock_guard<Mutex> protect(mutex);
274 275 276 277 278 279 280
		CommandAsync(Command::KILL);
	}
}

void
AudioOutput::FinishDestroy()
{
281
	if (thread.IsDefined())
282
		thread.Join();
283

284
	audio_output_free(this);
285
}