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