OpenALOutputPlugin.cxx 5.83 KB
Newer Older
Serge Ziryukin's avatar
Serge Ziryukin committed
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
Serge Ziryukin's avatar
Serge Ziryukin committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * http://www.musicpd.org
 *
 * 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.
 *
 * 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.
 */

20
#include "config.h"
21
#include "OpenALOutputPlugin.hxx"
22
#include "../OutputAPI.hxx"
23
#include "../Wrapper.hxx"
24 25
#include "util/Error.hxx"
#include "util/Domain.hxx"
Serge Ziryukin's avatar
Serge Ziryukin committed
26

27
#include <unistd.h>
Serge Ziryukin's avatar
Serge Ziryukin committed
28

29
#ifndef __APPLE__
Serge Ziryukin's avatar
Serge Ziryukin committed
30 31
#include <AL/al.h>
#include <AL/alc.h>
32 33 34 35
#else
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#endif
Serge Ziryukin's avatar
Serge Ziryukin committed
36

37
class OpenALOutput {
38 39
	friend struct AudioOutputWrapper<OpenALOutput>;

40 41 42
	/* should be enough for buffer size = 2048 */
	static constexpr unsigned NUM_BUFFERS = 16;

43
	AudioOutput base;
44

Serge Ziryukin's avatar
Serge Ziryukin committed
45 46 47 48
	const char *device_name;
	ALCdevice *device;
	ALCcontext *context;
	ALuint buffers[NUM_BUFFERS];
49
	unsigned filled;
Serge Ziryukin's avatar
Serge Ziryukin committed
50 51 52
	ALuint source;
	ALenum format;
	ALuint frequency;
53

54 55 56
	OpenALOutput()
		:base(openal_output_plugin) {}

57
	bool Configure(const ConfigBlock &block, Error &error);
58

59
	static OpenALOutput *Create(const ConfigBlock &block, Error &error);
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

	bool Open(AudioFormat &audio_format, Error &error);

	void Close();

	gcc_pure
	unsigned Delay() const {
		return filled < NUM_BUFFERS || HasProcessed()
			? 0
			/* we don't know exactly how long we must wait
			   for the next buffer to finish, so this is a
			   random guess: */
			: 50;
	}

	size_t Play(const void *chunk, size_t size, Error &error);

	void Cancel();

private:
	gcc_pure
	ALint GetSourceI(ALenum param) const {
		ALint value;
		alGetSourcei(source, param, &value);
		return value;
	}

	gcc_pure
	bool HasProcessed() const {
		return GetSourceI(AL_BUFFERS_PROCESSED) > 0;
90
	}
91 92 93 94 95 96 97

	gcc_pure
	bool IsPlaying() const {
		return GetSourceI(AL_SOURCE_STATE) == AL_PLAYING;
	}

	bool SetupContext(Error &error);
Serge Ziryukin's avatar
Serge Ziryukin committed
98 99
};

100
static constexpr Domain openal_output_domain("openal_output");
Serge Ziryukin's avatar
Serge Ziryukin committed
101 102

static ALenum
103
openal_audio_format(AudioFormat &audio_format)
Serge Ziryukin's avatar
Serge Ziryukin committed
104
{
105
	/* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
106 107 108
	   AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
	   samples, while MPD uses signed samples */

109 110 111
	switch (audio_format.format) {
	case SampleFormat::S16:
		if (audio_format.channels == 2)
Serge Ziryukin's avatar
Serge Ziryukin committed
112
			return AL_FORMAT_STEREO16;
113
		if (audio_format.channels == 1)
Serge Ziryukin's avatar
Serge Ziryukin committed
114
			return AL_FORMAT_MONO16;
115 116

		/* fall back to mono */
117
		audio_format.channels = 1;
118
		return openal_audio_format(audio_format);
Serge Ziryukin's avatar
Serge Ziryukin committed
119

120 121
	default:
		/* fall back to 16 bit */
122
		audio_format.format = SampleFormat::S16;
123
		return openal_audio_format(audio_format);
Serge Ziryukin's avatar
Serge Ziryukin committed
124 125 126
	}
}

127 128
inline bool
OpenALOutput::SetupContext(Error &error)
Serge Ziryukin's avatar
Serge Ziryukin committed
129
{
130
	device = alcOpenDevice(device_name);
Serge Ziryukin's avatar
Serge Ziryukin committed
131

132
	if (device == nullptr) {
133 134
		error.Format(openal_output_domain,
			     "Error opening OpenAL device \"%s\"",
135
			     device_name);
Serge Ziryukin's avatar
Serge Ziryukin committed
136 137 138
		return false;
	}

139
	context = alcCreateContext(device, nullptr);
Serge Ziryukin's avatar
Serge Ziryukin committed
140

141
	if (context == nullptr) {
142 143
		error.Format(openal_output_domain,
			     "Error creating context for \"%s\"",
144 145
			     device_name);
		alcCloseDevice(device);
Serge Ziryukin's avatar
Serge Ziryukin committed
146 147 148 149 150 151
		return false;
	}

	return true;
}

152
inline bool
153
OpenALOutput::Configure(const ConfigBlock &block, Error &error)
Serge Ziryukin's avatar
Serge Ziryukin committed
154
{
155
	if (!base.Configure(block, error))
156
		return false;
157

158
	device_name = block.GetBlockValue("device");
159 160 161
	if (device_name == nullptr)
		device_name = alcGetString(nullptr,
					   ALC_DEFAULT_DEVICE_SPECIFIER);
162

163
	return true;
Serge Ziryukin's avatar
Serge Ziryukin committed
164 165
}

166
inline OpenALOutput *
167
OpenALOutput::Create(const ConfigBlock &block, Error &error)
Serge Ziryukin's avatar
Serge Ziryukin committed
168
{
169 170
	OpenALOutput *oo = new OpenALOutput();

171
	if (!oo->Configure(block, error)) {
172 173 174
		delete oo;
		return nullptr;
	}
Serge Ziryukin's avatar
Serge Ziryukin committed
175

176
	return oo;
Serge Ziryukin's avatar
Serge Ziryukin committed
177 178
}

179 180
inline bool
OpenALOutput::Open(AudioFormat &audio_format, Error &error)
Serge Ziryukin's avatar
Serge Ziryukin committed
181
{
182
	format = openal_audio_format(audio_format);
Serge Ziryukin's avatar
Serge Ziryukin committed
183

184
	if (!SetupContext(error))
Serge Ziryukin's avatar
Serge Ziryukin committed
185 186
		return false;

187 188
	alcMakeContextCurrent(context);
	alGenBuffers(NUM_BUFFERS, buffers);
Serge Ziryukin's avatar
Serge Ziryukin committed
189 190

	if (alGetError() != AL_NO_ERROR) {
191
		error.Set(openal_output_domain, "Failed to generate buffers");
Serge Ziryukin's avatar
Serge Ziryukin committed
192 193 194
		return false;
	}

195
	alGenSources(1, &source);
Serge Ziryukin's avatar
Serge Ziryukin committed
196 197

	if (alGetError() != AL_NO_ERROR) {
198
		error.Set(openal_output_domain, "Failed to generate source");
199
		alDeleteBuffers(NUM_BUFFERS, buffers);
Serge Ziryukin's avatar
Serge Ziryukin committed
200 201 202
		return false;
	}

203 204
	filled = 0;
	frequency = audio_format.sample_rate;
Serge Ziryukin's avatar
Serge Ziryukin committed
205 206 207 208

	return true;
}

209 210
inline void
OpenALOutput::Close()
Serge Ziryukin's avatar
Serge Ziryukin committed
211
{
212 213 214 215 216
	alcMakeContextCurrent(context);
	alDeleteSources(1, &source);
	alDeleteBuffers(NUM_BUFFERS, buffers);
	alcDestroyContext(context);
	alcCloseDevice(device);
Serge Ziryukin's avatar
Serge Ziryukin committed
217 218
}

219 220
inline size_t
OpenALOutput::Play(const void *chunk, size_t size, gcc_unused Error &error)
221
{
222 223
	if (alcGetCurrentContext() != context)
		alcMakeContextCurrent(context);
224

Serge Ziryukin's avatar
Serge Ziryukin committed
225
	ALuint buffer;
226
	if (filled < NUM_BUFFERS) {
Serge Ziryukin's avatar
Serge Ziryukin committed
227
		/* fill all buffers */
228 229
		buffer = buffers[filled];
		filled++;
Serge Ziryukin's avatar
Serge Ziryukin committed
230 231
	} else {
		/* wait for processed buffer */
232
		while (!HasProcessed())
233
			usleep(10);
Serge Ziryukin's avatar
Serge Ziryukin committed
234

235
		alSourceUnqueueBuffers(source, 1, &buffer);
Serge Ziryukin's avatar
Serge Ziryukin committed
236 237
	}

238 239
	alBufferData(buffer, format, chunk, size, frequency);
	alSourceQueueBuffers(source, 1, &buffer);
Serge Ziryukin's avatar
Serge Ziryukin committed
240

241 242
	if (!IsPlaying())
		alSourcePlay(source);
Serge Ziryukin's avatar
Serge Ziryukin committed
243 244 245 246

	return size;
}

247 248
inline void
OpenALOutput::Cancel()
Serge Ziryukin's avatar
Serge Ziryukin committed
249
{
250 251 252
	filled = 0;
	alcMakeContextCurrent(context);
	alSourceStop(source);
253 254

	/* force-unqueue all buffers */
255 256
	alSourcei(source, AL_BUFFER, 0);
	filled = 0;
Serge Ziryukin's avatar
Serge Ziryukin committed
257 258
}

259 260
typedef AudioOutputWrapper<OpenALOutput> Wrapper;

261
const struct AudioOutputPlugin openal_output_plugin = {
262 263
	"openal",
	nullptr,
264 265
	&Wrapper::Init,
	&Wrapper::Finish,
266 267
	nullptr,
	nullptr,
268 269 270
	&Wrapper::Open,
	&Wrapper::Close,
	&Wrapper::Delay,
271
	nullptr,
272
	&Wrapper::Play,
273
	nullptr,
274
	&Wrapper::Cancel,
275 276
	nullptr,
	nullptr,
Serge Ziryukin's avatar
Serge Ziryukin committed
277
};