alsa sound programming

C programming forum.

alsa sound programming

Postby QuantumKnot on September 22nd, 2008, 10:25 pm

I decided to update the old CSE labs (which used the deprecated OSS library) to use alsa for playback. As usual, setting up the program is complicated as hell but there are a few parameters which can be set.

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(&params);

   /* 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(&params);

   /* 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;
}
Attachments
quote.dat.bz2
This is the sound file for the second example
(605.17 KB) Downloaded 29 times
Image
User avatar
QuantumKnot
Linux Adept
 
Posts: 101
Joined: January 21st, 2008, 11:58 am

Re: alsa sound programming

Postby garrymodes on July 26th, 2010, 8:27 pm

Very good article bigginers ALSA. I tried running this example on my PC. I want to play my file. Wav (I use the file bugs_bye.wav my post) and hearing.
garrymodes
New Member
 
Posts: 5
Joined: July 26th, 2010, 8:19 pm

Re: alsa sound programming

Postby henrybrittle33 on January 8th, 2012, 1:41 am

I found 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.
henrybrittle33
New Member
 
Posts: 1
Joined: January 8th, 2012, 1:32 am


Return to C

Who is online

Users browsing this forum: No registered users and 0 guests

cron

dsplabs homelinux bloglinux forums new!travel photography
©2012 dsplabs.com.au