SDL2 audio example - Initialization
Like any SDL program, we include SDL.h in our headers so we can use SDL.
#include "SDL.h"
#include <stdio.h>
/* dummy callback */
void MyAudioCallback(void *data, Uint8* stream, int len) {
return;
}
int main() {
int i;
The first thing to do is initialize SDL and its audio subsystem. There are 2 ways to do this, but both are basically the same. First, you can call SDL_Init with the SDL_INIT_AUDIO flag, and it will call SDL_AudioInit(NULL):
if(SDL_Init(SDL_INIT_AUDIO)) {
printf("[SDL] Failed to initialize: %s\n", SDL_GetError());
return 1;
}
Second, you can call SDL_AudioInit directly, and pass it a specific driver name e.g. SDL_AudioInit("pulseaudio"). It returns 0 on success, -1 on error. Remember to handle missing drivers yourself:
SDL_Init();
int numAudioDrivers = SDL_GetNumAudioDrivers();
/* print the list of audio backends */
printf("[SDL] %d audio backends compiled into SDL:", A_numAudioDrivers);
for(i=0;i<numAudioDrivers;i++)
printf(" \'%s\'", SDL_GetAudioDriver(i));
printf("\n");
/* attempt to init audio */
for(i=0;i<numAudioDrivers;i++) {
if(!SDL_AudioInit(SDL_GetAudioDriver(i)) break;
}
if(i == numAudioDrivers) {
printf("[SDL] Failed to init audio: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
Two things can happen if you call SDL_InitAudio(NULL): If the environment variable SDL_AUDIODRIVER is set it will attempt to load that driver as if you had called SDL_InitAudio(SDL_AUDIODRIVER), meaning it only tries 1 driver. If not, it will determine the audio driver to use by running through the list of driver backends compiled into the library, as above.
If the audio subsystem was already initialized, calling SDL_InitAudio will shut down the current audio driver by calling SDL_AudioQuit before reinitializing. The audio driver we end up using can be found easily:
/* print the audio driver we are using */
printf("[SDL] Audio driver: %s\n", SDL_GetCurrentAudioDriver());
After the audio is initialized, we will want to connect to a specific audio device. To do this we'll call SDL_OpenAudioDevice and pass it the name of a device, whether we are opening the device as playback or recording, pointers to 2 SDL_AudioSpec structs, one we fill and one SDL fills, and whether SDL should change our specification.
If we pass a device name to SDL_OpenAudioDevice, it will attempt to open it, and return 0 on success, or -1 on error. If we pass a device name of NULL, SDL will try all available devices, or if env SDL_AUDIO_DEVICE_NAME is set, use that name. You can't open an already opened device. The available devices can be enumerated with:
/* pass it 0 for playback */
int numAudioDevices = SDL_GetNumAudioDevices(0);
/* print the audio devices that we can see */
printf("[SDL] %d audio devices:", numAudioDevices);
for(i = 0; i < numAudioDevices; i++)
printf(" \'%s\'", SDL_GetAudioDeviceName(i, 0)); /* again, 0 for playback */
printf("\n");
SDL doesn't support recording audio now, so you have to pass 0 for this option, as with other functions that deal with audio devices
The audio specification determines the frequency, sample format, number or channels, size of the audio buffer in samples, the callback function used to fill the buffer, and a pointer that is passed to the callback function, which can hold information about the sounds playing etc., see https://wiki.libsdl.org/SDL_AudioSpec.
SDL_AudioSpec want, have;
SDL_zero(want);
/* a general specification */
want.freq = 22050;
want.format = AUDIO_S16LSB;
want.channels = 1; /* 1, 2, 4, or 6 */
want.samples = 4096; /* power of 2, or 0 and env SDL_AUDIO_SAMPLES is used */
want.callback = MyAudioCallback; /* can not be NULL */
printf("[SDL] Desired - frequency: %d format: f %d s %d be %d sz %d channels: %d samples: %d\n", want.freq, SDL_AUDIO_ISFLOAT(want.format), SDL_AUDIO_ISSIGNED(want.format), SDL_AUDIO_ISBIGENDIAN(want.format), SDL_AUDIO_BITSIZE(want.format), want.channels, want.samples);
The macros such as SDL_AUDIO_ISFLOAT(x) just perform bit operations to extract the information, and are defined in SDL_audio.h. SDL_zero(x) is a macro for memset.
With the specification set up, we can open an audio device, using NULL for the device name, for convenience, and allowing any change because we aren't playing any audio yet so we don't care. See https://wiki.libsdl.org/SDL_OpenAudioDevice for details.
/* open audio device, allowing any changes to the specification */
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
if(!dev) {
printf("[SDL] Failed to open audio device: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
printf("[SDL] Obtained - frequency: %d format: f %d s %d be %d sz %d channels: %d samples: %d\n", have.freq, SDL_AUDIO_ISFLOAT(have.format), SDL_AUDIO_ISSIGNED(have.format), SDL_AUDIO_ISBIGENDIAN(have.format), SDL_AUDIO_BITSIZE(have.format), have.channels, have.samples);
Random note: if you use pulseaudio, the returned samples are always half the requested samples. Yea.
I don't think there is a simple function to determine if a paticular format or rate is supported, so to determine what things your system supports you'd have to loop through multiple specifications, opening the audio device, comparing the obtained spec with the desired spec, and then closing the device.
If the driver doesn't support our requested format and we don't allow it to change, SDL should be able to set up a transparent conversion, at least for the formats and channels, the resampler handles 2x, 4x, and their inverses, but other sample rates conversions will need to be handled by you.
There isn't anything else to do now. If everything has worked up to here, then we would be ready to play some audio, if we had actually written a proper callback function. We close the device and then shutdown SDL before exiting.
SDL_CloseAudioDevice(dev);
SDL_Quit();
return 0;
}
Running this gives me:
[SDL] 5 audio backends compiled into SDL: 'pulseaudio' 'alsa' 'dsp' 'disk' 'dummy' [SDL] Audio driver: pulseaudio [SDL] 1 audio devices: 'System audio output device' [SDL] Desired - frequency: 22050 format: f 0 s 32768 be 0 sz 16 channels: 1 samples: 4096 [SDL] Obtained - frequency: 22050 format: f 0 s 32768 be 0 sz 16 channels: 1 samples: 2048
The signed format bitwise ANDs the format with the sign bit, so 32768 means it is signed, 0 would be unsigned
References
- https://wiki.libsdl.org/APIByCategory
- SDL 2.0.3 source code e.g. SDL_audio.c
- Uses javascript SyntaxHighlighter for syntax highlighting.
- Uses javascript MathJax for LateX rendering.
To the extent possible under law,
Craig Hughes
has waived all copyright and related or neighboring rights to
the text and source code in this example/tutorial.