Some documentation cleanups
[alure.git] / src / streamplay.cpp
blobbcf5b8addf41b991b9a78c648e8511b265aaa179
1 /*
2 * ALURE OpenAL utility library
3 * Copyright (C) 2009 by Chris Robinson.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 /* Title: Asynchronous Playback */
23 #include "config.h"
25 #include "main.h"
27 #include <list>
28 #include <vector>
30 #ifdef HAVE_WINDOWS_H
32 typedef struct {
33 ALuint (*func)(ALvoid*);
34 ALvoid *ptr;
35 HANDLE thread;
36 } ThreadInfo;
38 static DWORD CALLBACK StarterFunc(void *ptr)
40 ThreadInfo *inf = (ThreadInfo*)ptr;
41 ALint ret;
43 ret = inf->func(inf->ptr);
44 ExitThread((DWORD)ret);
46 return (DWORD)ret;
49 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
51 DWORD dummy;
52 ThreadInfo *inf = new ThreadInfo;
53 inf->func = func;
54 inf->ptr = ptr;
56 inf->thread = CreateThread(NULL, 0, StarterFunc, inf, 0, &dummy);
57 if(!inf->thread)
59 delete inf;
60 return NULL;
63 return inf;
66 static ALuint StopThread(ThreadInfo *inf)
68 CloseHandle(inf->thread);
69 delete inf;
71 return 0;
74 #else
76 typedef struct {
77 ALuint (*func)(ALvoid*);
78 ALvoid *ptr;
79 ALuint ret;
80 pthread_t thread;
81 } ThreadInfo;
83 static void *StarterFunc(void *ptr)
85 ThreadInfo *inf = (ThreadInfo*)ptr;
86 inf->ret = inf->func(inf->ptr);
87 return NULL;
90 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
92 ThreadInfo *inf = new ThreadInfo;
93 inf->func = func;
94 inf->ptr = ptr;
96 if(pthread_create(&inf->thread, NULL, StarterFunc, inf) != 0)
98 delete inf;
99 return NULL;
102 return inf;
105 static ALuint StopThread(ThreadInfo *inf)
107 pthread_detach(inf->thread);
108 delete inf;
110 return 0;
113 #endif
115 struct AsyncPlayEntry {
116 ALuint source;
117 alureStream *stream;
118 std::vector<ALuint> buffers;
119 ALsizei loopcount;
120 ALsizei maxloops;
121 void (*eos_callback)(void*,ALuint);
122 void *user_data;
123 bool finished;
124 bool paused;
125 alureUInt64 base_time;
126 alureUInt64 max_time;
128 AsyncPlayEntry() : source(0), stream(NULL), loopcount(0), maxloops(0),
129 eos_callback(NULL), user_data(NULL), finished(false),
130 paused(false), base_time(0), max_time(0)
132 AsyncPlayEntry(const AsyncPlayEntry &rhs)
133 : source(rhs.source), stream(rhs.stream), buffers(rhs.buffers),
134 loopcount(rhs.loopcount), maxloops(rhs.maxloops),
135 eos_callback(rhs.eos_callback), user_data(rhs.user_data),
136 finished(rhs.finished), paused(rhs.paused), base_time(rhs.base_time),
137 max_time(rhs.max_time)
140 static std::list<AsyncPlayEntry> AsyncPlayList;
141 static ThreadInfo *PlayThreadHandle;
143 ALuint AsyncPlayFunc(ALvoid*)
145 while(1)
147 EnterCriticalSection(&cs_StreamPlay);
148 restart:
149 if(AsyncPlayList.size() == 0)
151 StopThread(PlayThreadHandle);
152 PlayThreadHandle = NULL;
153 LeaveCriticalSection(&cs_StreamPlay);
154 break;
157 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
158 end = AsyncPlayList.end();
159 for(;i != end;i++)
161 ALuint buf;
162 ALint processed;
163 ALint queued;
164 ALint state;
166 if(i->stream == NULL)
168 alGetSourcei(i->source, AL_SOURCE_STATE, &state);
169 if(state != AL_PLAYING && state != AL_PAUSED)
171 AsyncPlayEntry ent(*i);
172 AsyncPlayList.erase(i);
173 ent.eos_callback(ent.user_data, ent.source);
174 goto restart;
176 continue;
179 alGetSourcei(i->source, AL_SOURCE_STATE, &state);
180 alGetSourcei(i->source, AL_BUFFERS_QUEUED, &queued);
181 alGetSourcei(i->source, AL_BUFFERS_PROCESSED, &processed);
182 while(((ALuint)queued < i->buffers.size() && !i->finished) ||
183 processed > 0)
185 ALint size, channels, bits;
186 if(processed > 0)
188 queued--;
189 processed--;
190 alSourceUnqueueBuffers(i->source, 1, &buf);
192 alGetBufferi(buf, AL_SIZE, &size);
193 alGetBufferi(buf, AL_CHANNELS, &channels);
194 alGetBufferi(buf, AL_BITS, &bits);
195 i->base_time += size / channels * 8 / bits;
196 i->base_time %= i->max_time;
199 while(!i->finished)
201 ALenum format;
202 ALuint freq, blockAlign;
204 if(i->stream->GetFormat(&format, &freq, &blockAlign))
206 ALuint got = i->stream->GetData(i->stream->dataChunk,
207 i->stream->chunkLen);
208 got -= got%blockAlign;
209 if(got > 0)
211 alBufferData(buf, format, i->stream->dataChunk, got, freq);
212 alSourceQueueBuffers(i->source, 1, &buf);
213 queued++;
214 if(i->loopcount == 0)
216 alGetBufferi(buf, AL_SIZE, &size);
217 alGetBufferi(buf, AL_CHANNELS, &channels);
218 alGetBufferi(buf, AL_BITS, &bits);
219 i->max_time += size / channels * 8 / bits;
221 break;
224 if(i->loopcount == i->maxloops)
226 i->finished = true;
227 break;
229 if(i->maxloops != -1 || i->loopcount < 1)
230 i->loopcount++;
231 i->finished = !i->stream->Rewind();
234 if(state != AL_PLAYING)
236 if(queued == 0)
238 AsyncPlayEntry ent(*i);
239 AsyncPlayList.erase(i);
241 alSourcei(ent.source, AL_BUFFER, 0);
242 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
243 if(ent.eos_callback)
244 ent.eos_callback(ent.user_data, ent.source);
245 goto restart;
247 if(!i->paused)
248 alSourcePlay(i->source);
251 LeaveCriticalSection(&cs_StreamPlay);
253 alureSleep(0.015625f);
256 return 0;
259 void StopStream(alureStream *stream)
261 EnterCriticalSection(&cs_StreamPlay);
263 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
264 end = AsyncPlayList.end();
265 while(i != end)
267 if(i->stream == stream)
269 AsyncPlayEntry ent(*i);
270 AsyncPlayList.erase(i);
272 alSourceStop(ent.source);
273 alSourcei(ent.source, AL_BUFFER, 0);
274 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
275 alGetError();
277 if(ent.eos_callback)
278 ent.eos_callback(ent.user_data, ent.source);
279 break;
281 i++;
284 LeaveCriticalSection(&cs_StreamPlay);
288 extern "C" {
290 /* Function: alurePlaySourceStream
292 * Plays a stream asynchronously, using the specified source ID. A stream can
293 * only be played asynchronously if it is not already playing. It is important
294 * that the current context is NOT changed while a stream is playing, otherwise
295 * the asynchronous method used to play may start calling OpenAL with invalid
296 * IDs. Also note that checking the state of the specified source is not a good
297 * method to determine if a stream is playing. If an underrun occurs, the
298 * source will enter a stopped state until it is automatically restarted.
299 * Instead, set a flag using the callback to indicate the stream being stopped.
301 * Parameters:
302 * source - The source ID to play the stream with. Any buffers on the source
303 * will be unqueued. It is valid to set source properties not related
304 * to the buffer queue or playback state (ie. you may change the
305 * source's position, pitch, gain, etc, but you must not stop the
306 * source or queue/unqueue buffers on it). To pause the source, call
307 * <alurePauseSource>.
308 * stream - The stream to play asynchronously. Any valid stream will work,
309 * although looping will only work if the stream can be rewound (eg.
310 * streams made with <alureCreateStreamFromCallback> cannot loop, but
311 * will play for as long as the callback provides data).
312 * numBufs - The number of buffers used to queue with the OpenAL source. Each
313 * buffer will be filled with the chunk length specified when the
314 * stream was created. This value must be at least 2.
315 * loopcount - The number of times to loop the stream. When the stream reaches
316 * the end of processing, it will be rewound to continue buffering
317 * data. A value of -1 will cause the stream to loop indefinitely
318 * (or until <alureStopSource> is called).
319 * eos_callback - This callback will be called when the stream reaches the end,
320 * no more loops are pending, and the source reaches a stopped
321 * state. It will also be called if an error occured and
322 * playback terminated.
323 * userdata - An opaque user pointer passed to the callback.
325 * Returns:
326 * AL_FALSE on error.
328 * See Also:
329 * <alureStopSource>, <alurePauseSource>, <alureGetSourceOffset>
331 ALURE_API ALboolean ALURE_APIENTRY alurePlaySourceStream(ALuint source,
332 alureStream *stream, ALsizei numBufs, ALsizei loopcount,
333 void (*eos_callback)(void *userdata, ALuint source), void *userdata)
335 if(alGetError() != AL_NO_ERROR)
337 SetError("Existing OpenAL error");
338 return AL_FALSE;
341 if(!alureStream::Verify(stream))
343 SetError("Invalid stream pointer");
344 return AL_FALSE;
347 if(numBufs < 2)
349 SetError("Invalid buffer count");
350 return AL_FALSE;
353 if(!alIsSource(source))
355 SetError("Invalid source ID");
356 return AL_FALSE;
359 EnterCriticalSection(&cs_StreamPlay);
361 if(!PlayThreadHandle)
362 PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
363 if(!PlayThreadHandle)
365 SetError("Error starting async thread");
366 LeaveCriticalSection(&cs_StreamPlay);
367 return AL_FALSE;
370 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
371 end = AsyncPlayList.end();
372 while(i != end)
374 if(i->stream == stream)
376 SetError("Stream is already playing");
377 LeaveCriticalSection(&cs_StreamPlay);
378 return AL_FALSE;
380 if(i->source == source)
382 SetError("Source is already playing");
383 LeaveCriticalSection(&cs_StreamPlay);
384 return AL_FALSE;
386 i++;
389 AsyncPlayEntry ent;
390 ent.stream = stream;
391 ent.source = source;
392 ent.maxloops = loopcount;
393 ent.eos_callback = eos_callback;
394 ent.user_data = userdata;
396 ent.buffers.resize(numBufs);
397 alGenBuffers(ent.buffers.size(), &ent.buffers[0]);
398 if(alGetError() != AL_NO_ERROR)
400 LeaveCriticalSection(&cs_StreamPlay);
401 SetError("Error generating buffers");
402 return AL_FALSE;
405 ALenum format;
406 ALuint freq, blockAlign;
408 numBufs = 0;
409 if(ent.stream->GetFormat(&format, &freq, &blockAlign))
411 for(size_t i = 0;i < ent.buffers.size();i++)
413 ALuint got = ent.stream->GetData(ent.stream->dataChunk,
414 ent.stream->chunkLen);
415 got -= got%blockAlign;
416 if(got <= 0)
417 break;
419 ALuint buf = ent.buffers[i];
420 alBufferData(buf, format, ent.stream->dataChunk, got, freq);
421 alSourceQueueBuffers(ent.source, 1, &buf);
422 numBufs++;
424 ALint size, channels, bits;
425 alGetBufferi(buf, AL_SIZE, &size);
426 alGetBufferi(buf, AL_CHANNELS, &channels);
427 alGetBufferi(buf, AL_BITS, &bits);
428 ent.max_time += size / channels * 8 / bits;
431 if(numBufs == 0)
433 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
434 alGetError();
435 LeaveCriticalSection(&cs_StreamPlay);
436 SetError("Error buffering from stream");
437 return AL_FALSE;
440 if((ALuint)numBufs < ent.buffers.size())
442 if(ent.loopcount == ent.maxloops)
443 ent.finished = true;
444 else
446 if(ent.maxloops != -1 || ent.loopcount < 1)
447 ent.loopcount++;
448 ent.finished = !ent.stream->Rewind();
452 if((alSourcei(source, AL_BUFFER, 0),alGetError()) != AL_NO_ERROR ||
453 (alSourceQueueBuffers(source, numBufs, &ent.buffers[0]),alGetError()) != AL_NO_ERROR)
455 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
456 alGetError();
457 LeaveCriticalSection(&cs_StreamPlay);
458 SetError("Error starting source");
459 return AL_FALSE;
462 AsyncPlayList.push_front(ent);
464 LeaveCriticalSection(&cs_StreamPlay);
466 return AL_TRUE;
469 /* Function: alurePlaySource
471 * Plays the specified source ID and watches for it to stop. When a source
472 * enters the AL_STOPPED state, the specified callback is called to alert the
473 * application. As with <alurePlaySourceStream>, the current context must not
474 * be changed while the source is being watched (before the callback is called
475 * or <alureStopSource> is called). It also must not be deleted while being
476 * watched.
478 * Parameters:
479 * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
480 * to set source properties not related to the playback state (ie. you
481 * may change a source's position, pitch, gain, etc). Pausing a source
482 * and restarting a paused source is allowed, and the callback will
483 * still be invoked when the source naturally reaches an AL_STOPPED
484 * state.
485 * callback - The callback to be called when the source stops.
486 * userdata - An opaque user pointer passed to the callback.
488 * Returns:
489 * AL_FALSE on error.
491 * See Also:
492 * <alureStopSource>
494 ALURE_API ALboolean ALURE_APIENTRY alurePlaySource(ALuint source,
495 void (*callback)(void *userdata, ALuint source), void *userdata)
497 if(alGetError() != AL_NO_ERROR)
499 SetError("Existing OpenAL error");
500 return AL_FALSE;
503 EnterCriticalSection(&cs_StreamPlay);
505 if(!PlayThreadHandle)
506 PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
507 if(!PlayThreadHandle)
509 LeaveCriticalSection(&cs_StreamPlay);
510 SetError("Error starting async thread");
511 return AL_FALSE;
514 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
515 end = AsyncPlayList.end();
516 while(i != end)
518 if(i->source == source)
520 SetError("Source is already playing");
521 LeaveCriticalSection(&cs_StreamPlay);
522 return AL_FALSE;
524 i++;
527 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
529 LeaveCriticalSection(&cs_StreamPlay);
530 SetError("Error starting source");
531 return AL_FALSE;
534 if(callback != NULL)
536 AsyncPlayEntry ent;
537 ent.source = source;
538 ent.eos_callback = callback;
539 ent.user_data = userdata;
540 AsyncPlayList.push_front(ent);
543 LeaveCriticalSection(&cs_StreamPlay);
545 return AL_TRUE;
548 /* Function: alureStopSource
550 * Stops the specified source ID, and any associated asynchronous stream. The
551 * previously specified callback will be invoked if 'run_callback' is not
552 * AL_FALSE. Sources that were not started with <alurePlaySourceStream> or
553 * <alurePlaySource> will still be stopped, but will not have any callback
554 * called for them.
556 * Returns:
557 * AL_FALSE on error.
559 * See Also:
560 * <alurePlaySourceStream>, <alurePlaySource>
562 ALURE_API ALboolean ALURE_APIENTRY alureStopSource(ALuint source, ALboolean run_callback)
564 if(alGetError() != AL_NO_ERROR)
566 SetError("Existing OpenAL error");
567 return AL_FALSE;
570 EnterCriticalSection(&cs_StreamPlay);
572 if((alSourceStop(source),alGetError()) != AL_NO_ERROR)
574 LeaveCriticalSection(&cs_StreamPlay);
575 SetError("Error stopping source");
576 return AL_FALSE;
579 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
580 end = AsyncPlayList.end();
581 while(i != end)
583 if(i->source == source)
585 AsyncPlayEntry ent(*i);
586 AsyncPlayList.erase(i);
588 if(ent.buffers.size() > 0)
590 alSourcei(ent.source, AL_BUFFER, 0);
591 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
592 alGetError();
595 if(run_callback && ent.eos_callback)
596 ent.eos_callback(ent.user_data, ent.source);
597 break;
599 i++;
602 LeaveCriticalSection(&cs_StreamPlay);
604 return AL_TRUE;
607 /* Function: alurePauseSource
609 * Pauses the specified source ID, and any associated asynchronous stream. This
610 * is needed to avoid potential race conditions with sources that are playing
611 * an asynchronous stream. Pass AL_TRUE to 'resume' to resume a paused stream.
613 * Note that it is possible for the specified source to become stopped, and any
614 * associated stream to finish, before this function is called, causing the
615 * callback to be delayed until after the function returns when the async
616 * thread detects the stopped source.
618 * Returns:
619 * AL_FALSE on error.
621 * See Also:
622 * <alurePlaySourceStream>, <alurePlaySource>
624 ALURE_API ALboolean ALURE_APIENTRY alurePauseSource(ALuint source, ALboolean resume)
626 if(alGetError() != AL_NO_ERROR)
628 SetError("Existing OpenAL error");
629 return AL_FALSE;
632 EnterCriticalSection(&cs_StreamPlay);
634 if(!resume && (alSourcePause(source),alGetError()) != AL_NO_ERROR)
636 SetError("Error pausing source");
637 LeaveCriticalSection(&cs_StreamPlay);
638 return AL_FALSE;
640 if(resume && (alSourcePlay(source),alGetError()) != AL_NO_ERROR)
642 SetError("Error playing source");
643 LeaveCriticalSection(&cs_StreamPlay);
644 return AL_FALSE;
647 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
648 end = AsyncPlayList.end();
649 while(i != end)
651 if(i->source == source)
653 i->paused = !resume;
654 break;
656 i++;
659 LeaveCriticalSection(&cs_StreamPlay);
661 return AL_TRUE;
664 /* Function: alureGetSourceOffset
666 * Gets the sample offset of the specified source. For sources started with
667 * <alurePlaySourceStream>, this will be the total samples played. The offset
668 * will loop back to 0 when the stream rewinds for any specified loopcount. For
669 * non-streamed sources, the function will behave as if retrieving the
670 * AL_SAMPLE_OFFSET source value.
672 * Returns:
673 * (alureUInt64)-1 on error.
675 * See Also:
676 * <alurePlaySourceStream>
678 ALURE_API alureUInt64 ALURE_APIENTRY alureGetSourceOffset(ALuint source)
680 if(alGetError() != AL_NO_ERROR)
682 SetError("Existing OpenAL error");
683 return (alureUInt64)-1;
686 EnterCriticalSection(&cs_StreamPlay);
688 ALint pos;
689 if((alGetSourcei(source, AL_SAMPLE_OFFSET, &pos),alGetError()) != AL_NO_ERROR)
691 LeaveCriticalSection(&cs_StreamPlay);
692 SetError("Error retrieving source offset");
693 return (alureUInt64)-1;
696 alureUInt64 retval = static_cast<ALuint>(pos);
697 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
698 end = AsyncPlayList.end();
699 while(i != end)
701 if(i->source == source)
703 retval += i->base_time;
704 if(i->max_time)
705 retval %= i->max_time;
706 break;
708 i++;
711 LeaveCriticalSection(&cs_StreamPlay);
713 return retval;
716 } // extern "C"