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 struct AsyncPlayEntry
{
121 std::vector
<ALuint
> buffers
;
124 void (*eos_callback
)(void*,ALuint
);
129 ALenum stream_format
;
132 AsyncPlayEntry() : source(0), stream(NULL
), loopcount(0), maxloops(0),
133 eos_callback(NULL
), user_data(NULL
), finished(false),
134 paused(false), stream_freq(0), stream_format(AL_NONE
),
137 AsyncPlayEntry(const AsyncPlayEntry
&rhs
)
138 : source(rhs
.source
), stream(rhs
.stream
), buffers(rhs
.buffers
),
139 loopcount(rhs
.loopcount
), maxloops(rhs
.maxloops
),
140 eos_callback(rhs
.eos_callback
), user_data(rhs
.user_data
),
141 finished(rhs
.finished
), paused(rhs
.paused
),
142 stream_freq(rhs
.stream_freq
), stream_format(rhs
.stream_format
),
143 stream_align(rhs
.stream_align
)
146 ALenum
Update(ALint
*queued
)
148 ALint processed
, state
;
150 alGetSourcei(source
, AL_SOURCE_STATE
, &state
);
151 alGetSourcei(source
, AL_BUFFERS_QUEUED
, queued
);
152 alGetSourcei(source
, AL_BUFFERS_PROCESSED
, &processed
);
157 alSourceUnqueueBuffers(source
, 1, &buf
);
163 ALuint got
= stream
->GetData(&stream
->dataChunk
[0], stream
->dataChunk
.size());
164 got
-= got
%stream_align
;
167 alBufferData(buf
, stream_format
, &stream
->dataChunk
[0], got
, stream_freq
);
168 alSourceQueueBuffers(source
, 1, &buf
);
173 if(loopcount
== maxloops
)
180 finished
= !stream
->Rewind();
187 static std::list
<AsyncPlayEntry
> AsyncPlayList
;
188 static ThreadInfo
*PlayThreadHandle
;
190 ALfloat CurrentInterval
= 0.0f
;
192 ALuint
AsyncPlayFunc(ALvoid
*)
194 EnterCriticalSection(&cs_StreamPlay
);
195 while(CurrentInterval
> 0.0f
)
199 ALfloat interval
= CurrentInterval
;
200 LeaveCriticalSection(&cs_StreamPlay
);
201 alureSleep(interval
);
202 EnterCriticalSection(&cs_StreamPlay
);
204 LeaveCriticalSection(&cs_StreamPlay
);
209 void StopStream(alureStream
*stream
)
211 EnterCriticalSection(&cs_StreamPlay
);
213 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
214 end
= AsyncPlayList
.end();
217 if(i
->stream
== stream
)
219 AsyncPlayEntry
ent(*i
);
220 AsyncPlayList
.erase(i
);
222 alSourceStop(ent
.source
);
223 alSourcei(ent
.source
, AL_BUFFER
, 0);
224 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
228 ent
.eos_callback(ent
.user_data
, ent
.source
);
234 LeaveCriticalSection(&cs_StreamPlay
);
240 /* Function: alurePlaySourceStream
242 * Starts playing a stream, using the specified source ID. A stream can only be
243 * played if it is not already playing. You must call <alureUpdate> at regular
244 * intervals to keep the stream playing, or else the stream will underrun and
245 * cause a break in the playback until an update call can restart it. It also
246 * is important that the current context is kept for <alureUpdate> calls,
247 * otherwise the method may start calling OpenAL with invalid IDs. Note that
248 * checking the state of the specified source is not a good method to determine
249 * if a stream is playing. If an underrun occurs, the source will enter a
250 * stopped state until it is automatically restarted. Instead, set a flag using
251 * the callback to indicate the stream being stopped.
254 * source - The source ID to play the stream with. Any buffers on the source
255 * will be unqueued. It is valid to set source properties not related
256 * to the buffer queue or playback state (ie. you may change the
257 * source's position, pitch, gain, etc, but you must not stop the
258 * source or queue/unqueue buffers on it). To pause the source, call
259 * <alurePauseSource>.
260 * stream - The stream to play. Any valid stream will work, although looping
261 * will only work if the stream can be rewound (eg. streams made with
262 * <alureCreateStreamFromCallback> cannot loop, but will play for as
263 * long as the callback provides data).
264 * numBufs - The number of buffers used to queue with the OpenAL source. Each
265 * buffer will be filled with the chunk length specified when the
266 * stream was created. This value must be at least 2. More buffers at
267 * a larger size will decrease the time needed between updates, but
268 * at the cost of more memory usage.
269 * loopcount - The number of times to loop the stream. When the stream reaches
270 * the end of processing, it will be rewound to continue buffering
271 * data. A value of -1 will cause the stream to loop indefinitely
272 * (or until <alureStopSource> is called).
273 * eos_callback - This callback will be called when the stream reaches the end,
274 * no more loops are pending, and the source reaches a stopped
275 * state. It will also be called if an error occured and
276 * playback terminated.
277 * userdata - An opaque user pointer passed to the callback.
282 * *Version Added*: 1.1
285 * <alureStopSource>, <alurePauseSource>, <alureGetSourceOffset>, <alureUpdate>
287 ALURE_API ALboolean ALURE_APIENTRY
alurePlaySourceStream(ALuint source
,
288 alureStream
*stream
, ALsizei numBufs
, ALsizei loopcount
,
289 void (*eos_callback
)(void *userdata
, ALuint source
), void *userdata
)
291 if(alGetError() != AL_NO_ERROR
)
293 SetError("Existing OpenAL error");
297 if(!alureStream::Verify(stream
))
299 SetError("Invalid stream pointer");
305 SetError("Invalid buffer count");
309 if(!alIsSource(source
))
311 SetError("Invalid source ID");
315 EnterCriticalSection(&cs_StreamPlay
);
317 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
318 end
= AsyncPlayList
.end();
321 if(i
->stream
== stream
)
323 SetError("Stream is already playing");
324 LeaveCriticalSection(&cs_StreamPlay
);
327 if(i
->source
== source
)
329 SetError("Source is already playing");
330 LeaveCriticalSection(&cs_StreamPlay
);
339 ent
.maxloops
= loopcount
;
340 ent
.eos_callback
= eos_callback
;
341 ent
.user_data
= userdata
;
343 ent
.buffers
.resize(numBufs
);
344 alGenBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
345 if(alGetError() != AL_NO_ERROR
)
347 LeaveCriticalSection(&cs_StreamPlay
);
348 SetError("Error generating buffers");
353 if(ent
.stream
->GetFormat(&ent
.stream_format
, &ent
.stream_freq
, &ent
.stream_align
))
355 for(size_t i
= 0;i
< ent
.buffers
.size();i
++)
357 ALuint got
= ent
.stream
->GetData(&ent
.stream
->dataChunk
[0],
358 ent
.stream
->dataChunk
.size());
359 got
-= got
%ent
.stream_align
;
362 if(ent
.loopcount
== ent
.maxloops
|| i
== 0)
366 if(ent
.maxloops
!= -1)
368 ent
.finished
= !ent
.stream
->Rewind();
375 ALuint buf
= ent
.buffers
[i
];
376 alBufferData(buf
, ent
.stream_format
, &ent
.stream
->dataChunk
[0], got
, ent
.stream_freq
);
382 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
384 LeaveCriticalSection(&cs_StreamPlay
);
385 SetError("Error buffering from stream");
389 if((alSourcei(source
, AL_LOOPING
, AL_FALSE
),
390 alSourcei(source
, AL_BUFFER
, 0),alGetError()) != AL_NO_ERROR
||
391 (alSourceQueueBuffers(source
, numBufs
, &ent
.buffers
[0]),
392 alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
394 alSourcei(source
, AL_BUFFER
, 0);
395 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
397 LeaveCriticalSection(&cs_StreamPlay
);
398 SetError("Error starting source");
402 AsyncPlayList
.push_front(ent
);
404 LeaveCriticalSection(&cs_StreamPlay
);
409 /* Function: alurePlaySource
411 * Plays the specified source ID and watches for it to stop. When a source
412 * enters an AL_STOPPED state, the specified callback will be called by
413 * <alureUpdate> to alert the application. As with <alurePlaySourceStream>, the
414 * current context must not be changed while the source is being watched
415 * (before the callback is called or <alureStopSource> is called). It also must
416 * not be deleted while being watched.
419 * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
420 * to set source properties not related to the playback state (ie. you
421 * may change a source's position, pitch, gain, etc). Pausing a source
422 * and restarting a paused source is allowed, and the callback will
423 * still be invoked when the source naturally reaches an AL_STOPPED
425 * callback - The callback to be called when the source stops.
426 * userdata - An opaque user pointer passed to the callback.
431 * *Version Added*: 1.1
434 * <alureStopSource>, <alureUpdate>
436 ALURE_API ALboolean ALURE_APIENTRY
alurePlaySource(ALuint source
,
437 void (*callback
)(void *userdata
, ALuint source
), void *userdata
)
439 if(alGetError() != AL_NO_ERROR
)
441 SetError("Existing OpenAL error");
445 EnterCriticalSection(&cs_StreamPlay
);
447 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
448 end
= AsyncPlayList
.end();
451 if(i
->source
== source
)
453 SetError("Source is already playing");
454 LeaveCriticalSection(&cs_StreamPlay
);
460 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
462 LeaveCriticalSection(&cs_StreamPlay
);
463 SetError("Error starting source");
471 ent
.eos_callback
= callback
;
472 ent
.user_data
= userdata
;
473 AsyncPlayList
.push_front(ent
);
476 LeaveCriticalSection(&cs_StreamPlay
);
481 /* Function: alureStopSource
483 * Stops the specified source ID, and any associated stream. The previously
484 * specified callback will be invoked if 'run_callback' is not AL_FALSE.
485 * Sources that were not started with <alurePlaySourceStream> or
486 * <alurePlaySource> will still be stopped, but will not have any callback
492 * *Version Added*: 1.1
495 * <alurePlaySourceStream>, <alurePlaySource>
497 ALURE_API ALboolean ALURE_APIENTRY
alureStopSource(ALuint source
, ALboolean run_callback
)
499 if(alGetError() != AL_NO_ERROR
)
501 SetError("Existing OpenAL error");
505 EnterCriticalSection(&cs_StreamPlay
);
507 if((alSourceStop(source
),alGetError()) != AL_NO_ERROR
)
509 LeaveCriticalSection(&cs_StreamPlay
);
510 SetError("Error stopping source");
514 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
515 end
= AsyncPlayList
.end();
518 if(i
->source
== source
)
520 AsyncPlayEntry
ent(*i
);
521 AsyncPlayList
.erase(i
);
523 if(ent
.buffers
.size() > 0)
525 alSourcei(ent
.source
, AL_BUFFER
, 0);
526 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
530 if(run_callback
&& ent
.eos_callback
)
531 ent
.eos_callback(ent
.user_data
, ent
.source
);
537 LeaveCriticalSection(&cs_StreamPlay
);
542 /* Function: alurePauseSource
544 * Pauses the specified source ID, and any associated stream. This is needed to
545 * avoid potential race conditions with sources that are playing a stream.
547 * Note that it is possible for the specified source to become stopped, and any
548 * associated stream to finish, before this function is called, causing the
549 * callback to be delayed until after the function returns and <alureUpdate>
550 * detects the stopped source.
555 * *Version Added*: 1.1
558 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
560 ALURE_API ALboolean ALURE_APIENTRY
alurePauseSource(ALuint source
)
562 if(alGetError() != AL_NO_ERROR
)
564 SetError("Existing OpenAL error");
568 EnterCriticalSection(&cs_StreamPlay
);
570 if((alSourcePause(source
),alGetError()) != AL_NO_ERROR
)
572 SetError("Error pausing source");
573 LeaveCriticalSection(&cs_StreamPlay
);
577 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
578 end
= AsyncPlayList
.end();
581 if(i
->source
== source
)
589 LeaveCriticalSection(&cs_StreamPlay
);
594 /* Function: alureResumeSource
596 * Resumes the specified source ID after being paused.
601 * *Version Added*: 1.1
606 ALURE_API ALboolean ALURE_APIENTRY
alureResumeSource(ALuint source
)
608 if(alGetError() != AL_NO_ERROR
)
610 SetError("Existing OpenAL error");
614 EnterCriticalSection(&cs_StreamPlay
);
616 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
618 SetError("Error playing source");
619 LeaveCriticalSection(&cs_StreamPlay
);
623 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
624 end
= AsyncPlayList
.end();
627 if(i
->source
== source
)
635 LeaveCriticalSection(&cs_StreamPlay
);
640 /* Function: alureUpdate
642 * Updates the running list of streams, and checks for stopped sources. This
643 * makes sure that sources played with <alurePlaySourceStream> are kept fed
644 * from their associated stream, and sources played with <alurePlaySource> are
645 * still playing. It will call their callbacks as needed.
647 * *Version Added*: 1.1
650 * <alurePlaySourceStream>, <alurePlaySource>
652 ALURE_API
void ALURE_APIENTRY
alureUpdate(void)
654 EnterCriticalSection(&cs_StreamPlay
);
656 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
657 end
= AsyncPlayList
.end();
660 if(i
->stream
== NULL
)
663 alGetSourcei(i
->source
, AL_SOURCE_STATE
, &state
);
664 if(state
!= AL_PLAYING
&& state
!= AL_PAUSED
)
666 AsyncPlayEntry
ent(*i
);
667 AsyncPlayList
.erase(i
);
668 ent
.eos_callback(ent
.user_data
, ent
.source
);
675 if(i
->Update(&queued
) != AL_PLAYING
)
679 AsyncPlayEntry
ent(*i
);
680 AsyncPlayList
.erase(i
);
682 alSourcei(ent
.source
, AL_BUFFER
, 0);
683 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
685 ent
.eos_callback(ent
.user_data
, ent
.source
);
689 alSourcePlay(i
->source
);
692 LeaveCriticalSection(&cs_StreamPlay
);
695 /* Function: alureUpdateInterval
697 * Sets up a timer or thread to automatically call <alureUpdate> at the given
698 * interval, in seconds. If the timer or thread is already running, the update
699 * interval will be modified. A 0 or negative interval will stop <alureUpdate>
705 * *Version Added*: 1.1
710 ALURE_API ALboolean ALURE_APIENTRY
alureUpdateInterval(ALfloat interval
)
712 EnterCriticalSection(&cs_StreamPlay
);
715 CurrentInterval
= 0.0f
;
718 ThreadInfo
*threadinf
= PlayThreadHandle
;
719 PlayThreadHandle
= NULL
;
720 LeaveCriticalSection(&cs_StreamPlay
);
721 StopThread(threadinf
);
722 EnterCriticalSection(&cs_StreamPlay
);
727 if(!PlayThreadHandle
)
728 PlayThreadHandle
= StartThread(AsyncPlayFunc
, NULL
);
729 if(!PlayThreadHandle
)
731 SetError("Error starting async thread");
732 LeaveCriticalSection(&cs_StreamPlay
);
735 CurrentInterval
= interval
;
737 LeaveCriticalSection(&cs_StreamPlay
);