Don't over-allocate the active effect slot array
[openal-soft.git] / examples / alstream.c
blob028290f554f82b18be7dfd0b22128140e9afa19a
1 /*
2 * OpenAL Audio Stream Example
4 * Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 /* This file contains a relatively simple streaming audio player. */
27 #include <assert.h>
28 #include <inttypes.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
33 #include "sndfile.h"
35 #include "AL/al.h"
36 #include "AL/alext.h"
38 #include "common/alhelpers.h"
40 #include "win_main_utf8.h"
43 /* Define the number of buffers and buffer size (in milliseconds) to use. 4
44 * buffers at 200ms each gives a nice per-chunk size, and lets the queue last
45 * for almost one second.
47 enum { NumBuffers = 4 };
48 enum { BufferMillisec = 200 };
50 typedef enum SampleType {
51 Int16, Float, IMA4, MSADPCM
52 } SampleType;
54 typedef struct StreamPlayer {
55 /* These are the buffers and source to play out through OpenAL with. */
56 ALuint buffers[NumBuffers];
57 ALuint source;
59 /* Handle for the audio file */
60 SNDFILE *sndfile;
61 SF_INFO sfinfo;
62 void *membuf;
64 /* The sample type and block/frame size being read for the buffer. */
65 SampleType sample_type;
66 int byteblockalign;
67 int sampleblockalign;
68 int block_count;
70 /* The format of the output stream (sample rate is in sfinfo) */
71 ALenum format;
72 } StreamPlayer;
74 static StreamPlayer *NewPlayer(void);
75 static void DeletePlayer(StreamPlayer *player);
76 static int OpenPlayerFile(StreamPlayer *player, const char *filename);
77 static void ClosePlayerFile(StreamPlayer *player);
78 static int StartPlayer(StreamPlayer *player);
79 static int UpdatePlayer(StreamPlayer *player);
81 /* Creates a new player object, and allocates the needed OpenAL source and
82 * buffer objects. Error checking is simplified for the purposes of this
83 * example, and will cause an abort if needed.
85 static StreamPlayer *NewPlayer(void)
87 StreamPlayer *player;
89 player = calloc(1, sizeof(*player));
90 assert(player != NULL);
92 /* Generate the buffers and source */
93 alGenBuffers(NumBuffers, player->buffers);
94 assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
96 alGenSources(1, &player->source);
97 assert(alGetError() == AL_NO_ERROR && "Could not create source");
99 /* Set parameters so mono sources play out the front-center speaker and
100 * won't distance attenuate. */
101 alSource3i(player->source, AL_POSITION, 0, 0, -1);
102 alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
103 alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
104 assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
106 return player;
109 /* Destroys a player object, deleting the source and buffers. No error handling
110 * since these calls shouldn't fail with a properly-made player object. */
111 static void DeletePlayer(StreamPlayer *player)
113 ClosePlayerFile(player);
115 alDeleteSources(1, &player->source);
116 alDeleteBuffers(NumBuffers, player->buffers);
117 if(alGetError() != AL_NO_ERROR)
118 fprintf(stderr, "Failed to delete object IDs\n");
120 memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */
121 free(player);
125 /* Opens the first audio stream of the named file. If a file is already open,
126 * it will be closed first. */
127 static int OpenPlayerFile(StreamPlayer *player, const char *filename)
129 int byteblockalign=0, splblockalign=0;
131 ClosePlayerFile(player);
133 /* Open the audio file and check that it's usable. */
134 player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo);
135 if(!player->sndfile)
137 fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL));
138 return 0;
141 /* Detect a suitable format to load. Formats like Vorbis and Opus use float
142 * natively, so load as float to avoid clipping when possible. Formats
143 * larger than 16-bit can also use float to preserve a bit more precision.
145 switch((player->sfinfo.format&SF_FORMAT_SUBMASK))
147 case SF_FORMAT_PCM_24:
148 case SF_FORMAT_PCM_32:
149 case SF_FORMAT_FLOAT:
150 case SF_FORMAT_DOUBLE:
151 case SF_FORMAT_VORBIS:
152 case SF_FORMAT_OPUS:
153 case SF_FORMAT_ALAC_20:
154 case SF_FORMAT_ALAC_24:
155 case SF_FORMAT_ALAC_32:
156 case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/:
157 case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/:
158 case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/:
159 if(alIsExtensionPresent("AL_EXT_FLOAT32"))
160 player->sample_type = Float;
161 break;
162 case SF_FORMAT_IMA_ADPCM:
163 /* ADPCM formats require setting a block alignment as specified in the
164 * file, which needs to be read from the wave 'fmt ' chunk manually
165 * since libsndfile doesn't provide it in a format-agnostic way.
167 if(player->sfinfo.channels <= 2
168 && (player->sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV
169 && alIsExtensionPresent("AL_EXT_IMA4")
170 && alIsExtensionPresent("AL_SOFT_block_alignment"))
171 player->sample_type = IMA4;
172 break;
173 case SF_FORMAT_MS_ADPCM:
174 if(player->sfinfo.channels <= 2
175 && (player->sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV
176 && alIsExtensionPresent("AL_SOFT_MSADPCM")
177 && alIsExtensionPresent("AL_SOFT_block_alignment"))
178 player->sample_type = MSADPCM;
179 break;
182 if(player->sample_type == IMA4 || player->sample_type == MSADPCM)
184 /* For ADPCM, lookup the wave file's "fmt " chunk, which is a
185 * WAVEFORMATEX-based structure for the audio format.
187 SF_CHUNK_INFO inf = { "fmt ", 4, 0, NULL };
188 SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(player->sndfile, &inf);
190 /* If there's an issue getting the chunk or block alignment, load as
191 * 16-bit and have libsndfile do the conversion.
193 if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14)
194 player->sample_type = Int16;
195 else
197 ALubyte *fmtbuf = calloc(inf.datalen, 1);
198 inf.data = fmtbuf;
199 if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR)
200 player->sample_type = Int16;
201 else
203 /* Read the nBlockAlign field, and convert from bytes- to
204 * samples-per-block (verifying it's valid by converting back
205 * and comparing to the original value).
207 byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8);
208 if(player->sample_type == IMA4)
210 splblockalign = (byteblockalign/player->sfinfo.channels - 4)/4*8 + 1;
211 if(splblockalign < 1
212 || ((splblockalign-1)/2 + 4)*player->sfinfo.channels != byteblockalign)
213 player->sample_type = Int16;
215 else
217 splblockalign = (byteblockalign/player->sfinfo.channels - 7)*2 + 2;
218 if(splblockalign < 2
219 || ((splblockalign-2)/2 + 7)*player->sfinfo.channels != byteblockalign)
220 player->sample_type = Int16;
223 free(fmtbuf);
227 if(player->sample_type == Int16)
229 player->sampleblockalign = 1;
230 player->byteblockalign = player->sfinfo.channels * 2;
232 else if(player->sample_type == Float)
234 player->sampleblockalign = 1;
235 player->byteblockalign = player->sfinfo.channels * 4;
237 else
239 player->sampleblockalign = splblockalign;
240 player->byteblockalign = byteblockalign;
243 /* Figure out the OpenAL format from the file and desired sample type. */
244 player->format = AL_NONE;
245 if(player->sfinfo.channels == 1)
247 if(player->sample_type == Int16)
248 player->format = AL_FORMAT_MONO16;
249 else if(player->sample_type == Float)
250 player->format = AL_FORMAT_MONO_FLOAT32;
251 else if(player->sample_type == IMA4)
252 player->format = AL_FORMAT_MONO_IMA4;
253 else if(player->sample_type == MSADPCM)
254 player->format = AL_FORMAT_MONO_MSADPCM_SOFT;
256 else if(player->sfinfo.channels == 2)
258 if(player->sample_type == Int16)
259 player->format = AL_FORMAT_STEREO16;
260 else if(player->sample_type == Float)
261 player->format = AL_FORMAT_STEREO_FLOAT32;
262 else if(player->sample_type == IMA4)
263 player->format = AL_FORMAT_STEREO_IMA4;
264 else if(player->sample_type == MSADPCM)
265 player->format = AL_FORMAT_STEREO_MSADPCM_SOFT;
267 else if(player->sfinfo.channels == 3)
269 if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
271 if(player->sample_type == Int16)
272 player->format = AL_FORMAT_BFORMAT2D_16;
273 else if(player->sample_type == Float)
274 player->format = AL_FORMAT_BFORMAT2D_FLOAT32;
277 else if(player->sfinfo.channels == 4)
279 if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
281 if(player->sample_type == Int16)
282 player->format = AL_FORMAT_BFORMAT3D_16;
283 else if(player->sample_type == Float)
284 player->format = AL_FORMAT_BFORMAT3D_FLOAT32;
287 if(!player->format)
289 fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels);
290 sf_close(player->sndfile);
291 player->sndfile = NULL;
292 return 0;
295 player->block_count = player->sfinfo.samplerate / player->sampleblockalign;
296 player->block_count = player->block_count * BufferMillisec / 1000;
297 player->membuf = malloc((size_t)player->block_count * (size_t)player->byteblockalign);
299 return 1;
302 /* Closes the audio file stream */
303 static void ClosePlayerFile(StreamPlayer *player)
305 if(player->sndfile)
306 sf_close(player->sndfile);
307 player->sndfile = NULL;
309 free(player->membuf);
310 player->membuf = NULL;
312 if(player->sampleblockalign > 1)
314 ALsizei i;
315 for(i = 0;i < NumBuffers;i++)
316 alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0);
317 player->sampleblockalign = 0;
318 player->byteblockalign = 0;
323 /* Prebuffers some audio from the file, and starts playing the source */
324 static int StartPlayer(StreamPlayer *player)
326 ALsizei i;
328 /* Rewind the source position and clear the buffer queue */
329 alSourceRewind(player->source);
330 alSourcei(player->source, AL_BUFFER, 0);
332 /* Fill the buffer queue */
333 for(i = 0;i < NumBuffers;i++)
335 sf_count_t slen;
337 /* Get some data to give it to the buffer */
338 if(player->sample_type == Int16)
340 slen = sf_readf_short(player->sndfile, player->membuf,
341 (sf_count_t)player->block_count * player->sampleblockalign);
342 if(slen < 1) break;
343 slen *= player->byteblockalign;
345 else if(player->sample_type == Float)
347 slen = sf_readf_float(player->sndfile, player->membuf,
348 (sf_count_t)player->block_count * player->sampleblockalign);
349 if(slen < 1) break;
350 slen *= player->byteblockalign;
352 else
354 slen = sf_read_raw(player->sndfile, player->membuf,
355 (sf_count_t)player->block_count * player->byteblockalign);
356 if(slen > 0) slen -= slen%player->byteblockalign;
357 if(slen < 1) break;
360 if(player->sampleblockalign > 1)
361 alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT,
362 player->sampleblockalign);
364 alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen,
365 player->sfinfo.samplerate);
367 if(alGetError() != AL_NO_ERROR)
369 fprintf(stderr, "Error buffering for playback\n");
370 return 0;
373 /* Now queue and start playback! */
374 alSourceQueueBuffers(player->source, i, player->buffers);
375 alSourcePlay(player->source);
376 if(alGetError() != AL_NO_ERROR)
378 fprintf(stderr, "Error starting playback\n");
379 return 0;
382 return 1;
385 static int UpdatePlayer(StreamPlayer *player)
387 ALint processed, state;
389 /* Get relevant source info */
390 alGetSourcei(player->source, AL_SOURCE_STATE, &state);
391 alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
392 if(alGetError() != AL_NO_ERROR)
394 fprintf(stderr, "Error checking source state\n");
395 return 0;
398 /* Unqueue and handle each processed buffer */
399 while(processed > 0)
401 ALuint bufid;
402 sf_count_t slen;
404 alSourceUnqueueBuffers(player->source, 1, &bufid);
405 processed--;
407 /* Read the next chunk of data, refill the buffer, and queue it
408 * back on the source */
409 if(player->sample_type == Int16)
411 slen = sf_readf_short(player->sndfile, player->membuf,
412 (sf_count_t)player->block_count * player->sampleblockalign);
413 if(slen > 0) slen *= player->byteblockalign;
415 else if(player->sample_type == Float)
417 slen = sf_readf_float(player->sndfile, player->membuf,
418 (sf_count_t)player->block_count * player->sampleblockalign);
419 if(slen > 0) slen *= player->byteblockalign;
421 else
423 slen = sf_read_raw(player->sndfile, player->membuf,
424 (sf_count_t)player->block_count * player->byteblockalign);
425 if(slen > 0) slen -= slen%player->byteblockalign;
428 if(slen > 0)
430 alBufferData(bufid, player->format, player->membuf, (ALsizei)slen,
431 player->sfinfo.samplerate);
432 alSourceQueueBuffers(player->source, 1, &bufid);
434 if(alGetError() != AL_NO_ERROR)
436 fprintf(stderr, "Error buffering data\n");
437 return 0;
441 /* Make sure the source hasn't underrun */
442 if(state != AL_PLAYING && state != AL_PAUSED)
444 ALint queued;
446 /* If no buffers are queued, playback is finished */
447 alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued);
448 if(queued == 0)
449 return 0;
451 alSourcePlay(player->source);
452 if(alGetError() != AL_NO_ERROR)
454 fprintf(stderr, "Error restarting playback\n");
455 return 0;
459 return 1;
463 int main(int argc, char **argv)
465 StreamPlayer *player;
466 int i;
468 /* Print out usage if no arguments were specified */
469 if(argc < 2)
471 fprintf(stderr, "Usage: %s [-device <name>] <filenames...>\n", argv[0]);
472 return 1;
475 argv++; argc--;
476 if(InitAL(&argv, &argc) != 0)
477 return 1;
479 player = NewPlayer();
481 /* Play each file listed on the command line */
482 for(i = 0;i < argc;i++)
484 const char *namepart;
486 if(!OpenPlayerFile(player, argv[i]))
487 continue;
489 /* Get the name portion, without the path, for display. */
490 namepart = strrchr(argv[i], '/');
491 if(!namepart) namepart = strrchr(argv[i], '\\');
492 if(!namepart) namepart = argv[i];
493 else namepart++;
495 printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format),
496 player->sfinfo.samplerate);
497 fflush(stdout);
499 if(!StartPlayer(player))
501 ClosePlayerFile(player);
502 continue;
505 while(UpdatePlayer(player))
506 al_nssleep(10000000);
508 /* All done with this file. Close it and go to the next */
509 ClosePlayerFile(player);
511 printf("Done.\n");
513 /* All files done. Delete the player, and close down OpenAL */
514 DeletePlayer(player);
515 player = NULL;
517 CloseAL();
519 return 0;