Programming ⇝ Tutorial: ALSA Tutorial 1 - Initialization

This tutorial assumes that you are familiar with the C++ programming language and the Linux operating system.

The Advanced Linux Sound Architecture (ALSA) is the standard audio API of Linux as of kernel version 2.6. It is a higher-level API than its predecessor, the Open Sound System (OSS) and requires less effort on the part of the programmer to implement in an application.

This tutorial assumes that you will be using g++, which is the standard Linux C++ compiler. You will also need to have libasound2 and libasound2-dev installed. In Ubuntu, which was the Linux distribution that was used for this tutorial, g++, libasound2, and libasound2-dev are not installed by default and will need to be installed using apt-get (sudo apt-get install libasound2-dev libasound2).

ALSA requires the the asoundlib.h header. Is is included with this line:

#include <alsa/asoundlib.h>
The process of initializing ALSA is fairly long and involved. In a nutshell, we open the sound device, create a structure to hold the parameters we want to use, and then tell the sound device to use those settings. If successful, we then prepare the sound device for use.

For the first example, we are using a global variable to keep track of our sound device. Globals are generally a bad idea, but in this case it keeps the example simple.

The first call is to snd_pcm_open. One of the parameters is the sound device, into which we will pass "plughw:0,0", the default sound device, and a constant that tells the device we want to use it for playback.

err = snd_pcm_open( &_soundDevice, "plughw:0,0", SND_PCM_STREAM_PLAYBACK, 0 );
If something goes wrong, we will have an error code, which can be translated into a string using snd_strerror(). We check for errors throughout the code.

Next we allocate memory for the hardware parameter struct.

err = snd_pcm_hw_params_malloc (&hw_params);
Once we have this, we can get the current paramters from the sound device:
err = snd_pcm_hw_params_any (_soundDevice, hw_params);
Now that we have this information, we can change it to suit our needs.

Here we turn on resampling:

unsigned int resample = 1;
err = snd_pcm_hw_params_set_rate_resample(_soundDevice, hw_params, resample);
Here we set our samples to interleaved:
err = snd_pcm_hw_params_set_access (_soundDevice, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)
Interleaving means that we alternate samples for the left and right channel (LRLRLR).

Here we set our sample format to PCM 16-bit little-endian. If you are not using a little-endian (i.e. Intel or AMD) machine you will want to use a different setting.

(err = snd_pcm_hw_params_set_format (_soundDevice, hw_params, SND_PCM_FORMAT_S16_LE)
Here we set our stream to 2 channels (stereo):
err = snd_pcm_hw_params_set_channels (_soundDevice, hw_params, 2)
Here we set our sample playback rate to 44100:
unsigned int actualRate = 44100;
err = snd_pcm_hw_params_set_rate_near (_soundDevice, hw_params, &actualRate, 0);
Now that we've made our settings, we apply them to the sound device.
err = snd_pcm_hw_params (_soundDevice, hw_params);
With the sound device set up, we now check what buffer size we've been given:
snd_pcm_uframes_t bufferSize;
snd_pcm_hw_params_get_buffer_size( hw_params, &bufferSize );
cout << "Init: Buffer size = " << bufferSize << " frames." << endl;
_playbackFrames = bufferSize;
cout << "Init: Significant bits for linear samples = " << snd_pcm_hw_params_get_sbits(hw_params) << endl;
We the free the memory for the hardware parameter struct:
snd_pcm_hw_params_free (hw_params);
And finally, we prepare the audio interface for use:
err = snd_pcm_prepare (_soundDevice);
When we're done using the audio interface, we can close it like this:
snd_pcm_close (_soundDevice);
There are a lot of changes that can be made to parameters and a lot more settings that can be made, but this should give you the basics.

The code for this tutorial only initializes and then de-initializes the sound device. That isn't much, but it's a start. In the second tutorial we'll actually hear something come from the speakers.

Linking requires the asound library. To link the file, we add -lasound to the linker flags.

g++ -o alsatut1 alsatut1.cpp -lasound
To run the program, enter ./asound at the command prompt. If everything goes as planned, the application should display the following messages, or something similar:

Audio device opened successfully. Audio device parameters have been set successfully. Init: Buffer size = 2048 frames. Init: Significant bits for linear samples = 16 Audio device has been prepared for use. Audio device has been uninitialized.

Here is the actual code used. Note that we have done everything in a single file, alsatut1.cpp, with no header files:

#include <alsa/asoundlib.h>
#include <iostream>
using namespace std;

// Globals are generally a bad idea in code. We're using one here to keep it simple. snd_pcm_t * _soundDevice;

bool Init(const char *name) { int i; int err; snd_pcm_hw_params_t *hw_params;

if( name == NULL ) { // Try to open the default device err = snd_pcm_open( &_soundDevice, "plughw:0,0", SND_PCM_STREAM_PLAYBACK, 0 ); } else { // Open the device we were told to open. err = snd_pcm_open (&_soundDevice, name, SND_PCM_STREAM_PLAYBACK, 0); }

// Check for error on open. if( err < 0 ) { cout << "Init: cannot open audio device " << name << " (" << snd_strerror (err) << ")" << endl; return false; } else { cout << "Audio device opened successfully." << endl; }

// Allocate the hardware parameter structure. if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) { cout << "Init: cannot allocate hardware parameter structure (" << snd_strerror (err) << ")" << endl; return false; }

if ((err = snd_pcm_hw_params_any (_soundDevice, hw_params)) < 0) { cout << "Init: cannot initialize hardware parameter structure (" << snd_strerror (err) << ")" << endl; return false; }

// Enable resampling. unsigned int resample = 1; err = snd_pcm_hw_params_set_rate_resample(_soundDevice, hw_params, resample); if (err < 0) { cout << "Init: Resampling setup failed for playback: " << snd_strerror(err) << endl; return err; }

// Set access to RW interleaved. if ((err = snd_pcm_hw_params_set_access (_soundDevice, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { cout << "Init: cannot set access type (" << snd_strerror (err) << ")" << endl; return false; }

if ((err = snd_pcm_hw_params_set_format (_soundDevice, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { cout << "Init: cannot set sample format (" << snd_strerror (err) << ")" << endl; return false; }

// Set channels to stereo (2). if ((err = snd_pcm_hw_params_set_channels (_soundDevice, hw_params, 2)) < 0) { cout << "Init: cannot set channel count (" << snd_strerror (err) << ")" << endl; return false; }

// Set sample rate. unsigned int actualRate = 44100; if ((err = snd_pcm_hw_params_set_rate_near (_soundDevice, hw_params, &actualRate, 0)) < 0) { cout << "Init: cannot set sample rate to 44100. (" << snd_strerror (err) << ")" << endl; return false; } if( actualRate < 44100 ) { cout << "Init: sample rate does not match requested rate. (" << "44100 requested, " << actualRate << " acquired)" << endl; }

// Apply the hardware parameters that we've set. if ((err = snd_pcm_hw_params (_soundDevice, hw_params)) < 0) { cout << "Init: cannot set parameters (" << snd_strerror (err) << ")" << endl; return false; } else { cout << "Audio device parameters have been set successfully." << endl; }

// Get the buffer size. snd_pcm_uframes_t bufferSize; snd_pcm_hw_params_get_buffer_size( hw_params, &bufferSize ); // If we were going to do more with our sound device we would want to store // the buffer size so we know how much data we will need to fill it with. cout << "Init: Buffer size = " << bufferSize << " frames." << endl;

// Display the bit size of samples. cout << "Init: Significant bits for linear samples = " << snd_pcm_hw_params_get_sbits(hw_params) << endl;

// Free the hardware parameters now that we're done with them. snd_pcm_hw_params_free (hw_params);

// Prepare interface for use. if ((err = snd_pcm_prepare (_soundDevice)) < 0) { cout << "Init: cannot prepare audio interface for use (" << snd_strerror (err) << ")" << endl; return false; } else { cout << "Audio device has been prepared for use." << endl; }

return true; }

bool UnInit() { snd_pcm_close (_soundDevice); cout << "Audio device has been uninitialized." << endl; return true; }

int main( int argv, char **argc ) { Init(NULL); UnInit(); return 0; }

To build this program, you'll need to have the libasound2-dev package installed (or libasound-dev on older systems). Run this command to compile and create the executable file "alsatut1":

g++ -oalsatut1 alsatut1.cpp -lasound

You can then run the program with ./alsatut1 on the command line. With any luck, it'll initialize successfully. If not, welcome to the "fun" part of programming -- troubleshooting. Good luck.