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
{
132 old_ctx
= (alcGetThreadContext
? alcGetThreadContext() : NULL
);
133 if(alcSetThreadContext
)
134 alcSetThreadContext(alcGetCurrentContext());
139 if(alcSetThreadContext
)
141 if(alcSetThreadContext(old_ctx
) == ALC_FALSE
)
142 alcSetThreadContext(NULL
);
149 #define PROTECT_CONTEXT() ProtectContext _ctx_prot
150 #define DO_PROTECT() _ctx_prot.protect()
151 #define DO_UNPROTECT() _ctx_prot.unprotect()
153 struct AsyncPlayEntry
{
156 std::vector
<ALuint
> buffers
;
159 void (*eos_callback
)(void*,ALuint
);
164 ALenum stream_format
;
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
);
192 alSourceUnqueueBuffers(source
, 1, &buf
);
197 ALuint got
= stream
->GetData(&stream
->dataChunk
[0], stream
->dataChunk
.size());
198 got
-= got
%stream_align
;
201 alBufferData(buf
, stream_format
, &stream
->dataChunk
[0], got
, stream_freq
);
202 alSourceQueueBuffers(source
, 1, &buf
);
206 if(loopcount
== maxloops
)
213 finished
= !stream
->Rewind();
217 alGetSourcei(source
, AL_BUFFERS_QUEUED
, queued
);
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
)
233 ALfloat interval
= CurrentInterval
;
234 LeaveCriticalSection(&cs_StreamPlay
);
235 alureSleep(interval
);
236 EnterCriticalSection(&cs_StreamPlay
);
238 LeaveCriticalSection(&cs_StreamPlay
);
243 void StopStream(alureStream
*stream
)
245 EnterCriticalSection(&cs_StreamPlay
);
247 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
248 end
= AsyncPlayList
.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
)
264 alSourceStop(ent
.source
);
265 alSourcei(ent
.source
, AL_BUFFER
, 0);
266 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
269 if(alcSetThreadContext
)
271 if(alcSetThreadContext(old_ctx
) == ALC_FALSE
)
272 alcSetThreadContext(NULL
);
277 ent
.eos_callback(ent
.user_data
, ent
.source
);
283 LeaveCriticalSection(&cs_StreamPlay
);
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.
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.
332 * *Version Added*: 1.1
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
)
342 ALCcontext
*current_ctx
= alcGetCurrentContext();
344 if(alGetError() != AL_NO_ERROR
)
346 SetError("Existing OpenAL error");
350 if(!alureStream::Verify(stream
))
352 SetError("Invalid stream pointer");
358 SetError("Invalid buffer count");
362 if(!alIsSource(source
))
364 SetError("Invalid source ID");
368 EnterCriticalSection(&cs_StreamPlay
);
370 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
371 end
= AsyncPlayList
.end();
374 if(i
->stream
== stream
)
376 SetError("Stream is already playing");
377 LeaveCriticalSection(&cs_StreamPlay
);
380 if(i
->source
== source
&& i
->ctx
== current_ctx
)
382 SetError("Source is already playing");
383 LeaveCriticalSection(&cs_StreamPlay
);
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");
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
;
416 if(ent
.loopcount
== ent
.maxloops
|| i
== 0)
420 if(ent
.maxloops
!= -1)
422 ent
.finished
= !ent
.stream
->Rewind();
429 ALuint buf
= ent
.buffers
[i
];
430 alBufferData(buf
, ent
.stream_format
, &ent
.stream
->dataChunk
[0], got
, ent
.stream_freq
);
436 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
438 LeaveCriticalSection(&cs_StreamPlay
);
439 SetError("Error buffering from stream");
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]);
451 LeaveCriticalSection(&cs_StreamPlay
);
452 SetError("Error starting source");
456 AsyncPlayList
.push_front(ent
);
458 LeaveCriticalSection(&cs_StreamPlay
);
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
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
480 * callback - The callback to be called when the source stops.
481 * userdata - An opaque user pointer passed to the callback.
486 * *Version Added*: 1.1
489 * <alureStopSource>, <alureUpdate>
491 ALURE_API ALboolean ALURE_APIENTRY
alurePlaySource(ALuint source
,
492 void (*callback
)(void *userdata
, ALuint source
), void *userdata
)
495 ALCcontext
*current_ctx
= alcGetCurrentContext();
497 if(alGetError() != AL_NO_ERROR
)
499 SetError("Existing OpenAL error");
503 EnterCriticalSection(&cs_StreamPlay
);
505 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
506 end
= AsyncPlayList
.end();
509 if(i
->source
== source
&& i
->ctx
== current_ctx
)
511 SetError("Source is already playing");
512 LeaveCriticalSection(&cs_StreamPlay
);
518 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
520 LeaveCriticalSection(&cs_StreamPlay
);
521 SetError("Error starting 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
);
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
551 * *Version Added*: 1.1
554 * <alurePlaySourceStream>, <alurePlaySource>
556 ALURE_API ALboolean ALURE_APIENTRY
alureStopSource(ALuint source
, ALboolean run_callback
)
559 ALCcontext
*current_ctx
= alcGetCurrentContext();
561 if(alGetError() != AL_NO_ERROR
)
563 SetError("Existing OpenAL error");
567 EnterCriticalSection(&cs_StreamPlay
);
569 if((alSourceStop(source
),alGetError()) != AL_NO_ERROR
)
571 LeaveCriticalSection(&cs_StreamPlay
);
572 SetError("Error stopping source");
576 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
577 end
= AsyncPlayList
.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]);
592 if(run_callback
&& ent
.eos_callback
)
595 ent
.eos_callback(ent
.user_data
, ent
.source
);
603 LeaveCriticalSection(&cs_StreamPlay
);
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.
621 * *Version Added*: 1.1
624 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
626 ALURE_API ALboolean ALURE_APIENTRY
alurePauseSource(ALuint source
)
629 ALCcontext
*current_ctx
= alcGetCurrentContext();
631 if(alGetError() != AL_NO_ERROR
)
633 SetError("Existing OpenAL error");
637 EnterCriticalSection(&cs_StreamPlay
);
639 if((alSourcePause(source
),alGetError()) != AL_NO_ERROR
)
641 SetError("Error pausing source");
642 LeaveCriticalSection(&cs_StreamPlay
);
646 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
647 end
= AsyncPlayList
.end();
650 if(i
->source
== source
&& i
->ctx
== current_ctx
)
658 LeaveCriticalSection(&cs_StreamPlay
);
663 /* Function: alureResumeSource
665 * Resumes the specified source ID after being paused.
670 * *Version Added*: 1.1
675 ALURE_API ALboolean ALURE_APIENTRY
alureResumeSource(ALuint source
)
678 ALCcontext
*current_ctx
= alcGetCurrentContext();
680 if(alGetError() != AL_NO_ERROR
)
682 SetError("Existing OpenAL error");
686 EnterCriticalSection(&cs_StreamPlay
);
688 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
690 SetError("Error playing source");
691 LeaveCriticalSection(&cs_StreamPlay
);
695 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
696 end
= AsyncPlayList
.end();
699 if(i
->source
== source
&& i
->ctx
== current_ctx
)
707 LeaveCriticalSection(&cs_StreamPlay
);
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
722 * <alurePlaySourceStream>, <alurePlaySource>
724 ALURE_API
void ALURE_APIENTRY
alureUpdate(void)
728 EnterCriticalSection(&cs_StreamPlay
);
730 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
731 end
= AsyncPlayList
.end();
734 if(alcSetThreadContext
)
736 if(alcSetThreadContext(i
->ctx
) == ALC_FALSE
)
738 AsyncPlayEntry
ent(*i
);
739 AsyncPlayList
.erase(i
);
743 ent
.eos_callback(ent
.user_data
, ent
.source
);
750 if(i
->stream
== NULL
)
753 alGetSourcei(i
->source
, AL_SOURCE_STATE
, &state
);
754 if(state
== AL_STOPPED
|| state
== AL_INITIAL
)
756 AsyncPlayEntry
ent(*i
);
757 AsyncPlayList
.erase(i
);
759 ent
.eos_callback(ent
.user_data
, ent
.source
);
767 if(i
->Update(&queued
) != AL_PLAYING
)
771 AsyncPlayEntry
ent(*i
);
772 AsyncPlayList
.erase(i
);
774 alSourcei(ent
.source
, AL_BUFFER
, 0);
775 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
779 ent
.eos_callback(ent
.user_data
, ent
.source
);
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>
801 * *Version Added*: 1.1
806 ALURE_API ALboolean ALURE_APIENTRY
alureUpdateInterval(ALfloat interval
)
808 EnterCriticalSection(&cs_StreamPlay
);
811 CurrentInterval
= 0.0f
;
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
);
831 CurrentInterval
= interval
;
833 LeaveCriticalSection(&cs_StreamPlay
);