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 */
33 ALuint (*func
)(ALvoid
*);
38 static DWORD CALLBACK
StarterFunc(void *ptr
)
40 ThreadInfo
*inf
= (ThreadInfo
*)ptr
;
43 ret
= inf
->func(inf
->ptr
);
44 ExitThread((DWORD
)ret
);
49 static ThreadInfo
*StartThread(ALuint (*func
)(ALvoid
*), ALvoid
*ptr
)
52 ThreadInfo
*inf
= new ThreadInfo
;
56 inf
->thread
= CreateThread(NULL
, 0, StarterFunc
, inf
, 0, &dummy
);
66 static ALuint
StopThread(ThreadInfo
*inf
)
68 CloseHandle(inf
->thread
);
77 ALuint (*func
)(ALvoid
*);
83 static void *StarterFunc(void *ptr
)
85 ThreadInfo
*inf
= (ThreadInfo
*)ptr
;
86 inf
->ret
= inf
->func(inf
->ptr
);
90 static ThreadInfo
*StartThread(ALuint (*func
)(ALvoid
*), ALvoid
*ptr
)
92 ThreadInfo
*inf
= new ThreadInfo
;
96 if(pthread_create(&inf
->thread
, NULL
, StarterFunc
, inf
) != 0)
105 static ALuint
StopThread(ThreadInfo
*inf
)
107 pthread_detach(inf
->thread
);
115 struct AsyncPlayEntry
{
118 std::vector
<ALuint
> buffers
;
121 void (*eos_callback
)(void*,ALuint
);
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
*)
147 EnterCriticalSection(&cs_StreamPlay
);
149 if(AsyncPlayList
.size() == 0)
151 StopThread(PlayThreadHandle
);
152 PlayThreadHandle
= NULL
;
153 LeaveCriticalSection(&cs_StreamPlay
);
157 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
158 end
= AsyncPlayList
.end();
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
);
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
) ||
185 ALint size
, channels
, bits
;
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
;
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
;
211 alBufferData(buf
, format
, i
->stream
->dataChunk
, got
, freq
);
212 alSourceQueueBuffers(i
->source
, 1, &buf
);
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
;
224 if(i
->loopcount
== i
->maxloops
)
229 if(i
->maxloops
!= -1 || i
->loopcount
< 1)
231 i
->finished
= !i
->stream
->Rewind();
234 if(state
!= AL_PLAYING
)
238 AsyncPlayEntry
ent(*i
);
239 AsyncPlayList
.erase(i
);
241 alSourcei(ent
.source
, AL_BUFFER
, 0);
242 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
244 ent
.eos_callback(ent
.user_data
, ent
.source
);
248 alSourcePlay(i
->source
);
251 LeaveCriticalSection(&cs_StreamPlay
);
253 alureSleep(0.015625f
);
259 void StopStream(alureStream
*stream
)
261 EnterCriticalSection(&cs_StreamPlay
);
263 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
264 end
= AsyncPlayList
.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]);
278 ent
.eos_callback(ent
.user_data
, ent
.source
);
284 LeaveCriticalSection(&cs_StreamPlay
);
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.
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.
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");
341 if(!alureStream::Verify(stream
))
343 SetError("Invalid stream pointer");
349 SetError("Invalid buffer count");
353 if(!alIsSource(source
))
355 SetError("Invalid source ID");
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
);
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
)
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
;
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");
406 ALuint freq
, blockAlign
;
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
;
419 ALuint buf
= ent
.buffers
[i
];
420 alBufferData(buf
, format
, ent
.stream
->dataChunk
, got
, freq
);
421 alSourceQueueBuffers(ent
.source
, 1, &buf
);
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
;
433 alDeleteBuffers(ent
.buffers
.size(), &ent
.buffers
[0]);
435 LeaveCriticalSection(&cs_StreamPlay
);
436 SetError("Error buffering from stream");
440 if((ALuint
)numBufs
< ent
.buffers
.size())
442 if(ent
.loopcount
== ent
.maxloops
)
446 if(ent
.maxloops
!= -1 || ent
.loopcount
< 1)
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]);
457 LeaveCriticalSection(&cs_StreamPlay
);
458 SetError("Error starting source");
462 AsyncPlayList
.push_front(ent
);
464 LeaveCriticalSection(&cs_StreamPlay
);
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
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
485 * callback - The callback to be called when the source stops.
486 * userdata - An opaque user pointer passed to the callback.
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");
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");
514 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
515 end
= AsyncPlayList
.end();
518 if(i
->source
== source
)
520 SetError("Source is already playing");
521 LeaveCriticalSection(&cs_StreamPlay
);
527 if((alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
529 LeaveCriticalSection(&cs_StreamPlay
);
530 SetError("Error starting source");
538 ent
.eos_callback
= callback
;
539 ent
.user_data
= userdata
;
540 AsyncPlayList
.push_front(ent
);
543 LeaveCriticalSection(&cs_StreamPlay
);
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
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");
570 EnterCriticalSection(&cs_StreamPlay
);
572 if((alSourceStop(source
),alGetError()) != AL_NO_ERROR
)
574 LeaveCriticalSection(&cs_StreamPlay
);
575 SetError("Error stopping source");
579 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
580 end
= AsyncPlayList
.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]);
595 if(run_callback
&& ent
.eos_callback
)
596 ent
.eos_callback(ent
.user_data
, ent
.source
);
602 LeaveCriticalSection(&cs_StreamPlay
);
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.
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");
632 EnterCriticalSection(&cs_StreamPlay
);
634 if(!resume
&& (alSourcePause(source
),alGetError()) != AL_NO_ERROR
)
636 SetError("Error pausing source");
637 LeaveCriticalSection(&cs_StreamPlay
);
640 if(resume
&& (alSourcePlay(source
),alGetError()) != AL_NO_ERROR
)
642 SetError("Error playing source");
643 LeaveCriticalSection(&cs_StreamPlay
);
647 std::list
<AsyncPlayEntry
>::iterator i
= AsyncPlayList
.begin(),
648 end
= AsyncPlayList
.end();
651 if(i
->source
== source
)
659 LeaveCriticalSection(&cs_StreamPlay
);
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.
673 * (alureUInt64)-1 on error.
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
);
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();
701 if(i
->source
== source
)
703 retval
+= i
->base_time
;
705 retval
%= i
->max_time
;
711 LeaveCriticalSection(&cs_StreamPlay
);