The most important thing is to understand how alsa processes the sound data. If we are using 16-bit stereo, then for each time sample, we have two 16-bit numbers (left and right) that come after another (interleaved mode). These two samples constitute a frame, which in this case will be 32 bits (4 bytes) long. If we are using 16-bit mono, then each frame consists of just one 16-bit number, hence the frame will be 16 bits (2 bytes) long.
alsa transfers a fixed number of frames to the hardware ring buffer of the soundcard. This fixed number of frames is called a period in alsa (or fragment in OSS). Generally the more frames in the period, the more 'buffered' the sound will be, so less chance of buffer underruns, but this also increases the latency. In the template code below, we can set the number of frames/period but if it is too low, alsa will intelligently replace it with what it thinks will be a better value. In the template, numFrames is set to 256, but if it is set to anything lower than 128, alsa will replace it with the lowest it thinks is optimal (128). This is done via the function call snd_pcm_hw_params_set_period_size_near(). Note that alsa will also override the sampling frequency to the nearest one if it is not supported by the sound card.
The template below will play a 500 Hz sine tone for 2 seconds.
To compile:
- Code: Select all
gcc alsa_template.c -lm -lasound -o alsa_template
- Code: Select all
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <math.h>
int main(void)
{
/* typical parameters */
int fs = 8000; // sampling frequency
int numChannels = 1; // mono or stereo?
int sampleFormat = SND_PCM_FORMAT_S16_LE; // data format
snd_pcm_uframes_t numFrames = 256; // number of frames per period (buffer)
short int *buffer; // assuming 16-bit (2 byte)
long iterations, j;
float t;
int rc, f, i, size, dir;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
/* Open PCM device for playback. */
if ((snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
exit(1);
}
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
/* Set the desired hardware parameters. */
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params, sampleFormat);
/* number of channels */
snd_pcm_hw_params_set_channels(handle, params, numChannels);
/* set sampling frequency */
snd_pcm_hw_params_set_rate_near(handle, params, &fs, &dir);
printf("Sampling frequency set to %d\n", fs);
/* set number of frames/period, which alsa will override if too low */
snd_pcm_hw_params_set_period_size_near(handle, params, &numFrames, &dir);
/* Write the parameters to the driver */
if ((snd_pcm_hw_params(handle, params)) < 0) {
fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
exit(1);
}
/* Use a buffer large enough to hold one period */
snd_pcm_hw_params_get_period_size(params, &numFrames, &dir);
printf("Number of frame/period = %d\n", numFrames);
// allocate a big enough buffer (size is number of bytes)
size = numFrames * sizeof(short int); /* 2 bytes/sample, 1 channels */
buffer = (short int *)malloc(size);
/* Get the number of microsecs of one period */
snd_pcm_hw_params_get_period_time(params, &val, &dir);
/* 2 seconds divided by time of one period (in microseconds) */
printf("one period = %d microsecs\n",val);
iterations = 2 / (val * 1e-6); // determine number of periods to play sound
t = 0;
f = 500;
for (j = 0; j < iterations; j++) {
// sine wave generation
for (i = 0; i < numFrames; i++) {
buffer[i] = 32768 * sin(2 * M_PI * f * t);
t += 1.f/(float)fs;
}
rc = snd_pcm_writei(handle, buffer, numFrames);
if (rc == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
}
else if (rc < 0) {
fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));
}
else if (rc != (int)numFrames) {
fprintf(stderr, "short write, write %d frames\n", rc);
}
}
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
return 0;
}
The code below is a program I wrote to play the attached sound file (which is stored as a text file of samples between -1 and 1). Note that I rescale the sound to the maximum of a signed short int (2^15) to get better volume.
To compile:
- Code: Select all
gcc play_quote.c -lasound -o play_quote
- Code: Select all
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <math.h>
int main(void)
{
/* typical parameters */
int fs = 16000; // sampling frequency
int numChannels = 1; // mono or stereo?
int sampleFormat = SND_PCM_FORMAT_S16_LE; // data format
snd_pcm_uframes_t numFrames = 256; // number of frames per period (buffer)
FILE *filePtr = NULL;
float sound[300000];
short int *buffer; // assuming 16-bit (2 byte)
long iterations, j, k;
float t;
int rc, f, i, size, dir;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
/* Open PCM device for playback. */
if ((snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
exit(1);
}
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
/* Set the desired hardware parameters. */
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params, sampleFormat);
/* number of channels */
snd_pcm_hw_params_set_channels(handle, params, numChannels);
/* set sampling frequency */
snd_pcm_hw_params_set_rate_near(handle, params, &fs, &dir);
printf("Sampling frequency set to %d\n", fs);
/* set number of frames/period, which alsa will override if too low */
snd_pcm_hw_params_set_period_size_near(handle, params, &numFrames, &dir);
/* Write the parameters to the driver */
if ((snd_pcm_hw_params(handle, params)) < 0) {
fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
exit(1);
}
/* Use a buffer large enough to hold one period */
snd_pcm_hw_params_get_period_size(params, &numFrames, &dir);
printf("Number of frame/period = %d\n", numFrames);
// allocate a big enough buffer (size is number of bytes)
size = numFrames * sizeof(short int); /* 2 bytes/sample, 1 channels */
buffer = (short int *)malloc(size);
/* Get the number of microsecs of one period */
snd_pcm_hw_params_get_period_time(params, &val, &dir);
// open the file
if ((filePtr = fopen("quote.dat", "r")) == NULL) {
perror("Cannot open quote.dat");
exit(1);
}
// read the sound
for (i = 0; fscanf(filePtr, "%f", &sound[i]) != EOF; i++);
fclose(filePtr);
k = 0;
iterations = i / numFrames; // determine number of periods to play sound
for (j = 0; j < iterations; j++) {
// play the sound
for (i = 0; i < numFrames; i++) {
buffer[i] = 32768 * sound[k++];
}
rc = snd_pcm_writei(handle, buffer, numFrames);
if (rc == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
}
else if (rc < 0) {
fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));
}
else if (rc != (int)numFrames) {
fprintf(stderr, "short write, write %d frames\n", rc);
}
}
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
return 0;
}

