2 * OpenAL Callback-based Stream Example
4 * Copyright (c) 2020 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 streaming audio player using a callback buffer. */
45 #include "common/alhelpers.h"
50 using std::chrono::seconds;
51 using std::chrono::nanoseconds;
53 LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT;
56 /* A lockless ring-buffer (supports single-provider, single-consumer
59 std::unique_ptr<ALbyte[]> mBufferData;
60 size_t mBufferDataSize{0};
61 std::atomic<size_t> mReadPos{0};
62 std::atomic<size_t> mWritePos{0};
63 size_t mSamplesPerBlock{1};
64 size_t mBytesPerBlock{1};
66 enum class SampleType {
67 Int16, Float, IMA4, MSADPCM
69 SampleType mSampleFormat{SampleType::Int16};
71 /* The buffer to get the callback, and source to play with. */
72 ALuint mBuffer{0}, mSource{0};
73 size_t mStartOffset{0};
75 /* Handle for the audio file to decode. */
76 SNDFILE *mSndfile{nullptr};
78 size_t mDecoderOffset{0};
80 /* The format of the callback samples. */
85 alGenBuffers(1, &mBuffer);
86 if(alGetError() != AL_NO_ERROR)
87 throw std::runtime_error{"alGenBuffers failed"};
88 alGenSources(1, &mSource);
89 if(alGetError() != AL_NO_ERROR)
91 alDeleteBuffers(1, &mBuffer);
92 throw std::runtime_error{"alGenSources failed"};
97 alDeleteSources(1, &mSource);
98 alDeleteBuffers(1, &mBuffer);
105 if(mSamplesPerBlock > 1)
106 alBufferi(mBuffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0);
110 alSourceRewind(mSource);
111 alSourcei(mSource, AL_BUFFER, 0);
117 bool open(const char *filename)
121 /* Open the file and figure out the OpenAL format. */
122 mSndfile = sf_open(filename, SFM_READ, &mSfInfo);
125 fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile));
129 switch((mSfInfo.format&SF_FORMAT_SUBMASK))
131 case SF_FORMAT_PCM_24:
132 case SF_FORMAT_PCM_32:
133 case SF_FORMAT_FLOAT:
134 case SF_FORMAT_DOUBLE:
135 case SF_FORMAT_VORBIS:
137 case SF_FORMAT_ALAC_20:
138 case SF_FORMAT_ALAC_24:
139 case SF_FORMAT_ALAC_32:
140 case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/:
141 case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/:
142 case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/:
143 if(alIsExtensionPresent("AL_EXT_FLOAT32"))
144 mSampleFormat = SampleType::Float;
146 case SF_FORMAT_IMA_ADPCM:
147 if(mSfInfo.channels <= 2 && (mSfInfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV
148 && alIsExtensionPresent("AL_EXT_IMA4")
149 && alIsExtensionPresent("AL_SOFT_block_alignment"))
150 mSampleFormat = SampleType::IMA4;
152 case SF_FORMAT_MS_ADPCM:
153 if(mSfInfo.channels <= 2 && (mSfInfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV
154 && alIsExtensionPresent("AL_SOFT_MSADPCM")
155 && alIsExtensionPresent("AL_SOFT_block_alignment"))
156 mSampleFormat = SampleType::MSADPCM;
160 int splblocksize{}, byteblocksize{};
161 if(mSampleFormat == SampleType::IMA4 || mSampleFormat == SampleType::MSADPCM)
163 SF_CHUNK_INFO inf{ "fmt ", 4, 0, nullptr };
164 SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(mSndfile, &inf);
165 if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14)
166 mSampleFormat = SampleType::Int16;
169 auto fmtbuf = std::make_unique<ALubyte[]>(inf.datalen);
170 inf.data = fmtbuf.get();
171 if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR)
172 mSampleFormat = SampleType::Int16;
175 byteblocksize = fmtbuf[12] | (fmtbuf[13]<<8u);
176 if(mSampleFormat == SampleType::IMA4)
178 splblocksize = (byteblocksize/mSfInfo.channels - 4)/4*8 + 1;
180 || ((splblocksize-1)/2 + 4)*mSfInfo.channels != byteblocksize)
181 mSampleFormat = SampleType::Int16;
185 splblocksize = (byteblocksize/mSfInfo.channels - 7)*2 + 2;
187 || ((splblocksize-2)/2 + 7)*mSfInfo.channels != byteblocksize)
188 mSampleFormat = SampleType::Int16;
194 if(mSampleFormat == SampleType::Int16)
196 mSamplesPerBlock = 1;
197 mBytesPerBlock = static_cast<size_t>(mSfInfo.channels * 2);
199 else if(mSampleFormat == SampleType::Float)
201 mSamplesPerBlock = 1;
202 mBytesPerBlock = static_cast<size_t>(mSfInfo.channels * 4);
206 mSamplesPerBlock = static_cast<size_t>(splblocksize);
207 mBytesPerBlock = static_cast<size_t>(byteblocksize);
211 if(mSfInfo.channels == 1)
213 if(mSampleFormat == SampleType::Int16)
214 mFormat = AL_FORMAT_MONO16;
215 else if(mSampleFormat == SampleType::Float)
216 mFormat = AL_FORMAT_MONO_FLOAT32;
217 else if(mSampleFormat == SampleType::IMA4)
218 mFormat = AL_FORMAT_MONO_IMA4;
219 else if(mSampleFormat == SampleType::MSADPCM)
220 mFormat = AL_FORMAT_MONO_MSADPCM_SOFT;
222 else if(mSfInfo.channels == 2)
224 if(mSampleFormat == SampleType::Int16)
225 mFormat = AL_FORMAT_STEREO16;
226 else if(mSampleFormat == SampleType::Float)
227 mFormat = AL_FORMAT_STEREO_FLOAT32;
228 else if(mSampleFormat == SampleType::IMA4)
229 mFormat = AL_FORMAT_STEREO_IMA4;
230 else if(mSampleFormat == SampleType::MSADPCM)
231 mFormat = AL_FORMAT_STEREO_MSADPCM_SOFT;
233 else if(mSfInfo.channels == 3)
235 if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
237 if(mSampleFormat == SampleType::Int16)
238 mFormat = AL_FORMAT_BFORMAT2D_16;
239 else if(mSampleFormat == SampleType::Float)
240 mFormat = AL_FORMAT_BFORMAT2D_FLOAT32;
243 else if(mSfInfo.channels == 4)
245 if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
247 if(mSampleFormat == SampleType::Int16)
248 mFormat = AL_FORMAT_BFORMAT3D_16;
249 else if(mSampleFormat == SampleType::Float)
250 mFormat = AL_FORMAT_BFORMAT3D_FLOAT32;
255 fprintf(stderr, "Unsupported channel count: %d\n", mSfInfo.channels);
262 /* Set a 1s ring buffer size. */
263 size_t numblocks{(static_cast<ALuint>(mSfInfo.samplerate) + mSamplesPerBlock-1)
265 mBufferDataSize = static_cast<ALuint>(numblocks * mBytesPerBlock);
266 mBufferData.reset(new ALbyte[mBufferDataSize]);
267 mReadPos.store(0, std::memory_order_relaxed);
268 mWritePos.store(0, std::memory_order_relaxed);
274 /* The actual C-style callback just forwards to the non-static method. Not
275 * strictly needed and the compiler will optimize it to a normal function,
276 * but it allows the callback implementation to have a nice 'this' pointer
277 * with normal member access.
279 static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size)
280 { return static_cast<StreamPlayer*>(userptr)->bufferCallback(data, size); }
281 ALsizei bufferCallback(void *data, ALsizei size)
283 /* NOTE: The callback *MUST* be real-time safe! That means no blocking,
284 * no allocations or deallocations, no I/O, no page faults, or calls to
285 * functions that could do these things (this includes calling to
286 * libraries like SDL_sound, libsndfile, ffmpeg, etc). Nothing should
287 * unexpectedly stall this call since the audio has to get to the
292 size_t roffset{mReadPos.load(std::memory_order_acquire)};
295 /* If the write offset == read offset, there's nothing left in the
296 * ring-buffer. Break from the loop and give what has been written.
298 const size_t woffset{mWritePos.load(std::memory_order_relaxed)};
299 if(woffset == roffset) break;
301 /* If the write offset is behind the read offset, the readable
302 * portion wrapped around. Just read up to the end of the buffer in
303 * that case, otherwise read up to the write offset. Also limit the
304 * amount to copy given how much is remaining to write.
306 size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset};
307 todo = std::min<size_t>(todo, static_cast<ALuint>(size-got));
309 /* Copy from the ring buffer to the provided output buffer. Wrap
310 * the resulting read offset if it reached the end of the ring-
313 memcpy(data, &mBufferData[roffset], todo);
314 data = static_cast<ALbyte*>(data) + todo;
315 got += static_cast<ALsizei>(todo);
318 if(roffset == mBufferDataSize)
321 /* Finally, store the updated read offset, and return how many bytes
324 mReadPos.store(roffset, std::memory_order_release);
331 if(mSamplesPerBlock > 1)
332 alBufferi(mBuffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, static_cast<int>(mSamplesPerBlock));
333 alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this);
334 alSourcei(mSource, AL_BUFFER, static_cast<ALint>(mBuffer));
335 if(ALenum err{alGetError()})
337 fprintf(stderr, "Failed to set callback: %s (0x%04x)\n", alGetString(err), err);
347 alGetSourcei(mSource, AL_SAMPLE_OFFSET, &pos);
348 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
350 size_t woffset{mWritePos.load(std::memory_order_acquire)};
351 if(state != AL_INITIAL)
353 const size_t roffset{mReadPos.load(std::memory_order_relaxed)};
354 const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) -
356 /* For a stopped (underrun) source, the current playback offset is
357 * the current decoder offset excluding the readable buffered data.
358 * For a playing/paused source, it's the source's offset including
359 * the playback offset the source was started with.
361 const size_t curtime{((state == AL_STOPPED)
362 ? (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock
363 : (static_cast<ALuint>(pos) + mStartOffset/mBytesPerBlock*mSamplesPerBlock))
364 / static_cast<ALuint>(mSfInfo.samplerate)};
365 printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferDataSize);
368 fputs("Starting...", stdout);
371 while(!sf_error(mSndfile))
374 const size_t roffset{mReadPos.load(std::memory_order_relaxed)};
375 if(roffset > woffset)
377 /* Note that the ring buffer's writable space is one byte less
378 * than the available area because the write offset ending up
379 * at the read offset would be interpreted as being empty
382 const size_t writable{(roffset-woffset-1) / mBytesPerBlock};
385 if(mSampleFormat == SampleType::Int16)
387 sf_count_t num_frames{sf_readf_short(mSndfile,
388 reinterpret_cast<short*>(&mBufferData[woffset]),
389 static_cast<sf_count_t>(writable*mSamplesPerBlock))};
390 if(num_frames < 1) break;
391 read_bytes = static_cast<size_t>(num_frames) * mBytesPerBlock;
393 else if(mSampleFormat == SampleType::Float)
395 sf_count_t num_frames{sf_readf_float(mSndfile,
396 reinterpret_cast<float*>(&mBufferData[woffset]),
397 static_cast<sf_count_t>(writable*mSamplesPerBlock))};
398 if(num_frames < 1) break;
399 read_bytes = static_cast<size_t>(num_frames) * mBytesPerBlock;
403 sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset],
404 static_cast<sf_count_t>(writable*mBytesPerBlock))};
405 if(numbytes < 1) break;
406 read_bytes = static_cast<size_t>(numbytes);
409 woffset += read_bytes;
413 /* If the read offset is at or behind the write offset, the
414 * writeable area (might) wrap around. Make sure the sample
415 * data can fit, and calculate how much can go in front before
418 const size_t writable{(!roffset ? mBufferDataSize-woffset-1 :
419 (mBufferDataSize-woffset)) / mBytesPerBlock};
422 if(mSampleFormat == SampleType::Int16)
424 sf_count_t num_frames{sf_readf_short(mSndfile,
425 reinterpret_cast<short*>(&mBufferData[woffset]),
426 static_cast<sf_count_t>(writable*mSamplesPerBlock))};
427 if(num_frames < 1) break;
428 read_bytes = static_cast<size_t>(num_frames) * mBytesPerBlock;
430 else if(mSampleFormat == SampleType::Float)
432 sf_count_t num_frames{sf_readf_float(mSndfile,
433 reinterpret_cast<float*>(&mBufferData[woffset]),
434 static_cast<sf_count_t>(writable*mSamplesPerBlock))};
435 if(num_frames < 1) break;
436 read_bytes = static_cast<size_t>(num_frames) * mBytesPerBlock;
440 sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset],
441 static_cast<sf_count_t>(writable*mBytesPerBlock))};
442 if(numbytes < 1) break;
443 read_bytes = static_cast<size_t>(numbytes);
446 woffset += read_bytes;
447 if(woffset == mBufferDataSize)
450 mWritePos.store(woffset, std::memory_order_release);
451 mDecoderOffset += read_bytes;
454 if(state != AL_PLAYING && state != AL_PAUSED)
456 /* If the source is not playing or paused, it either underrun
457 * (AL_STOPPED) or is just getting started (AL_INITIAL). If the
458 * ring buffer is empty, it's done, otherwise play the source with
461 const size_t roffset{mReadPos.load(std::memory_order_relaxed)};
462 const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) -
467 /* Store the playback offset that the source will start reading
468 * from, so it can be tracked during playback.
470 mStartOffset = mDecoderOffset - readable;
471 alSourcePlay(mSource);
472 if(alGetError() != AL_NO_ERROR)
481 int main(int argc, char **argv)
483 /* A simple RAII container for OpenAL startup and shutdown. */
484 struct AudioManager {
485 AudioManager(char ***argv_, int *argc_)
487 if(InitAL(argv_, argc_) != 0)
488 throw std::runtime_error{"Failed to initialize OpenAL"};
490 ~AudioManager() { CloseAL(); }
493 /* Print out usage if no arguments were specified */
496 fprintf(stderr, "Usage: %s [-device <name>] <filenames...>\n", argv[0]);
501 AudioManager almgr{&argv, &argc};
503 if(!alIsExtensionPresent("AL_SOFT_callback_buffer"))
505 fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n");
509 alBufferCallbackSOFT = reinterpret_cast<LPALBUFFERCALLBACKSOFT>(
510 alGetProcAddress("alBufferCallbackSOFT"));
513 alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh);
515 std::unique_ptr<StreamPlayer> player{new StreamPlayer{}};
517 /* Play each file listed on the command line */
518 for(int i{0};i < argc;++i)
520 if(!player->open(argv[i]))
523 /* Get the name portion, without the path, for display. */
524 const char *namepart{strrchr(argv[i], '/')};
525 if(namepart || (namepart=strrchr(argv[i], '\\')))
530 printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat),
531 player->mSfInfo.samplerate);
534 if(!player->prepare())
540 while(player->update())
541 std::this_thread::sleep_for(nanoseconds{seconds{1}} / refresh);
544 /* All done with this file. Close it and go to the next */