OpenALOutputPlugin.cxx 5.34 KB
Newer Older
Serge Ziryukin's avatar
Serge Ziryukin committed
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "OpenALOutputPlugin.hxx"
21
#include "../OutputAPI.hxx"
22
#include "util/RuntimeError.hxx"
Serge Ziryukin's avatar
Serge Ziryukin committed
23

24
#include <unistd.h>
Serge Ziryukin's avatar
Serge Ziryukin committed
25

26
#ifndef __APPLE__
Serge Ziryukin's avatar
Serge Ziryukin committed
27 28
#include <AL/al.h>
#include <AL/alc.h>
29 30 31
#else
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
32 33 34
/* on macOS, OpenAL is deprecated, but since the user asked to enable
   this plugin, let's ignore the compiler warnings */
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
35
#endif
Serge Ziryukin's avatar
Serge Ziryukin committed
36

37
class OpenALOutput final : AudioOutput {
38 39 40
	/* should be enough for buffer size = 2048 */
	static constexpr unsigned NUM_BUFFERS = 16;

Serge Ziryukin's avatar
Serge Ziryukin committed
41 42 43 44
	const char *device_name;
	ALCdevice *device;
	ALCcontext *context;
	ALuint buffers[NUM_BUFFERS];
45
	unsigned filled;
Serge Ziryukin's avatar
Serge Ziryukin committed
46 47 48
	ALuint source;
	ALenum format;
	ALuint frequency;
49

Max Kellermann's avatar
Max Kellermann committed
50
	explicit OpenALOutput(const ConfigBlock &block);
51

52 53 54 55 56
public:
	static AudioOutput *Create(EventLoop &,
				   const ConfigBlock &block) {
		return new OpenALOutput(block);
	}
57

58 59 60
private:
	void Open(AudioFormat &audio_format) override;
	void Close() noexcept override;
61

62
	[[nodiscard]] gcc_pure
63
	std::chrono::steady_clock::duration Delay() const noexcept override {
64
		return filled < NUM_BUFFERS || HasProcessed()
65
			? std::chrono::steady_clock::duration::zero()
66 67 68
			/* we don't know exactly how long we must wait
			   for the next buffer to finish, so this is a
			   random guess: */
69
			: std::chrono::milliseconds(50);
70 71
	}

72
	size_t Play(const void *chunk, size_t size) override;
73

74
	void Cancel() noexcept override;
75

76
	[[nodiscard]] gcc_pure
77
	ALint GetSourceI(ALenum param) const noexcept {
78 79 80 81 82
		ALint value;
		alGetSourcei(source, param, &value);
		return value;
	}

83
	[[nodiscard]] gcc_pure
84
	bool HasProcessed() const noexcept {
85
		return GetSourceI(AL_BUFFERS_PROCESSED) > 0;
86
	}
87

88
	[[nodiscard]] gcc_pure
89
	bool IsPlaying() const noexcept {
90 91 92
		return GetSourceI(AL_SOURCE_STATE) == AL_PLAYING;
	}

93
	/**
94
	 * Throws on error.
95 96
	 */
	void SetupContext();
Serge Ziryukin's avatar
Serge Ziryukin committed
97 98 99
};

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

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

		/* fall back to mono */
114
		audio_format.channels = 1;
115
		return openal_audio_format(audio_format);
Serge Ziryukin's avatar
Serge Ziryukin committed
116

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

124 125
inline void
OpenALOutput::SetupContext()
Serge Ziryukin's avatar
Serge Ziryukin committed
126
{
127
	device = alcOpenDevice(device_name);
128 129 130
	if (device == nullptr)
		throw FormatRuntimeError("Error opening OpenAL device \"%s\"",
					 device_name);
Serge Ziryukin's avatar
Serge Ziryukin committed
131

132 133 134
	context = alcCreateContext(device, nullptr);
	if (context == nullptr) {
		alcCloseDevice(device);
135 136
		throw FormatRuntimeError("Error creating context for \"%s\"",
					 device_name);
Serge Ziryukin's avatar
Serge Ziryukin committed
137 138 139
	}
}

140
OpenALOutput::OpenALOutput(const ConfigBlock &block)
141
	:AudioOutput(0),
142
	 device_name(block.GetBlockValue("device"))
Serge Ziryukin's avatar
Serge Ziryukin committed
143
{
144 145 146
	if (device_name == nullptr)
		device_name = alcGetString(nullptr,
					   ALC_DEFAULT_DEVICE_SPECIFIER);
Serge Ziryukin's avatar
Serge Ziryukin committed
147 148
}

149
void
150
OpenALOutput::Open(AudioFormat &audio_format)
Serge Ziryukin's avatar
Serge Ziryukin committed
151
{
152
	format = openal_audio_format(audio_format);
Serge Ziryukin's avatar
Serge Ziryukin committed
153

154
	SetupContext();
Serge Ziryukin's avatar
Serge Ziryukin committed
155

156 157
	alcMakeContextCurrent(context);
	alGenBuffers(NUM_BUFFERS, buffers);
Serge Ziryukin's avatar
Serge Ziryukin committed
158

159 160
	if (alGetError() != AL_NO_ERROR)
		throw std::runtime_error("Failed to generate buffers");
Serge Ziryukin's avatar
Serge Ziryukin committed
161

162
	alGenSources(1, &source);
Serge Ziryukin's avatar
Serge Ziryukin committed
163 164

	if (alGetError() != AL_NO_ERROR) {
165
		alDeleteBuffers(NUM_BUFFERS, buffers);
166
		throw std::runtime_error("Failed to generate source");
Serge Ziryukin's avatar
Serge Ziryukin committed
167 168
	}

169 170
	filled = 0;
	frequency = audio_format.sample_rate;
Serge Ziryukin's avatar
Serge Ziryukin committed
171 172
}

173 174
void
OpenALOutput::Close() noexcept
Serge Ziryukin's avatar
Serge Ziryukin committed
175
{
176 177 178 179 180
	alcMakeContextCurrent(context);
	alDeleteSources(1, &source);
	alDeleteBuffers(NUM_BUFFERS, buffers);
	alcDestroyContext(context);
	alcCloseDevice(device);
Serge Ziryukin's avatar
Serge Ziryukin committed
181 182
}

183
size_t
184
OpenALOutput::Play(const void *chunk, size_t size)
185
{
186 187
	if (alcGetCurrentContext() != context)
		alcMakeContextCurrent(context);
188

Serge Ziryukin's avatar
Serge Ziryukin committed
189
	ALuint buffer;
190
	if (filled < NUM_BUFFERS) {
Serge Ziryukin's avatar
Serge Ziryukin committed
191
		/* fill all buffers */
192 193
		buffer = buffers[filled];
		filled++;
Serge Ziryukin's avatar
Serge Ziryukin committed
194 195
	} else {
		/* wait for processed buffer */
196
		while (!HasProcessed())
197
			usleep(10);
Serge Ziryukin's avatar
Serge Ziryukin committed
198

199
		alSourceUnqueueBuffers(source, 1, &buffer);
Serge Ziryukin's avatar
Serge Ziryukin committed
200 201
	}

202 203
	alBufferData(buffer, format, chunk, size, frequency);
	alSourceQueueBuffers(source, 1, &buffer);
Serge Ziryukin's avatar
Serge Ziryukin committed
204

205 206
	if (!IsPlaying())
		alSourcePlay(source);
Serge Ziryukin's avatar
Serge Ziryukin committed
207 208 209 210

	return size;
}

211 212
void
OpenALOutput::Cancel() noexcept
Serge Ziryukin's avatar
Serge Ziryukin committed
213
{
214 215 216
	filled = 0;
	alcMakeContextCurrent(context);
	alSourceStop(source);
217 218

	/* force-unqueue all buffers */
219 220
	alSourcei(source, AL_BUFFER, 0);
	filled = 0;
Serge Ziryukin's avatar
Serge Ziryukin committed
221 222
}

223
const struct AudioOutputPlugin openal_output_plugin = {
224 225
	"openal",
	nullptr,
226
	OpenALOutput::Create,
227
	nullptr,
Serge Ziryukin's avatar
Serge Ziryukin committed
228
};