Allow unknown block alignments, and multiples of known block alignments
[alure.git] / src / streamplay.cpp
blob9c643e1f83feb2c4da4fc90ed969d59c1d21d1a2
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 struct AsyncPlayEntry {
119 ALuint source;
120 alureStream *stream;
121 std::vector<ALuint> buffers;
122 ALsizei loopcount;
123 ALsizei maxloops;
124 void (*eos_callback)(void*,ALuint);
125 void *user_data;
126 bool finished;
127 bool paused;
128 ALuint stream_freq;
129 ALenum stream_format;
130 ALuint stream_align;
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),
135 stream_align(0)
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);
153 while(processed > 0)
155 ALuint buf;
157 alSourceUnqueueBuffers(source, 1, &buf);
158 processed--;
159 (*queued)--;
161 while(!finished)
163 ALuint got = stream->GetData(&stream->dataChunk[0], stream->dataChunk.size());
164 got -= got%stream_align;
165 if(got > 0)
167 alBufferData(buf, stream_format, &stream->dataChunk[0], got, stream_freq);
168 alSourceQueueBuffers(source, 1, &buf);
169 (*queued)++;
171 break;
173 if(loopcount == maxloops)
175 finished = true;
176 break;
178 if(maxloops != -1)
179 loopcount++;
180 finished = !stream->Rewind();
184 return state;
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)
197 alureUpdate();
199 ALfloat interval = CurrentInterval;
200 LeaveCriticalSection(&cs_StreamPlay);
201 alureSleep(interval);
202 EnterCriticalSection(&cs_StreamPlay);
204 LeaveCriticalSection(&cs_StreamPlay);
205 return 0;
209 void StopStream(alureStream *stream)
211 EnterCriticalSection(&cs_StreamPlay);
213 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
214 end = AsyncPlayList.end();
215 while(i != 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]);
225 alGetError();
227 if(ent.eos_callback)
228 ent.eos_callback(ent.user_data, ent.source);
229 break;
231 i++;
234 LeaveCriticalSection(&cs_StreamPlay);
238 extern "C" {
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.
253 * Parameters:
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.
279 * Returns:
280 * AL_FALSE on error.
282 * *Version Added*: 1.1
284 * See Also:
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");
294 return AL_FALSE;
297 if(!alureStream::Verify(stream))
299 SetError("Invalid stream pointer");
300 return AL_FALSE;
303 if(numBufs < 2)
305 SetError("Invalid buffer count");
306 return AL_FALSE;
309 if(!alIsSource(source))
311 SetError("Invalid source ID");
312 return AL_FALSE;
315 EnterCriticalSection(&cs_StreamPlay);
317 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
318 end = AsyncPlayList.end();
319 while(i != end)
321 if(i->stream == stream)
323 SetError("Stream is already playing");
324 LeaveCriticalSection(&cs_StreamPlay);
325 return AL_FALSE;
327 if(i->source == source)
329 SetError("Source is already playing");
330 LeaveCriticalSection(&cs_StreamPlay);
331 return AL_FALSE;
333 i++;
336 AsyncPlayEntry ent;
337 ent.stream = stream;
338 ent.source = source;
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");
349 return AL_FALSE;
352 numBufs = 0;
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;
360 if(got <= 0)
362 if(ent.loopcount == ent.maxloops || i == 0)
363 ent.finished = true;
364 else
366 if(ent.maxloops != -1)
367 ent.loopcount++;
368 ent.finished = !ent.stream->Rewind();
370 if(ent.finished)
371 break;
372 i--;
373 continue;
375 ALuint buf = ent.buffers[i];
376 alBufferData(buf, ent.stream_format, &ent.stream->dataChunk[0], got, ent.stream_freq);
377 numBufs++;
380 if(numBufs == 0)
382 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
383 alGetError();
384 LeaveCriticalSection(&cs_StreamPlay);
385 SetError("Error buffering from stream");
386 return AL_FALSE;
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]);
396 alGetError();
397 LeaveCriticalSection(&cs_StreamPlay);
398 SetError("Error starting source");
399 return AL_FALSE;
402 AsyncPlayList.push_front(ent);
404 LeaveCriticalSection(&cs_StreamPlay);
406 return AL_TRUE;
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.
418 * Parameters:
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
424 * state.
425 * callback - The callback to be called when the source stops.
426 * userdata - An opaque user pointer passed to the callback.
428 * Returns:
429 * AL_FALSE on error.
431 * *Version Added*: 1.1
433 * See Also:
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");
442 return AL_FALSE;
445 EnterCriticalSection(&cs_StreamPlay);
447 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
448 end = AsyncPlayList.end();
449 while(i != end)
451 if(i->source == source)
453 SetError("Source is already playing");
454 LeaveCriticalSection(&cs_StreamPlay);
455 return AL_FALSE;
457 i++;
460 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
462 LeaveCriticalSection(&cs_StreamPlay);
463 SetError("Error starting source");
464 return AL_FALSE;
467 if(callback != NULL)
469 AsyncPlayEntry ent;
470 ent.source = source;
471 ent.eos_callback = callback;
472 ent.user_data = userdata;
473 AsyncPlayList.push_front(ent);
476 LeaveCriticalSection(&cs_StreamPlay);
478 return AL_TRUE;
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
487 * called for them.
489 * Returns:
490 * AL_FALSE on error.
492 * *Version Added*: 1.1
494 * See Also:
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");
502 return AL_FALSE;
505 EnterCriticalSection(&cs_StreamPlay);
507 if((alSourceStop(source),alGetError()) != AL_NO_ERROR)
509 LeaveCriticalSection(&cs_StreamPlay);
510 SetError("Error stopping source");
511 return AL_FALSE;
514 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
515 end = AsyncPlayList.end();
516 while(i != 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]);
527 alGetError();
530 if(run_callback && ent.eos_callback)
531 ent.eos_callback(ent.user_data, ent.source);
532 break;
534 i++;
537 LeaveCriticalSection(&cs_StreamPlay);
539 return AL_TRUE;
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.
552 * Returns:
553 * AL_FALSE on error.
555 * *Version Added*: 1.1
557 * See Also:
558 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
560 ALURE_API ALboolean ALURE_APIENTRY alurePauseSource(ALuint source)
562 if(alGetError() != AL_NO_ERROR)
564 SetError("Existing OpenAL error");
565 return AL_FALSE;
568 EnterCriticalSection(&cs_StreamPlay);
570 if((alSourcePause(source),alGetError()) != AL_NO_ERROR)
572 SetError("Error pausing source");
573 LeaveCriticalSection(&cs_StreamPlay);
574 return AL_FALSE;
577 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
578 end = AsyncPlayList.end();
579 while(i != end)
581 if(i->source == source)
583 i->paused = true;
584 break;
586 i++;
589 LeaveCriticalSection(&cs_StreamPlay);
591 return AL_TRUE;
594 /* Function: alureResumeSource
596 * Resumes the specified source ID after being paused.
598 * Returns:
599 * AL_FALSE on error.
601 * *Version Added*: 1.1
603 * See Also:
604 * <alurePauseSource>
606 ALURE_API ALboolean ALURE_APIENTRY alureResumeSource(ALuint source)
608 if(alGetError() != AL_NO_ERROR)
610 SetError("Existing OpenAL error");
611 return AL_FALSE;
614 EnterCriticalSection(&cs_StreamPlay);
616 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
618 SetError("Error playing source");
619 LeaveCriticalSection(&cs_StreamPlay);
620 return AL_FALSE;
623 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
624 end = AsyncPlayList.end();
625 while(i != end)
627 if(i->source == source)
629 i->paused = false;
630 break;
632 i++;
635 LeaveCriticalSection(&cs_StreamPlay);
637 return AL_TRUE;
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
649 * See Also:
650 * <alurePlaySourceStream>, <alurePlaySource>
652 ALURE_API void ALURE_APIENTRY alureUpdate(void)
654 EnterCriticalSection(&cs_StreamPlay);
655 restart:
656 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
657 end = AsyncPlayList.end();
658 for(;i != end;i++)
660 if(i->stream == NULL)
662 ALint state;
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);
669 goto restart;
671 continue;
674 ALint queued;
675 if(i->Update(&queued) != AL_PLAYING)
677 if(queued == 0)
679 AsyncPlayEntry ent(*i);
680 AsyncPlayList.erase(i);
682 alSourcei(ent.source, AL_BUFFER, 0);
683 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
684 if(ent.eos_callback)
685 ent.eos_callback(ent.user_data, ent.source);
686 goto restart;
688 if(!i->paused)
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>
700 * from being called.
702 * Returns:
703 * AL_FALSE on error.
705 * *Version Added*: 1.1
707 * See Also:
708 * <alureUpdate>
710 ALURE_API ALboolean ALURE_APIENTRY alureUpdateInterval(ALfloat interval)
712 EnterCriticalSection(&cs_StreamPlay);
713 if(interval <= 0.0f)
715 CurrentInterval = 0.0f;
716 if(PlayThreadHandle)
718 ThreadInfo *threadinf = PlayThreadHandle;
719 PlayThreadHandle = NULL;
720 LeaveCriticalSection(&cs_StreamPlay);
721 StopThread(threadinf);
722 EnterCriticalSection(&cs_StreamPlay);
725 else
727 if(!PlayThreadHandle)
728 PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
729 if(!PlayThreadHandle)
731 SetError("Error starting async thread");
732 LeaveCriticalSection(&cs_StreamPlay);
733 return AL_FALSE;
735 CurrentInterval = interval;
737 LeaveCriticalSection(&cs_StreamPlay);
739 return AL_TRUE;
742 } // extern "C"