1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "OggStream.h"
4
5 #include <string.h> //memset
6 #include "System/FileSystem/FileHandler.h"
7 #include "System/Sound/SoundLog.h"
8 #include "ALShared.h"
9 #include "VorbisShared.h"
10
11
12 namespace VorbisCallbacks {
VorbisStreamRead(void * ptr,size_t size,size_t nmemb,void * datasource)13 size_t VorbisStreamRead(void* ptr, size_t size, size_t nmemb, void* datasource)
14 {
15 CFileHandler* buffer = static_cast<CFileHandler*>(datasource);
16 return buffer->Read(ptr, size * nmemb);
17 }
18
VorbisStreamClose(void * datasource)19 int VorbisStreamClose(void* datasource)
20 {
21 CFileHandler* buffer = static_cast<CFileHandler*>(datasource);
22 delete buffer;
23 return 0;
24 }
25
VorbisStreamSeek(void * datasource,ogg_int64_t offset,int whence)26 int VorbisStreamSeek(void* datasource, ogg_int64_t offset, int whence)
27 {
28 CFileHandler* buffer = static_cast<CFileHandler*>(datasource);
29 if (whence == SEEK_SET)
30 {
31 buffer->Seek(offset, std::ios_base::beg);
32 }
33 else if (whence == SEEK_CUR)
34 {
35 buffer->Seek(offset, std::ios_base::cur);
36 }
37 else if (whence == SEEK_END)
38 {
39 buffer->Seek(offset, std::ios_base::end);
40 }
41
42 return 0;
43 }
44
VorbisStreamTell(void * datasource)45 long VorbisStreamTell(void* datasource)
46 {
47 CFileHandler* buffer = static_cast<CFileHandler*>(datasource);
48 return buffer->GetPos();
49 }
50
51 }
52
53
54
COggStream(ALuint _source)55 COggStream::COggStream(ALuint _source)
56 : vorbisInfo(NULL)
57 , source(_source)
58 , format(AL_FORMAT_MONO16)
59 , stopped(true)
60 , paused(false)
61 {
62 for (unsigned i = 0; i < NUM_BUFFERS; ++i) {
63 buffers[i] = 0;
64 }
65 for (unsigned i = 0; i < BUFFER_SIZE; ++i) {
66 pcmDecodeBuffer[i] = 0;
67 }
68 }
69
~COggStream()70 COggStream::~COggStream()
71 {
72 Stop();
73 }
74
75 // open an Ogg stream from a given file and start playing it
Play(const std::string & path,float volume)76 void COggStream::Play(const std::string& path, float volume)
77 {
78 if (!stopped) {
79 // we're already playing another stream
80 return;
81 }
82
83 vorbisTags.clear();
84
85 ov_callbacks vorbisCallbacks;
86 vorbisCallbacks.read_func = VorbisCallbacks::VorbisStreamRead;
87 vorbisCallbacks.close_func = VorbisCallbacks::VorbisStreamClose;
88 vorbisCallbacks.seek_func = VorbisCallbacks::VorbisStreamSeek;
89 vorbisCallbacks.tell_func = VorbisCallbacks::VorbisStreamTell;
90
91 CFileHandler* buf = new CFileHandler(path);
92 const int result = ov_open_callbacks(buf, &oggStream, NULL, 0, vorbisCallbacks);
93 if (result < 0) {
94 LOG_L(L_WARNING, "Could not open Ogg stream (reason: %s).",
95 ErrorString(result).c_str());
96 return;
97 }
98
99
100 vorbisInfo = ov_info(&oggStream, -1);
101 {
102 vorbis_comment* vorbisComment;
103 vorbisComment = ov_comment(&oggStream, -1);
104 vorbisTags.resize(vorbisComment->comments);
105
106 for (unsigned i = 0; i < vorbisComment->comments; ++i) {
107 vorbisTags[i] = std::string(vorbisComment->user_comments[i], vorbisComment->comment_lengths[i]);
108 }
109
110 vendor = std::string(vorbisComment->vendor);
111 // DisplayInfo();
112 }
113
114 if (vorbisInfo->channels == 1) {
115 format = AL_FORMAT_MONO16;
116 } else {
117 format = AL_FORMAT_STEREO16;
118 }
119
120 alGenBuffers(2, buffers); CheckError("COggStream::Play");
121
122 if (!StartPlaying()) {
123 ReleaseBuffers();
124 } else {
125 stopped = false;
126 paused = false;
127 }
128
129 CheckError("COggStream::Play");
130 }
131
GetPlayTime() const132 float COggStream::GetPlayTime() const
133 {
134 return msecsPlayed.toSecsf();
135 }
136
GetTotalTime()137 float COggStream::GetTotalTime()
138 {
139 return ov_time_total(&oggStream, -1);
140 }
141
Valid() const142 bool COggStream::Valid() const
143 {
144 return (vorbisInfo != 0);
145 }
146
IsFinished()147 bool COggStream::IsFinished()
148 {
149 return !Valid() || (GetPlayTime() >= GetTotalTime());
150 }
151
VorbisTags() const152 const COggStream::TagVector& COggStream::VorbisTags() const
153 {
154 return vorbisTags;
155 }
156
157 // display Ogg info and comments
DisplayInfo()158 void COggStream::DisplayInfo()
159 {
160 LOG("version: %d", vorbisInfo->version);
161 LOG("channels: %d", vorbisInfo->channels);
162 LOG("time (sec): %lf", ov_time_total(&oggStream,-1));
163 LOG("rate (Hz): %ld", vorbisInfo->rate);
164 LOG("bitrate (upper): %ld", vorbisInfo->bitrate_upper);
165 LOG("bitrate (nominal): %ld", vorbisInfo->bitrate_nominal);
166 LOG("bitrate (lower): %ld", vorbisInfo->bitrate_lower);
167 LOG("bitrate (window): %ld", vorbisInfo->bitrate_window);
168 LOG("vendor: %s", vendor.c_str());
169
170 for (TagVector::const_iterator it = vorbisTags.begin(); it != vorbisTags.end(); ++it) {
171 LOG("%s", it->c_str());
172 }
173 }
174
175
176 // clean up the OpenAL resources
ReleaseBuffers()177 void COggStream::ReleaseBuffers()
178 {
179 stopped = true;
180 paused = false;
181
182 EmptyBuffers();
183
184 alDeleteBuffers(2, buffers);
185 CheckError("COggStream::ReleaseBuffers");
186
187 ov_clear(&oggStream);
188 }
189
190
191 // returns true if both buffers were
192 // filled with data from the stream
StartPlaying()193 bool COggStream::StartPlaying()
194 {
195 msecsPlayed = spring_nulltime;
196 lastTick = spring_gettime();
197
198 if (!DecodeStream(buffers[0])) { return false; }
199 if (!DecodeStream(buffers[1])) { return false; }
200
201 alSourceQueueBuffers(source, 2, buffers); CheckError("COggStream::StartPlaying");
202 alSourcePlay(source); CheckError("COggStream::StartPlaying");
203
204 return true;
205 }
206
207
208 // returns true if we're still playing
IsPlaying()209 bool COggStream::IsPlaying()
210 {
211 ALenum state = 0;
212 alGetSourcei(source, AL_SOURCE_STATE, &state);
213
214 return (state == AL_PLAYING);
215 }
216
217 // stops the currently playing stream
Stop()218 void COggStream::Stop()
219 {
220 if (stopped) {
221 return;
222 }
223
224 ReleaseBuffers();
225 msecsPlayed = spring_nulltime;
226 vorbisInfo = NULL;
227 lastTick = spring_gettime();
228 }
229
TogglePause()230 bool COggStream::TogglePause()
231 {
232 if (!stopped) {
233 paused = !paused;
234 }
235
236 return paused;
237 }
238
239
240 // pop the processed buffers from the queue,
241 // refill them, and push them back in line
UpdateBuffers()242 bool COggStream::UpdateBuffers()
243 {
244 int buffersProcessed = 0;
245 bool active = true;
246
247 alGetSourcei(source, AL_BUFFERS_PROCESSED, &buffersProcessed);
248
249 while (buffersProcessed-- > 0) {
250 ALuint buffer;
251 alSourceUnqueueBuffers(source, 1, &buffer); CheckError("COggStream::UpdateBuffers");
252
253 // false if we've reached end of stream
254 active = DecodeStream(buffer);
255 if (active)
256 alSourceQueueBuffers(source, 1, &buffer); CheckError("COggStream::UpdateBuffers");
257 }
258 CheckError("COggStream::UpdateBuffers");
259
260 return active;
261 }
262
263
Update()264 void COggStream::Update()
265 {
266 if (stopped) {
267 return;
268 }
269
270 spring_time tick = spring_gettime();
271
272 if (!paused) {
273 UpdateBuffers();
274
275 if (!IsPlaying()) {
276 ReleaseBuffers();
277 }
278
279 msecsPlayed += (tick - lastTick);
280 }
281
282 lastTick = tick;
283 }
284
285
286 // read decoded data from audio stream into PCM buffer
DecodeStream(ALuint buffer)287 bool COggStream::DecodeStream(ALuint buffer)
288 {
289 memset(pcmDecodeBuffer, 0, BUFFER_SIZE);
290
291 int size = 0;
292 int section = 0;
293 int result = 0;
294
295 while (size < BUFFER_SIZE) {
296 result = ov_read(&oggStream, pcmDecodeBuffer + size, BUFFER_SIZE - size, 0, 2, 1, §ion);
297
298 if (result > 0) {
299 size += result;
300 } else {
301 if (result < 0) {
302 LOG_L(L_WARNING, "Error reading Ogg stream (%s)",
303 ErrorString(result).c_str());
304 } else {
305 break;
306 }
307 }
308 }
309
310 if (size == 0) {
311 return false;
312 }
313
314 alBufferData(buffer, format, pcmDecodeBuffer, size, vorbisInfo->rate);
315 CheckError("COggStream::DecodeStream");
316
317 return true;
318 }
319
320
321 // dequeue any buffers pending on source
EmptyBuffers()322 void COggStream::EmptyBuffers()
323 {
324 int queuedBuffers = 0;
325 alGetSourcei(source, AL_BUFFERS_QUEUED, &queuedBuffers); CheckError("COggStream::EmptyBuffers");
326
327 while (queuedBuffers-- > 0) {
328 ALuint buffer;
329 alSourceUnqueueBuffers(source, 1, &buffer); CheckError("COggStream::EmptyBuffers");
330 }
331 }
332