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
25 /* This file contains a relatively simple streaming audio player. */
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
54 typedef struct StreamPlayer
{
55 /* These are the buffers and source to play out through OpenAL with. */
56 ALuint buffers
[NumBuffers
];
59 /* Handle for the audio file */
64 /* The sample type and block/frame size being read for the buffer. */
65 SampleType sample_type
;
70 /* The format of the output stream (sample rate is in sfinfo) */
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)
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");
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.*) */
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
);
137 fprintf(stderr
, "Could not open audio in %s: %s\n", filename
, sf_strerror(NULL
));
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
:
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
;
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
;
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
;
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
;
197 ALubyte
*fmtbuf
= calloc(inf
.datalen
, 1);
199 if(sf_get_chunk_data(iter
, &inf
) != SF_ERR_NO_ERROR
)
200 player
->sample_type
= Int16
;
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;
212 || ((splblockalign
-1)/2 + 4)*player
->sfinfo
.channels
!= byteblockalign
)
213 player
->sample_type
= Int16
;
217 splblockalign
= (byteblockalign
/player
->sfinfo
.channels
- 7)*2 + 2;
219 || ((splblockalign
-2)/2 + 7)*player
->sfinfo
.channels
!= byteblockalign
)
220 player
->sample_type
= Int16
;
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;
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
;
289 fprintf(stderr
, "Unsupported channel count: %d\n", player
->sfinfo
.channels
);
290 sf_close(player
->sndfile
);
291 player
->sndfile
= NULL
;
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
);
302 /* Closes the audio file stream */
303 static void ClosePlayerFile(StreamPlayer
*player
)
306 sf_close(player
->sndfile
);
307 player
->sndfile
= NULL
;
309 free(player
->membuf
);
310 player
->membuf
= NULL
;
312 if(player
->sampleblockalign
> 1)
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
)
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
++)
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
);
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
);
350 slen
*= player
->byteblockalign
;
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
;
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");
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");
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");
398 /* Unqueue and handle each processed buffer */
404 alSourceUnqueueBuffers(player
->source
, 1, &bufid
);
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
;
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
;
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");
441 /* Make sure the source hasn't underrun */
442 if(state
!= AL_PLAYING
&& state
!= AL_PAUSED
)
446 /* If no buffers are queued, playback is finished */
447 alGetSourcei(player
->source
, AL_BUFFERS_QUEUED
, &queued
);
451 alSourcePlay(player
->source
);
452 if(alGetError() != AL_NO_ERROR
)
454 fprintf(stderr
, "Error restarting playback\n");
463 int main(int argc
, char **argv
)
465 StreamPlayer
*player
;
468 /* Print out usage if no arguments were specified */
471 fprintf(stderr
, "Usage: %s [-device <name>] <filenames...>\n", argv
[0]);
476 if(InitAL(&argv
, &argc
) != 0)
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
]))
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
];
495 printf("Playing: %s (%s, %dhz)\n", namepart
, FormatName(player
->format
),
496 player
->sfinfo
.samplerate
);
499 if(!StartPlayer(player
))
501 ClosePlayerFile(player
);
505 while(UpdatePlayer(player
))
506 al_nssleep(10000000);
508 /* All done with this file. Close it and go to the next */
509 ClosePlayerFile(player
);
513 /* All files done. Delete the player, and close down OpenAL */
514 DeletePlayer(player
);