Tuesday, February 8, 2011

Simplest Way to Play Raw PCM Audio on Ubuntu: libao

There are a zillion ways for a Linux programmer to play audio through the sound card.  This is the problem.  There are many layers to the audio system, many ways to go, and most of them are very complex because multimedia is very complex.

But what if you have the simplest of all cases where you have a buffer in memory containing raw PCM samples, ready to play, and you just want to pump the data out to the sound card and play it at a certain sample rate?  In many cases you're looking at hundreds of lines of code, writing your own plugin, etc.

After two days of asking questions, Googling, and reading, I finally found what I was looking for.  Libao is part of the standard Ubuntu distribution and it does the job without writing tons of code.

I found a couple of examples but both had problems compiling cleanly.  After using Synaptic to install the libao development files the following will compile cleanly on Ubuntu 10.04 using the gcc command shown in the comments below:


 /*  
  *  
  * ao_example.c  
  *  
  *   Written by Stan Seibert - July 2001  
  *   Modified slightly by Phil Landmeier - February 2011  
  *  
  * Legal Terms:  
  *  
  *   This source file is released into the public domain. It is  
  *   distributed without any warranty; without even the implied  
  *   warranty * of merchantability or fitness for a particular  
  *   purpose.  
  *  
  * Function:  
  *  
  *   This program opens the default driver and plays a 440 Hz tone for  
  *   one second.  
  *  
  * Compilation command line (for Linux systems):  
  *  
  *   gcc -lao -ldl -lm -o ao_example ao_example.c  
  *  
  */  
 #include <stdio.h>  
 #include <string.h>  
 #include <ao/ao.h>  
 #include <math.h>  
 #define BUF_SIZE 4096  
 int main(int argc, char **argv)  
 {  
     ao_device *device;  
     ao_sample_format format;  
     int default_driver;  
     char *buffer;  
     int buf_size;  
     int sample;  
     float freq = 440.0;  
     int i;  
     /* -- Initialize -- */  
     fprintf(stderr, "libao example program\n");  
     ao_initialize();  
     /* -- Setup for default driver -- */  
     default_driver = ao_default_driver_id();  
     memset(&format, 0, sizeof(format));  
     format.bits = 16;  
     format.channels = 2;  
     format.rate = 44100;  
     format.byte_format = AO_FMT_LITTLE;  
     /* -- Open driver -- */  
     device = ao_open_live(default_driver, &format, NULL /* no options */);  
     if (device == NULL) {  
         fprintf(stderr, "Error opening device.\n");  
         return 1;  
     }  
     /* -- Play some stuff -- */  
     buf_size = format.bits/8 * format.channels * format.rate;  
     buffer = calloc(buf_size,  
             sizeof(char));  
     for (i = 0; i < format.rate; i++) {  
         sample = (int)(0.75 * 32768.0 *  
             sin(2 * M_PI * freq * ((float) i/format.rate)));  
         /* Put the same stuff in left and right channel */  
         buffer[4*i] = buffer[4*i+2] = sample & 0xff;  
         buffer[4*i+1] = buffer[4*i+3] = (sample >> 8) & 0xff;  
     }  
     ao_play(device, buffer, buf_size);  
     /* -- Close and shutdown -- */  
     ao_close(device);  
     ao_shutdown();  
  return (0);  
 }  

2 comments:

Anonymous said...

Excellent example. I was looking for something to play raw audio files and this helps a lot. On Ubuntu 10.10 libao was not present (at least not on my system) but I downloaded it (http://www.xiph.org/downloads/) and it compiled and installed without problems.

Now just loading the raw data into the buffer and I'm done ... should be easy ;-)

Thanks a lot, Albert

ShutterSparks / KW2P said...

I hope it helps. I've stayed with libao on the application I'm working on and have had no problems, no weird behaviors. Libao does block while the sound is playing but that's easily solved by putting the libao calls in a separate thread.

Phil