Programming with SDL Audio in Pure C (How to Start?)


Programming your game with SDL (respectively SDL2) is a reasonable approach. But how do you make that speakers play your sounds and music?

The Simple DirectMedia Layer (SDL) has an audio sub module that makes sound devices and audio files easily accessible for the programmer. As the library is written entirely in C it fits neatly into any Pure C application.

Wave Mixing Console
Image from Bokskapet at Pixabay

Learn more about how to use the sound and music part of the SDL through a small and simple example in the following paragraphs.

Programming with SDL Audio in Pure C

The Simple DirectMedia Layer Library (SDL)Opens in a new tab. consists several parts. Its main purpose is its window and graphics system for cross platform development but it also provides an audio subsystem to play sounds and music.

It was initially released in 1998 and migrated to SDL2 in 2013. SDL2 is not backwards compatible to the 1.x versions. The library is written entirely in C and is licensed under the zlib license which is described hereOpens in a new tab.. (The Versions 1.x were released under LGPL).

Besides the obvious support for C++ there exist other bindings to languages like C# or python.

Example: How to Start Programming SDL Audio in Pure C

We write a C Program that loads a WAVE File from a specified path and plays it through the primary sound device with the usage of SDL. We don’t need any other libraries besides one standard C library.

In order to keep this example short and simple I refrained from error handling throughout. This is, of course, not an approach that you should take in real life!

Used Libraries and Defines

As far as the includes go we only need the string.h (for memset) and two SDL includes. SDL_main is necessary if you don’t want to write WinMain your own and use the console main instead.

The Defines are for convenience and clarity. SDL_OK is the return code for “all went right” whenever SDL indicates something to you. The rest is relatively self explanatory.

/* INCLUDES */
#include "../Lib/SDL2-2.0.20/include/SDL.h"
#include "../Lib/SDL2-2.0.20/include/SDL_main.h"
#include <string.h>

/* DEFINES */
#define SDL_OK 0
#define INVALID_AUDIO_DEVICE 0
#define PLAY_AUDIO 0
#define STOP_AUDIO 1
#define CHANNELS 2
#define AUDIO_LENGTH_MS 6000
#define SAMPLE_RATE 32800
#define SDL_AUDIO_FORMAT AUDIO_S32

Function Declarations and Global Variables

At first we declare all functions that we will need. You could also define them here but I like to have the main function as first defined function in my programs. It also makes it easier to factor it out into an interface later if you want to.

I separated one function from the others because this one will not be called directly. It will instead be called by the SDL as a so called callback function. Again, there is no need for separation but it makes things clearer to other programmers (including yourself in the future).

Next come all global variables that we need throughout the whole program. They are valid in all functions but also static which makes them local to the current .c-file (in this case main.c). It also helps to keep things clean when moving to interfaces or other modules.

/* FUNCTIONS */
static int initialize_sdl();
static int initialize_audio();
static void play_audio();
static void clean_up();

/* CALLBACKS */
static void audio_callback(void*, Uint8*, int);

/* VARIABLES */
static SDL_AudioDeviceID _audioDeviceId = 0;
static Uint8* _audioBuffer = NULL;
static Uint32 _audioLength = 0;

Initializing SDL

The initialization of the SDL library is very neat. And because we don’t use graphics in this example we only have to initialize the audio subsystem of the SDL with SDL_INIT_AUDIO. The function returns whether everything worked as intended or not.

...
static int initialize_sdl();
...
static int initialize_sdl()
{
	if (!SDL_Init(SDL_INIT_AUDIO) == SDL_OK) {
		return -1; /* Log error message in real program */
	}

	return SDL_OK;
}
...

Loading the Wave File into the Buffer

We need to do two things to prepare our program for playing our wave file. First we need to open an audio device through which the sound is played. Passing NULL as the first parameter chooses the default audio device. We could check the obtained variable if the device does support everything or if the frequency changed (which we explicitly allowed to do so).

Also we set the callback here. This is the function that I said will not be called by us but by the SDL when the playback has started. We will see what it does in the next section.

Next we load the file from our hard disk, make sure to have a file with that name or type another path in your code which matches your file and path. This function also returns whether everything went right or not.

...
static int initialize_audio();
...
static int initialize_audio()
{
	SDL_AudioSpec audioSpec;
	SDL_AudioSpec obtained;

	audioSpec.channels = CHANNELS;
	audioSpec.freq = SAMPLE_RATE;
	audioSpec.format = SDL_AUDIO_FORMAT;
	audioSpec.callback = audio_callback;

	_audioDeviceId = SDL_OpenAudioDevice(NULL, 0, &audioSpec, &obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
	if (_audioDeviceId == INVALID_AUDIO_DEVICE) {
		return -2; /* Could not open Device */
	}

	if (SDL_LoadWAV("../Asset/piano.wav", &audioSpec, &_audioBuffer, &_audioLength) == NULL) {
		return -3; /* File Not Found */
	}

	return SDL_OK;
}
...

Playing the Audio

play_audio itself is very straightforward as it only starts playing the audio, waits a given time (we set a fix length for our file to play in this case) and then stops the audio.

audio_callback does the heavy lifting here. It is called by the SDL for every part of the stream that should be played. We first check if there is something left to play and that we don’t play more than there is left. Then we set everything silent and play the current part of the stream at the maximum volume possible.

Finally we update the audio buffer and length so that we move towards the end of the wave file and play it sequentially.

...
/* Functions */
static void play_audio();
...
/* Callbacks */
static void audio_callback(void*, Uint8*, int);
...
static void play_audio()
{
	SDL_PauseAudioDevice(_audioDeviceId, PLAY_AUDIO);
	SDL_Delay(AUDIO_LENGTH_MS);
	SDL_PauseAudioDevice(_audioDeviceId, STOP_AUDIO);
}
...
static void audio_callback(void* userData, Uint8* stream, int length)
{
	if (_audioLength == 0) { return; }
	if (length > (int)_audioLength) { length = _audioLength; }

	memset(stream, 0, length);	/* Silence everything */
	SDL_MixAudioFormat(stream, _audioBuffer, SDL_AUDIO_FORMAT, length, SDL_MIX_MAXVOLUME);

	_audioBuffer += length;
	_audioLength -= length;
}

Cleaning Up

It is not much to do here but nevertheless necessary and important. Close the audio device so that others can use it, free the memory where you loaded the wave file into (this can be very large) and then quit the SDL.

...
static void clean_up();
...

static void clean_up()
{
	SDL_CloseAudioDevice(_audioDeviceId);
	SDL_FreeWAV(_audioBuffer);
	SDL_Quit();
}

Putting It All Together

We have all our functions implemented and only need to glue them together to make our speakers sound. errorCode is used for different function return values. We obviously start with the initialization of the SDL. Then we setup our audio device load our wav file into the buffer. After that we play the audio for as long as we defined and finally clean everything up before quitting the application.

/* Includes, Defines, Declarations, ... */
...

int main(int argc, char** argv)
{
	int errorCode = SDL_OK;

	errorCode = initialize_sdl();
	if (errorCode) { return errorCode; }

	errorCode = initialize_audio();
	if (errorCode) { return errorCode; }

	play_audio();
	clean_up();

	return 0;
}

/* Definitions */
...

Using Audio in SDL is not that hard. Now you can try to use it together with the SDL Video to make a great game. A good start is my example for creating and showing simple graphics with SDL in pure C.

Marco Lieblang

Professional Programmer since 2003, passionate Programmer since the mid 90's. Developing in many languages from C/C++ to Java, C#, Python and some more. And I also may know a bit about Assembly Languages and Retro Systems.

Recent Posts