1 #include "stdafx.h"
2
3 #include <AL/al.h>
4 #include <AL/alc.h>
5
6 #include <vorbis/vorbisfile.h>
7
8 #include "audio_device.h"
9 #include "file_path.h"
10
AudioDevice()11 AudioDevice::AudioDevice() {
12 }
13
14 static bool initialized = false;
15
initialize()16 optional<string> AudioDevice::initialize() {
17 if ((device = alcOpenDevice(nullptr))) {
18 if ((context = alcCreateContext((ALCdevice*)device, nullptr))) {
19 alcMakeContextCurrent((ALCcontext*)context);
20 alDistanceModel(AL_NONE);
21 initialized = true;
22 return none;
23 }
24 }
25 return "Error code: " + toString(alGetError());
26 }
27
~AudioDevice()28 AudioDevice::~AudioDevice() {
29 if (device) {
30 RecursiveLock lock(mutex);
31 sources.clear();
32 alcMakeContextCurrent(NULL);
33 alcDestroyContext((ALCcontext*)context);
34 alcCloseDevice((ALCdevice*)device);
35 }
36 }
37
38 const int maxSources = 12;
39
40 #define AL(X) { if (initialized) {X; checkError(__FILE__, __LINE__, #X); }}
41
checkError(const char * file,int line,const char * functionName)42 static void checkError(const char* file, int line, const char* functionName) {
43 ALenum error = alGetError();
44 CHECK(!error) << file << ":" << line << " " << alGetString(error);
45 }
46
getFreeSource()47 optional<OpenalId> AudioDevice::getFreeSource() {
48 RecursiveLock lock(mutex);
49 for (int i : All(sources)) {
50 auto& source = sources[i];
51 ALint state = 0;
52 AL(alGetSourcei(source.getId(), AL_SOURCE_STATE, &state));
53 if (state == AL_STOPPED) {
54 // recreating the source does a better job at cleaning up after streaming
55 // without it the old buffer queue still existed
56 source = SoundSource();
57 return source.getId();
58 }
59 }
60 if (sources.size() < maxSources) {
61 sources.emplace_back();
62 return sources.back().getId();
63 } else
64 return none;
65 }
66
play(const SoundBuffer & sound,double volume,double pitch)67 void AudioDevice::play(const SoundBuffer& sound, double volume, double pitch) {
68 RecursiveLock lock(mutex);
69 if (auto source = getFreeSource()) {
70 ALint state = 0;
71 AL(alGetSourcei(*source, AL_SOURCE_STATE, &state));
72 CHECK(state == AL_INITIAL) << state;
73 AL(alSourcei(*source, AL_BUFFER, sound.getBufferId()));
74 AL(alSourcef(*source, AL_PITCH, pitch));
75 AL(alSourcef(*source, AL_GAIN, volume));
76 AL(alSourcePlay(*source));
77 }
78 }
79
readSoundData(OggVorbis_File & file,optional<int> length=none)80 static vector<char> readSoundData(OggVorbis_File& file, optional<int> length = none) {
81 vector<char> ret;
82 while (!length || ret.size() < *length) {
83 char tmp[4096];
84 int bit_stream = 0;
85 int lengthNow = sizeof(tmp);
86 if (length)
87 lengthNow = min<int>(*length - ret.size(), lengthNow);
88 int result = ov_read(&file, tmp, lengthNow, 0, 2, 1, &bit_stream);
89 if (result <= 0)
90 break;
91 ret.append(tmp, tmp + result);
92 }
93 return ret;
94 }
95
SoundBuffer(const FilePath & path)96 SoundBuffer::SoundBuffer(const FilePath& path) {
97 OggVorbis_File file;
98 CHECK(ov_fopen(path.getPath(), &file) == 0) << "Error opening audio file: " << path;
99 vorbis_info* info = ov_info(&file, -1);
100 ov_raw_seek(&file, 0);
101 vector<char> buffer = readSoundData(file);
102 OpenalId id;
103 AL(alGenBuffers(1, &id));
104 AL(alBufferData(id, (info->channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, buffer.data(), buffer.size(),
105 info->rate));
106 bufferId = id;
107 ov_clear(&file);
108 }
109
~SoundBuffer()110 SoundBuffer::~SoundBuffer() {
111 if (bufferId)
112 AL(alDeleteBuffers(1, &(*bufferId)));
113 }
114
SoundBuffer(SoundBuffer && o)115 SoundBuffer::SoundBuffer(SoundBuffer&& o) {
116 bufferId = o.bufferId;
117 o.bufferId = none;
118 }
119
getBufferId() const120 OpenalId SoundBuffer::getBufferId() const {
121 return *bufferId;
122 }
123
SoundSource()124 SoundSource::SoundSource() {
125 id.emplace();
126 AL(alGenSources(1, &*id));
127 }
128
SoundSource(SoundSource && o)129 SoundSource::SoundSource(SoundSource&& o) {
130 id = o.id;
131 o.id = none;
132 }
133
operator =(SoundSource && o)134 SoundSource& SoundSource::operator = (SoundSource&& o) {
135 if (id)
136 destroy();
137 id = o.id;
138 o.id = none;
139 return *this;
140 }
141
~SoundSource()142 SoundSource::~SoundSource() {
143 if (id)
144 destroy();
145 }
146
getId() const147 OpenalId SoundSource::getId() const {
148 return *id;
149 }
150
destroy()151 void SoundSource::destroy() {
152 AL(alDeleteSources(1, &*id));
153 id.reset();
154 }
155
156 const int streamingBufferSize = 1 * 2 * 2 * 44100;
157
SoundStream(const FilePath & path,double volume)158 SoundStream::SoundStream(const FilePath& path, double volume) : startedPlaying(false),
159 streamer([path, this]{ init(path);}, [volume, this]{loop(volume);}) {
160 }
161
isPlaying() const162 bool SoundStream::isPlaying() const {
163 if (!startedPlaying)
164 return true;
165 ALint state = 0;
166 AL(alGetSourcei(source.getId(), AL_SOURCE_STATE, &state));
167 return state == AL_PLAYING;
168 }
169
setVolume(double v)170 void SoundStream::setVolume(double v) {
171 alSourcef(source.getId(), AL_GAIN, v);
172 }
173
getVolume() const174 double SoundStream::getVolume() const {
175 float ret = 0;
176 AL(alGetSourcef(source.getId(), AL_GAIN, &ret));
177 return ret;
178 }
179
init(const FilePath & path)180 void SoundStream::init(const FilePath& path) {
181 CHECK(ov_fopen(path.getPath(), file.get()) == 0) << "Error opening audio file: " << path;
182 info = ov_info(file.get(), -1);
183 AL(alGenBuffers(2, buffers));
184 }
185
loop(double volume)186 void SoundStream::loop(double volume) {
187 int numQueued = 0;
188 AL(alGetSourcei(source.getId(), AL_BUFFERS_QUEUED, &numQueued));
189 if (numQueued == 0) { /*fill and queue initial buffers*/
190 vector<char> data = readSoundData(*file, streamingBufferSize);
191 AL(alBufferData(buffers[0], (info->channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data.data(),
192 data.size(), info->rate));
193 data = readSoundData(*file, streamingBufferSize);
194 AL(alBufferData(buffers[1], (info->channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data.data(),
195 data.size(), info->rate));
196 AL(alSourceQueueBuffers(source.getId(), 2, buffers));
197 AL(alSourcef(source.getId(), AL_GAIN, volume));
198 AL(alSourcePlay(source.getId()));
199 startedPlaying = true;
200 //CHECK(isPlaying()); fails if I unplug/plug the speaker cable...?
201 } else { /*refill processed buffers*/
202 int numProcessed = 0;
203 AL(alGetSourcei(source.getId(), AL_BUFFERS_PROCESSED, &numProcessed));
204 while (numProcessed--) {
205 vector<char> data = readSoundData(*file, streamingBufferSize);
206 if (data.size() == 0)
207 break;
208 else {
209 ALuint buffer = 0;
210 AL(alSourceUnqueueBuffers(source.getId(), 1, &buffer));
211 AL(alBufferData(buffer, (info->channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data.data(),
212 data.size(), info->rate));
213 AL(alSourceQueueBuffers(source.getId(), 1, &buffer));
214 }
215 }
216 }
217 sleep_for(milliseconds(10));
218 }
219
~SoundStream()220 SoundStream::~SoundStream() {
221 streamer.finishAndWait();
222 ov_clear(file.get());
223 source.destroy();
224 AL(alDeleteBuffers(2, buffers));
225 }
226
227