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, &section);
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