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
24 /* Title: Automatic Playback */
36 ALuint (*func
)(ALvoid
*);
41 static DWORD CALLBACK
StarterFunc(void *ptr
)
43 ThreadInfo
*inf
= (ThreadInfo
*)ptr
;
46 ret
= inf
->func(inf
->ptr
);
47 ExitThread((DWORD
)ret
);
52 static ThreadInfo
*StartThread(ALuint (*func
)(ALvoid
*), ALvoid
*ptr
)
55 ThreadInfo
*inf
= new ThreadInfo
;
59 inf
->thread
= CreateThread(NULL
, 0, StarterFunc
, inf
, 0, &dummy
);
69 static ALuint
StopThread(ThreadInfo
*inf
)
71 WaitForSingleObject(inf
->thread
, INFINITE
);
72 CloseHandle(inf
->thread
);
81 ALuint (*func
)(ALvoid
*);
86 static void *StarterFunc(void *ptr
)
88 ThreadInfo
*inf
= (ThreadInfo
*)ptr
;
89 void *ret
= (void*)(inf
->func(inf
->ptr
));
93 static ThreadInfo
*StartThread(ALuint (*func
)(ALvoid
*), ALvoid
*ptr
)
95 ThreadInfo
*inf
= new ThreadInfo
;
99 if(pthread_create(&inf
->thread
, NULL
, StarterFunc
, inf
) != 0)
108 static ALuint
StopThread(ThreadInfo
*inf
)
110 pthread_join(inf
->thread
, NULL
);
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
122 // This obviously only works when ALC_EXT_thread_local_context is supported
123 struct ProtectContext
{
126 old_ctx
= (alcGetThreadContext
? alcGetThreadContext() : NULL
);
127 if(alcSetThreadContext
)
128 alcSetThreadContext(alcGetCurrentContext());
132 if(alcSetThreadContext
)
134 if(alcSetThreadContext(old_ctx
) == ALC_FALSE
)
135 alcSetThreadContext(NULL
);
142 #define PROTECT_CONTEXT() ProtectContext _ctx_prot
144 struct AsyncPlayEntry
{
147 std::vector
<ALuint
> buffers
;
150 void (*eos_callback
)(void*,ALuint
);
155 ALenum stream_format
;
159 AsyncPlayEntry() : source(0), stream(NULL
), loopcount(0), maxloops(0),
160 eos_callback(NULL
), user_data(NULL
), finished(false),
161 paused(false), stream_freq(0), stream_format(AL_NONE
),
162 stream_align(0), ctx(NULL
)
164 AsyncPlayEntry(const AsyncPlayEntry
&rhs
)
165 : source(rhs
.source
), stream(rhs
.stream
), buffers(rhs
.buffers
),
166 loopcount(rhs
.loopcount
), maxloops(rhs
.maxloops
),
167 eos_callback(rhs
.eos_callback
), user_data(rhs
.user_data
),
168 finished(rhs
.finished
), paused(rhs
.paused
),
169 stream_freq(rhs
.stream_freq
), stream_format(rhs
.stream_format
),
170 stream_align(rhs
.stream_align
), ctx(rhs
.ctx
)
173 ALenum
Update(ALint
*queued
)
175 ALint processed
, state
;
177 alGetSourcei(source
, AL_SOURCE_STATE
, &state
);
178 alGetSourcei(source
, AL_BUFFERS_PROCESSED
, &processed
);
183 alSourceUnqueueBuffers(source
, 1, &buf
);
188 ALuint got
= stream
->GetData(&stream
->dataChunk
[0], stream
->dataChunk
.size());
189 got
-= got
%stream_align
;
192 alBufferData(buf
, stream_format
, &stream
->dataChunk
[0], got
, stream_freq
);
193 alSourceQueueBuffers(source
, 1, &buf
);
197 if(loopcount
== maxloops
)
204 finished
= !stream
->Rewind();
208 alGetSourcei(source
, AL_BUFFERS_QUEUED
, queued
);
212 static std::list
<AsyncPlayEntry
> AsyncPlayList
;
213 static ThreadInfo
*PlayThreadHandle
;
215 ALfloat CurrentInterval
= 0.0f
;
217 ALuint
AsyncPlayFunc(ALvoid
*)
219 EnterCriticalSection(&cs_StreamPlay
);
220 while(CurrentInterval
> 0.0f
)
224 ALfloat interval
= CurrentInterval
;
225 LeaveCriticalSection(&cs_StreamPlay
);
226 alureSleep(interval
);
227 EnterCriticalSection(&cs_StreamPlay
);
229 LeaveCriticalSection(&cs_StreamPlay
);
234 void StopStream(alureStream
*stream
)
236 EnterCriticalSection(&cs_StreamPlay
);
238 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
239 end
= AsyncPlayList
.end();
242 if(i
->stream
== stream
)
244 ALCcontext
*old_ctx
= (alcGetThreadContext
?
245 alcGetThreadContext() : NULL
);
246 if(alcSetThreadContext
) alcSetThreadContext(i
->ctx
);
248 AsyncPlayEntry
ent(*i
);
249 AsyncPlayList
.erase(i
);
251 alSourceStop(ent
.source
);
252 alSourcei(ent
.source
, AL_BUFFER
, 0);
253 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
257 ent
.eos_callback(ent
.user_data
, ent
.source
);
259 if(alcSetThreadContext
)
261 if(alcSetThreadContext(old_ctx
) == ALC_FALSE
)
262 alcSetThreadContext(NULL
);
269 LeaveCriticalSection(&cs_StreamPlay
);
275 /* Function: alurePlaySourceStream
277 * Starts playing a stream, using the specified source ID. A stream can only be
278 * played if it is not already playing. You must call <alureUpdate> at regular
279 * intervals to keep the stream playing, or else the stream will underrun and
280 * cause a break in the playback until an update call can restart it. It is
281 * also important that the current context is kept for <alureUpdate> calls if
282 * ALC_EXT_thread_local_context is not supported, otherwise the method may
283 * start calling OpenAL with invalid IDs. Note that checking the state of the
284 * specified source is not a good method to determine if a stream is playing.
285 * If an underrun occurs, the source will enter a stopped state until it is
286 * automatically restarted. Instead, set a flag using the callback to indicate
287 * the stream being stopped.
290 * source - The source ID to play the stream with. Any buffers on the source
291 * will be unqueued. It is valid to set source properties not related
292 * to the buffer queue or playback state (ie. you may change the
293 * source's position, pitch, gain, etc, but you must not stop the
294 * source or queue/unqueue buffers on it). To pause the source, call
295 * <alurePauseSource>.
296 * stream - The stream to play. Any valid stream will work, although looping
297 * will only work if the stream can be rewound (eg. streams made with
298 * <alureCreateStreamFromCallback> cannot loop, but will play for as
299 * long as the callback provides data).
300 * numBufs - The number of buffers used to queue with the OpenAL source. Each
301 * buffer will be filled with the chunk length specified when the
302 * stream was created. This value must be at least 2. More buffers at
303 * a larger size will decrease the time needed between updates, but
304 * at the cost of more memory usage.
305 * loopcount - The number of times to loop the stream. When the stream reaches
306 * the end of processing, it will be rewound to continue buffering
307 * data. A value of -1 will cause the stream to loop indefinitely
308 * (or until <alureStopSource> is called).
309 * eos_callback - This callback will be called when the stream reaches the end,
310 * no more loops are pending, and the source reaches a stopped
311 * state. It will also be called if an error occured and
312 * playback terminated.
313 * userdata - An opaque user pointer passed to the callback.
318 * *Version Added*: 1.1
321 * <alureStopSource>, <alurePauseSource>, <alureUpdate>
323 ALURE_API ALboolean ALURE_APIENTRY
alurePlaySourceStream(ALuint source
,
324 alureStream
*stream
, ALsizei numBufs
, ALsizei loopcount
,
325 void (*eos_callback
)(void *userdata
, ALuint source
), void *userdata
)
328 ALCcontext
*current_ctx
= alcGetCurrentContext();
330 if(alGetError() != AL_NO_ERROR
)
332 SetError("Existing OpenAL error");
336 if(!alureStream::Verify(stream
))
338 SetError("Invalid stream pointer");
344 SetError("Invalid buffer count");
348 if(!alIsSource(source
))
350 SetError("Invalid source ID");
354 EnterCriticalSection(&cs_StreamPlay
);
356 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
357 end
= AsyncPlayList
.end();
360 if(i
->stream
== stream
)
362 SetError("Stream is already playing");
363 LeaveCriticalSection(&cs_StreamPlay
);
366 if(i
->source
== source
&& i
->ctx
== current_ctx
)
368 SetError("Source is already playing");
369 LeaveCriticalSection(&cs_StreamPlay
);
378 ent
.maxloops
= loopcount
;
379 ent
.eos_callback
= eos_callback
;
380 ent
.user_data
= userdata
;
381 ent
.ctx
= current_ctx
;
383 ent
.buffers
.resize(numBufs
);
384 alGenBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
385 if(alGetError() != AL_NO_ERROR
)
387 LeaveCriticalSection(&cs_StreamPlay
);
388 SetError("Error generating buffers");
393 if(ent
.stream
->GetFormat(&ent
.stream_format
, &ent
.stream_freq
, &ent
.stream_align
))
395 for(size_t i
= 0;i
< ent
.buffers
.size();i
++)
397 ALuint got
= ent
.stream
->GetData(&ent
.stream
->dataChunk
[0],
398 ent
.stream
->dataChunk
.size());
399 got
-= got
%ent
.stream_align
;
402 if(ent
.loopcount
== ent
.maxloops
|| i
== 0)
406 if(ent
.maxloops
!= -1)
408 ent
.finished
= !ent
.stream
->Rewind();
415 ALuint buf
= ent
.buffers
[i
];
416 alBufferData(buf
, ent
.stream_format
, &ent
.stream
->dataChunk
[0], got
, ent
.stream_freq
);
422 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
424 LeaveCriticalSection(&cs_StreamPlay
);
425 SetError("Error buffering from stream");
429 if((alSourcei(source
, AL_LOOPING
, AL_FALSE
),
430 alSourcei(source
, AL_BUFFER
, 0),alGetError()) != AL_NO_ERROR
||
431 (alSourceQueueBuffers(source
, numBufs
, &ent
.buffers
[0]),
432 alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
434 alSourcei(source
, AL_BUFFER
, 0);
435 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
437 LeaveCriticalSection(&cs_StreamPlay
);
438 SetError("Error starting source");
442 AsyncPlayList
.push_front(ent
);
444 LeaveCriticalSection(&cs_StreamPlay
);
449 /* Function: alurePlaySource
451 * Plays the specified source ID and watches for it to stop. When a source
452 * enters an AL_STOPPED state, the specified callback will be called by
453 * <alureUpdate> to alert the application. As with <alurePlaySourceStream>, if
454 * ALC_EXT_thread_local_context is not supported, the current context must not
455 * be changed while the source is being watched (before the callback is called
456 * or <alureStopSource> is called). It also must not be deleted while being
460 * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
461 * to set source properties not related to the playback state (ie. you
462 * may change a source's position, pitch, gain, etc). Pausing a source
463 * and restarting a paused source is allowed, and the callback will
464 * still be invoked when the source naturally reaches an AL_STOPPED
466 * callback - The callback to be called when the source stops.
467 * userdata - An opaque user pointer passed to the callback.
472 * *Version Added*: 1.1
475 * <alureStopSource>, <alureUpdate>
477 ALURE_API ALboolean ALURE_APIENTRY
alurePlaySource(ALuint source
,
478 void (*callback
)(void *userdata
, ALuint source
), void *userdata
)
481 ALCcontext
*current_ctx
= alcGetCurrentContext();
483 if(alGetError() != AL_NO_ERROR
)
485 SetError("Existing OpenAL error");
489 EnterCriticalSection(&cs_StreamPlay
);
491 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
492 end
= AsyncPlayList
.end();
495 if(i
->source
== source
&& i
->ctx
== current_ctx
)
497 SetError("Source is already playing");
498 LeaveCriticalSection(&cs_StreamPlay
);
504 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
506 LeaveCriticalSection(&cs_StreamPlay
);
507 SetError("Error starting source");
515 ent
.eos_callback
= callback
;
516 ent
.user_data
= userdata
;
517 ent
.ctx
= current_ctx
;
518 AsyncPlayList
.push_front(ent
);
521 LeaveCriticalSection(&cs_StreamPlay
);
526 /* Function: alureStopSource
528 * Stops the specified source ID, and any associated stream. The previously
529 * specified callback will be invoked if 'run_callback' is not AL_FALSE.
530 * Sources that were not started with <alurePlaySourceStream> or
531 * <alurePlaySource> will still be stopped, but will not have any callback
537 * *Version Added*: 1.1
540 * <alurePlaySourceStream>, <alurePlaySource>
542 ALURE_API ALboolean ALURE_APIENTRY
alureStopSource(ALuint source
, ALboolean run_callback
)
545 ALCcontext
*current_ctx
= alcGetCurrentContext();
547 if(alGetError() != AL_NO_ERROR
)
549 SetError("Existing OpenAL error");
553 EnterCriticalSection(&cs_StreamPlay
);
555 if((alSourceStop(source
),alGetError()) != AL_NO_ERROR
)
557 LeaveCriticalSection(&cs_StreamPlay
);
558 SetError("Error stopping source");
562 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
563 end
= AsyncPlayList
.end();
566 if(i
->source
== source
&& i
->ctx
== current_ctx
)
568 AsyncPlayEntry
ent(*i
);
569 AsyncPlayList
.erase(i
);
571 if(ent
.buffers
.size() > 0)
573 alSourcei(ent
.source
, AL_BUFFER
, 0);
574 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
578 if(run_callback
&& ent
.eos_callback
)
579 ent
.eos_callback(ent
.user_data
, ent
.source
);
585 LeaveCriticalSection(&cs_StreamPlay
);
590 /* Function: alurePauseSource
592 * Pauses the specified source ID, and any associated stream. This is needed to
593 * avoid potential race conditions with sources that are playing a stream.
595 * Note that it is possible for the specified source to become stopped, and any
596 * associated stream to finish, before this function is called, causing the
597 * callback to be delayed until after the function returns and <alureUpdate>
598 * detects the stopped source.
603 * *Version Added*: 1.1
606 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
608 ALURE_API ALboolean ALURE_APIENTRY
alurePauseSource(ALuint source
)
611 ALCcontext
*current_ctx
= alcGetCurrentContext();
613 if(alGetError() != AL_NO_ERROR
)
615 SetError("Existing OpenAL error");
619 EnterCriticalSection(&cs_StreamPlay
);
621 if((alSourcePause(source
),alGetError()) != AL_NO_ERROR
)
623 SetError("Error pausing source");
624 LeaveCriticalSection(&cs_StreamPlay
);
628 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
629 end
= AsyncPlayList
.end();
632 if(i
->source
== source
&& i
->ctx
== current_ctx
)
640 LeaveCriticalSection(&cs_StreamPlay
);
645 /* Function: alureResumeSource
647 * Resumes the specified source ID after being paused.
652 * *Version Added*: 1.1
657 ALURE_API ALboolean ALURE_APIENTRY
alureResumeSource(ALuint source
)
660 ALCcontext
*current_ctx
= alcGetCurrentContext();
662 if(alGetError() != AL_NO_ERROR
)
664 SetError("Existing OpenAL error");
668 EnterCriticalSection(&cs_StreamPlay
);
670 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
672 SetError("Error playing source");
673 LeaveCriticalSection(&cs_StreamPlay
);
677 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
678 end
= AsyncPlayList
.end();
681 if(i
->source
== source
&& i
->ctx
== current_ctx
)
689 LeaveCriticalSection(&cs_StreamPlay
);
694 /* Function: alureUpdate
696 * Updates the running list of streams, and checks for stopped sources. This
697 * makes sure that sources played with <alurePlaySourceStream> are kept fed
698 * from their associated stream, and sources played with <alurePlaySource> are
699 * still playing. It will call their callbacks as needed.
701 * *Version Added*: 1.1
704 * <alurePlaySourceStream>, <alurePlaySource>
706 ALURE_API
void ALURE_APIENTRY
alureUpdate(void)
710 EnterCriticalSection(&cs_StreamPlay
);
712 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
713 end
= AsyncPlayList
.end();
716 if(alcSetThreadContext
)
718 if(alcSetThreadContext(i
->ctx
) == ALC_FALSE
)
720 AsyncPlayEntry
ent(*i
);
721 AsyncPlayList
.erase(i
);
723 ent
.eos_callback(ent
.user_data
, ent
.source
);
728 if(i
->stream
== NULL
)
731 alGetSourcei(i
->source
, AL_SOURCE_STATE
, &state
);
732 if(state
!= AL_PLAYING
&& state
!= AL_PAUSED
)
734 AsyncPlayEntry
ent(*i
);
735 AsyncPlayList
.erase(i
);
736 ent
.eos_callback(ent
.user_data
, ent
.source
);
743 if(i
->Update(&queued
) != AL_PLAYING
)
747 AsyncPlayEntry
ent(*i
);
748 AsyncPlayList
.erase(i
);
750 alSourcei(ent
.source
, AL_BUFFER
, 0);
751 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
753 ent
.eos_callback(ent
.user_data
, ent
.source
);
757 alSourcePlay(i
->source
);
760 LeaveCriticalSection(&cs_StreamPlay
);
763 /* Function: alureUpdateInterval
765 * Sets up a timer or thread to automatically call <alureUpdate> at the given
766 * interval, in seconds. If the timer or thread is already running, the update
767 * interval will be modified. A 0 or negative interval will stop <alureUpdate>
773 * *Version Added*: 1.1
778 ALURE_API ALboolean ALURE_APIENTRY
alureUpdateInterval(ALfloat interval
)
780 EnterCriticalSection(&cs_StreamPlay
);
783 CurrentInterval
= 0.0f
;
786 ThreadInfo
*threadinf
= PlayThreadHandle
;
787 PlayThreadHandle
= NULL
;
788 LeaveCriticalSection(&cs_StreamPlay
);
789 StopThread(threadinf
);
790 EnterCriticalSection(&cs_StreamPlay
);
795 if(!PlayThreadHandle
)
796 PlayThreadHandle
= StartThread(AsyncPlayFunc
, NULL
);
797 if(!PlayThreadHandle
)
799 SetError("Error starting async thread");
800 LeaveCriticalSection(&cs_StreamPlay
);
803 CurrentInterval
= interval
;
805 LeaveCriticalSection(&cs_StreamPlay
);