Release 1.1
[alure.git] / src / streamplay.cpp
blobd33cdb655075236c8b09d8136a3029b3a51062f7
1 /*
2 * ALURE OpenAL utility library
3 * Copyright (c) 2009 by Chris Robinson.
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
24 /* Title: Automatic Playback */
26 #include "config.h"
28 #include "main.h"
30 #include <list>
31 #include <vector>
33 #ifdef HAVE_WINDOWS_H
35 typedef struct {
36 ALuint (*func)(ALvoid*);
37 ALvoid *ptr;
38 HANDLE thread;
39 } ThreadInfo;
41 static DWORD CALLBACK StarterFunc(void *ptr)
43 ThreadInfo *inf = (ThreadInfo*)ptr;
44 ALint ret;
46 ret = inf->func(inf->ptr);
47 ExitThread((DWORD)ret);
49 return (DWORD)ret;
52 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
54 DWORD dummy;
55 ThreadInfo *inf = new ThreadInfo;
56 inf->func = func;
57 inf->ptr = ptr;
59 inf->thread = CreateThread(NULL, 0, StarterFunc, inf, 0, &dummy);
60 if(!inf->thread)
62 delete inf;
63 return NULL;
66 return inf;
69 static ALuint StopThread(ThreadInfo *inf)
71 WaitForSingleObject(inf->thread, INFINITE);
72 CloseHandle(inf->thread);
73 delete inf;
75 return 0;
78 #else
80 typedef struct {
81 ALuint (*func)(ALvoid*);
82 ALvoid *ptr;
83 pthread_t thread;
84 } ThreadInfo;
86 static void *StarterFunc(void *ptr)
88 ThreadInfo *inf = (ThreadInfo*)ptr;
89 void *ret = (void*)(inf->func(inf->ptr));
90 return ret;
93 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
95 ThreadInfo *inf = new ThreadInfo;
96 inf->func = func;
97 inf->ptr = ptr;
99 if(pthread_create(&inf->thread, NULL, StarterFunc, inf) != 0)
101 delete inf;
102 return NULL;
105 return inf;
108 static ALuint StopThread(ThreadInfo *inf)
110 pthread_join(inf->thread, NULL);
111 delete inf;
113 return 0;
116 #endif
118 // This object is used to make sure the current context isn't switched out on
119 // us by another thread, by setting the current thread context to the current
120 // context. The old thread context is then restored when the object goes out
121 // of scope.
122 // This obviously only works when ALC_EXT_thread_local_context is supported
123 struct ProtectContext {
124 ProtectContext()
125 { protect(); }
127 ~ProtectContext()
128 { unprotect(); }
130 void protect()
132 old_ctx = (alcGetThreadContext ? alcGetThreadContext() : NULL);
133 if(alcSetThreadContext)
134 alcSetThreadContext(alcGetCurrentContext());
137 void unprotect()
139 if(alcSetThreadContext)
141 if(alcSetThreadContext(old_ctx) == ALC_FALSE)
142 alcSetThreadContext(NULL);
146 private:
147 ALCcontext *old_ctx;
149 #define PROTECT_CONTEXT() ProtectContext _ctx_prot
150 #define DO_PROTECT() _ctx_prot.protect()
151 #define DO_UNPROTECT() _ctx_prot.unprotect()
153 struct AsyncPlayEntry {
154 ALuint source;
155 alureStream *stream;
156 std::vector<ALuint> buffers;
157 ALsizei loopcount;
158 ALsizei maxloops;
159 void (*eos_callback)(void*,ALuint);
160 void *user_data;
161 bool finished;
162 bool paused;
163 ALuint stream_freq;
164 ALenum stream_format;
165 ALuint stream_align;
166 ALCcontext *ctx;
168 AsyncPlayEntry() : source(0), stream(NULL), loopcount(0), maxloops(0),
169 eos_callback(NULL), user_data(NULL), finished(false),
170 paused(false), stream_freq(0), stream_format(AL_NONE),
171 stream_align(0), ctx(NULL)
173 AsyncPlayEntry(const AsyncPlayEntry &rhs)
174 : source(rhs.source), stream(rhs.stream), buffers(rhs.buffers),
175 loopcount(rhs.loopcount), maxloops(rhs.maxloops),
176 eos_callback(rhs.eos_callback), user_data(rhs.user_data),
177 finished(rhs.finished), paused(rhs.paused),
178 stream_freq(rhs.stream_freq), stream_format(rhs.stream_format),
179 stream_align(rhs.stream_align), ctx(rhs.ctx)
182 ALenum Update(ALint *queued)
184 ALint processed, state;
186 alGetSourcei(source, AL_SOURCE_STATE, &state);
187 alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
188 while(processed > 0)
190 ALuint buf;
192 alSourceUnqueueBuffers(source, 1, &buf);
193 processed--;
195 while(!finished)
197 ALuint got = stream->GetData(&stream->dataChunk[0], stream->dataChunk.size());
198 got -= got%stream_align;
199 if(got > 0)
201 alBufferData(buf, stream_format, &stream->dataChunk[0], got, stream_freq);
202 alSourceQueueBuffers(source, 1, &buf);
204 break;
206 if(loopcount == maxloops)
208 finished = true;
209 break;
211 if(maxloops != -1)
212 loopcount++;
213 finished = !stream->Rewind();
217 alGetSourcei(source, AL_BUFFERS_QUEUED, queued);
218 return state;
221 static std::list<AsyncPlayEntry> AsyncPlayList;
222 static ThreadInfo *PlayThreadHandle;
224 ALfloat CurrentInterval = 0.0f;
226 ALuint AsyncPlayFunc(ALvoid*)
228 EnterCriticalSection(&cs_StreamPlay);
229 while(CurrentInterval > 0.0f)
231 alureUpdate();
233 ALfloat interval = CurrentInterval;
234 LeaveCriticalSection(&cs_StreamPlay);
235 alureSleep(interval);
236 EnterCriticalSection(&cs_StreamPlay);
238 LeaveCriticalSection(&cs_StreamPlay);
239 return 0;
243 void StopStream(alureStream *stream)
245 EnterCriticalSection(&cs_StreamPlay);
247 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
248 end = AsyncPlayList.end();
249 while(i != end)
251 if(i->stream == stream)
253 AsyncPlayEntry ent(*i);
254 AsyncPlayList.erase(i);
256 ALCcontext *old_ctx = (alcGetThreadContext ?
257 alcGetThreadContext() : NULL);
258 if(alcSetThreadContext)
260 if(alcSetThreadContext(ent.ctx) == ALC_FALSE)
261 goto ctx_err;
264 alSourceStop(ent.source);
265 alSourcei(ent.source, AL_BUFFER, 0);
266 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
267 alGetError();
269 if(alcSetThreadContext)
271 if(alcSetThreadContext(old_ctx) == ALC_FALSE)
272 alcSetThreadContext(NULL);
275 ctx_err:
276 if(ent.eos_callback)
277 ent.eos_callback(ent.user_data, ent.source);
278 break;
280 i++;
283 LeaveCriticalSection(&cs_StreamPlay);
287 extern "C" {
289 /* Function: alurePlaySourceStream
291 * Starts playing a stream, using the specified source ID. A stream can only be
292 * played if it is not already playing. You must call <alureUpdate> at regular
293 * intervals to keep the stream playing, or else the stream will underrun and
294 * cause a break in the playback until an update call can restart it. It is
295 * also important that the current context is kept for <alureUpdate> calls if
296 * ALC_EXT_thread_local_context is not supported, otherwise the method may
297 * start calling OpenAL with invalid IDs. Note that checking the state of the
298 * specified source is not a good method to determine if a stream is playing.
299 * If an underrun occurs, the source will enter a stopped state until it is
300 * automatically restarted. Instead, set a flag using the callback to indicate
301 * the stream being stopped.
303 * Parameters:
304 * source - The source ID to play the stream with. Any buffers on the source
305 * will be unqueued. It is valid to set source properties not related
306 * to the buffer queue or playback state (ie. you may change the
307 * source's position, pitch, gain, etc, but you must not stop the
308 * source or queue/unqueue buffers on it). To pause the source, call
309 * <alurePauseSource>.
310 * stream - The stream to play. Any valid stream will work, although looping
311 * will only work if the stream can be rewound (eg. streams made with
312 * <alureCreateStreamFromCallback> cannot loop, but will play for as
313 * long as the callback provides data).
314 * numBufs - The number of buffers used to queue with the OpenAL source. Each
315 * buffer will be filled with the chunk length specified when the
316 * stream was created. This value must be at least 2. More buffers at
317 * a larger size will increase the time needed between updates, but
318 * at the cost of more memory usage.
319 * loopcount - The number of times to loop the stream. When the stream reaches
320 * the end of processing, it will be rewound to continue buffering
321 * data. A value of -1 will cause the stream to loop indefinitely
322 * (or until <alureStopSource> is called).
323 * eos_callback - This callback will be called when the stream reaches the end,
324 * no more loops are pending, and the source reaches a stopped
325 * state. It will also be called if an error occured and
326 * playback terminated.
327 * userdata - An opaque user pointer passed to the callback.
329 * Returns:
330 * AL_FALSE on error.
332 * *Version Added*: 1.1
334 * See Also:
335 * <alureStopSource>, <alurePauseSource>, <alureUpdate>
337 ALURE_API ALboolean ALURE_APIENTRY alurePlaySourceStream(ALuint source,
338 alureStream *stream, ALsizei numBufs, ALsizei loopcount,
339 void (*eos_callback)(void *userdata, ALuint source), void *userdata)
341 PROTECT_CONTEXT();
342 ALCcontext *current_ctx = alcGetCurrentContext();
344 if(alGetError() != AL_NO_ERROR)
346 SetError("Existing OpenAL error");
347 return AL_FALSE;
350 if(!alureStream::Verify(stream))
352 SetError("Invalid stream pointer");
353 return AL_FALSE;
356 if(numBufs < 2)
358 SetError("Invalid buffer count");
359 return AL_FALSE;
362 if(!alIsSource(source))
364 SetError("Invalid source ID");
365 return AL_FALSE;
368 EnterCriticalSection(&cs_StreamPlay);
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 && i->ctx == current_ctx)
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;
395 ent.ctx = current_ctx;
397 ent.buffers.resize(numBufs);
398 alGenBuffers(ent.buffers.size(), &ent.buffers[0]);
399 if(alGetError() != AL_NO_ERROR)
401 LeaveCriticalSection(&cs_StreamPlay);
402 SetError("Error generating buffers");
403 return AL_FALSE;
406 numBufs = 0;
407 if(ent.stream->GetFormat(&ent.stream_format, &ent.stream_freq, &ent.stream_align))
409 for(size_t i = 0;i < ent.buffers.size();i++)
411 ALuint got = ent.stream->GetData(&ent.stream->dataChunk[0],
412 ent.stream->dataChunk.size());
413 got -= got%ent.stream_align;
414 if(got <= 0)
416 if(ent.loopcount == ent.maxloops || i == 0)
417 ent.finished = true;
418 else
420 if(ent.maxloops != -1)
421 ent.loopcount++;
422 ent.finished = !ent.stream->Rewind();
424 if(ent.finished)
425 break;
426 i--;
427 continue;
429 ALuint buf = ent.buffers[i];
430 alBufferData(buf, ent.stream_format, &ent.stream->dataChunk[0], got, ent.stream_freq);
431 numBufs++;
434 if(numBufs == 0)
436 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
437 alGetError();
438 LeaveCriticalSection(&cs_StreamPlay);
439 SetError("Error buffering from stream");
440 return AL_FALSE;
443 if((alSourcei(source, AL_LOOPING, AL_FALSE),
444 alSourcei(source, AL_BUFFER, 0),alGetError()) != AL_NO_ERROR ||
445 (alSourceQueueBuffers(source, numBufs, &ent.buffers[0]),
446 alSourcePlay(source),alGetError()) != AL_NO_ERROR)
448 alSourcei(source, AL_BUFFER, 0);
449 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
450 alGetError();
451 LeaveCriticalSection(&cs_StreamPlay);
452 SetError("Error starting source");
453 return AL_FALSE;
456 AsyncPlayList.push_front(ent);
458 LeaveCriticalSection(&cs_StreamPlay);
460 return AL_TRUE;
463 /* Function: alurePlaySource
465 * Plays the specified source ID and watches for it to stop. When the source
466 * enters an AL_STOPPED or AL_INITIAL state, the specified callback will be
467 * called by <alureUpdate> to alert the application. If
468 * ALC_EXT_thread_local_context is not supported, the current context must not
469 * be changed while the source is being watched (before the callback is called
470 * or <alureStopSource> is called). It also must not be deleted while being
471 * watched.
473 * Parameters:
474 * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
475 * to set source properties not related to the playback state (ie. you
476 * may change a source's position, pitch, gain, etc). Pausing a source
477 * and restarting a paused source is allowed, and the callback will
478 * still be called when the source reaches an AL_STOPPED or AL_INITIAL
479 * state.
480 * callback - The callback to be called when the source stops.
481 * userdata - An opaque user pointer passed to the callback.
483 * Returns:
484 * AL_FALSE on error.
486 * *Version Added*: 1.1
488 * See Also:
489 * <alureStopSource>, <alureUpdate>
491 ALURE_API ALboolean ALURE_APIENTRY alurePlaySource(ALuint source,
492 void (*callback)(void *userdata, ALuint source), void *userdata)
494 PROTECT_CONTEXT();
495 ALCcontext *current_ctx = alcGetCurrentContext();
497 if(alGetError() != AL_NO_ERROR)
499 SetError("Existing OpenAL error");
500 return AL_FALSE;
503 EnterCriticalSection(&cs_StreamPlay);
505 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
506 end = AsyncPlayList.end();
507 while(i != end)
509 if(i->source == source && i->ctx == current_ctx)
511 SetError("Source is already playing");
512 LeaveCriticalSection(&cs_StreamPlay);
513 return AL_FALSE;
515 i++;
518 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
520 LeaveCriticalSection(&cs_StreamPlay);
521 SetError("Error starting source");
522 return AL_FALSE;
525 if(callback != NULL)
527 AsyncPlayEntry ent;
528 ent.source = source;
529 ent.eos_callback = callback;
530 ent.user_data = userdata;
531 ent.ctx = current_ctx;
532 AsyncPlayList.push_front(ent);
535 LeaveCriticalSection(&cs_StreamPlay);
537 return AL_TRUE;
540 /* Function: alureStopSource
542 * Stops the specified source ID, and any associated stream. The previously
543 * specified callback will be invoked if 'run_callback' is not AL_FALSE.
544 * Sources that were not started with <alurePlaySourceStream> or
545 * <alurePlaySource> will still be stopped, but will not have any callback
546 * called for them.
548 * Returns:
549 * AL_FALSE on error.
551 * *Version Added*: 1.1
553 * See Also:
554 * <alurePlaySourceStream>, <alurePlaySource>
556 ALURE_API ALboolean ALURE_APIENTRY alureStopSource(ALuint source, ALboolean run_callback)
558 PROTECT_CONTEXT();
559 ALCcontext *current_ctx = alcGetCurrentContext();
561 if(alGetError() != AL_NO_ERROR)
563 SetError("Existing OpenAL error");
564 return AL_FALSE;
567 EnterCriticalSection(&cs_StreamPlay);
569 if((alSourceStop(source),alGetError()) != AL_NO_ERROR)
571 LeaveCriticalSection(&cs_StreamPlay);
572 SetError("Error stopping source");
573 return AL_FALSE;
576 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
577 end = AsyncPlayList.end();
578 while(i != end)
580 if(i->source == source && i->ctx == current_ctx)
582 AsyncPlayEntry ent(*i);
583 AsyncPlayList.erase(i);
585 if(ent.buffers.size() > 0)
587 alSourcei(ent.source, AL_BUFFER, 0);
588 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
589 alGetError();
592 if(run_callback && ent.eos_callback)
594 DO_UNPROTECT();
595 ent.eos_callback(ent.user_data, ent.source);
596 DO_PROTECT();
598 break;
600 i++;
603 LeaveCriticalSection(&cs_StreamPlay);
605 return AL_TRUE;
608 /* Function: alurePauseSource
610 * Pauses the specified source ID, and any associated stream. This is needed to
611 * avoid potential race conditions with sources that are playing a 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 and <alureUpdate>
616 * detects the stopped source.
618 * Returns:
619 * AL_FALSE on error.
621 * *Version Added*: 1.1
623 * See Also:
624 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
626 ALURE_API ALboolean ALURE_APIENTRY alurePauseSource(ALuint source)
628 PROTECT_CONTEXT();
629 ALCcontext *current_ctx = alcGetCurrentContext();
631 if(alGetError() != AL_NO_ERROR)
633 SetError("Existing OpenAL error");
634 return AL_FALSE;
637 EnterCriticalSection(&cs_StreamPlay);
639 if((alSourcePause(source),alGetError()) != AL_NO_ERROR)
641 SetError("Error pausing source");
642 LeaveCriticalSection(&cs_StreamPlay);
643 return AL_FALSE;
646 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
647 end = AsyncPlayList.end();
648 while(i != end)
650 if(i->source == source && i->ctx == current_ctx)
652 i->paused = true;
653 break;
655 i++;
658 LeaveCriticalSection(&cs_StreamPlay);
660 return AL_TRUE;
663 /* Function: alureResumeSource
665 * Resumes the specified source ID after being paused.
667 * Returns:
668 * AL_FALSE on error.
670 * *Version Added*: 1.1
672 * See Also:
673 * <alurePauseSource>
675 ALURE_API ALboolean ALURE_APIENTRY alureResumeSource(ALuint source)
677 PROTECT_CONTEXT();
678 ALCcontext *current_ctx = alcGetCurrentContext();
680 if(alGetError() != AL_NO_ERROR)
682 SetError("Existing OpenAL error");
683 return AL_FALSE;
686 EnterCriticalSection(&cs_StreamPlay);
688 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
690 SetError("Error playing source");
691 LeaveCriticalSection(&cs_StreamPlay);
692 return AL_FALSE;
695 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
696 end = AsyncPlayList.end();
697 while(i != end)
699 if(i->source == source && i->ctx == current_ctx)
701 i->paused = false;
702 break;
704 i++;
707 LeaveCriticalSection(&cs_StreamPlay);
709 return AL_TRUE;
712 /* Function: alureUpdate
714 * Updates the running list of streams, and checks for stopped sources. This
715 * makes sure that sources played with <alurePlaySourceStream> are kept fed
716 * from their associated stream, and sources played with <alurePlaySource> are
717 * still playing. It will call their callbacks as needed.
719 * *Version Added*: 1.1
721 * See Also:
722 * <alurePlaySourceStream>, <alurePlaySource>
724 ALURE_API void ALURE_APIENTRY alureUpdate(void)
726 PROTECT_CONTEXT();
728 EnterCriticalSection(&cs_StreamPlay);
729 restart:
730 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
731 end = AsyncPlayList.end();
732 for(;i != end;i++)
734 if(alcSetThreadContext)
736 if(alcSetThreadContext(i->ctx) == ALC_FALSE)
738 AsyncPlayEntry ent(*i);
739 AsyncPlayList.erase(i);
740 if(ent.eos_callback)
742 DO_UNPROTECT();
743 ent.eos_callback(ent.user_data, ent.source);
744 DO_PROTECT();
746 goto restart;
750 if(i->stream == NULL)
752 ALint state;
753 alGetSourcei(i->source, AL_SOURCE_STATE, &state);
754 if(state == AL_STOPPED || state == AL_INITIAL)
756 AsyncPlayEntry ent(*i);
757 AsyncPlayList.erase(i);
758 DO_UNPROTECT();
759 ent.eos_callback(ent.user_data, ent.source);
760 DO_PROTECT();
761 goto restart;
763 continue;
766 ALint queued;
767 if(i->Update(&queued) != AL_PLAYING)
769 if(queued == 0)
771 AsyncPlayEntry ent(*i);
772 AsyncPlayList.erase(i);
774 alSourcei(ent.source, AL_BUFFER, 0);
775 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
776 if(ent.eos_callback)
778 DO_UNPROTECT();
779 ent.eos_callback(ent.user_data, ent.source);
780 DO_PROTECT();
782 goto restart;
784 if(!i->paused)
785 alSourcePlay(i->source);
788 LeaveCriticalSection(&cs_StreamPlay);
791 /* Function: alureUpdateInterval
793 * Sets up a timer or thread to automatically call <alureUpdate> at the given
794 * interval, in seconds. If the timer or thread is already running, the update
795 * interval will be modified. A 0 or negative interval will stop <alureUpdate>
796 * from being called.
798 * Returns:
799 * AL_FALSE on error.
801 * *Version Added*: 1.1
803 * See Also:
804 * <alureUpdate>
806 ALURE_API ALboolean ALURE_APIENTRY alureUpdateInterval(ALfloat interval)
808 EnterCriticalSection(&cs_StreamPlay);
809 if(interval <= 0.0f)
811 CurrentInterval = 0.0f;
812 if(PlayThreadHandle)
814 ThreadInfo *threadinf = PlayThreadHandle;
815 PlayThreadHandle = NULL;
816 LeaveCriticalSection(&cs_StreamPlay);
817 StopThread(threadinf);
818 EnterCriticalSection(&cs_StreamPlay);
821 else if(interval > 0.0f)
823 if(!PlayThreadHandle)
824 PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
825 if(!PlayThreadHandle)
827 SetError("Error starting async thread");
828 LeaveCriticalSection(&cs_StreamPlay);
829 return AL_FALSE;
831 CurrentInterval = interval;
833 LeaveCriticalSection(&cs_StreamPlay);
835 return AL_TRUE;
838 } // extern "C"