Playing audio files with libao

Learn playing audio files with libao with practical examples, diagrams, and best practices. Covers c, linux, audio development techniques with visual explanations.

Playing Audio Files with libao: A C Developer's Guide

Hero image for Playing audio files with libao

Learn how to integrate libao into your C applications to play various audio formats like WAV and AIFF on Linux systems, covering initialization, device selection, and audio output.

Playing audio files programmatically is a common requirement for many applications, from games to media players. On Linux, libao provides a simple, cross-platform audio output library that abstracts away the complexities of different audio drivers and APIs (like ALSA, PulseAudio, OSS). This article will guide you through the process of using libao in a C program to play audio files, focusing on common formats like WAV and AIFF.

Understanding libao Basics

libao is designed to be a straightforward audio output library. It handles the low-level details of sending audio data to your sound card. Before you can play any audio, you need to initialize the library, select an output device, and open it with the correct audio format parameters (sample rate, number of channels, bits per sample). Once the device is open, you can write raw audio data to it.

flowchart TD
    A[Start Application] --> B{Initialize libao};
    B --> C{Select Default Driver};
    C --> D{Define Audio Format (Rate, Channels, Bits)};
    D --> E{Open Audio Device};
    E --> F{Read Audio Data from File};
    F --> G{Write Data to Audio Device};
    G -- Loop until EOF --> F;
    G --> H{Close Audio Device};
    H --> I{Shutdown libao};
    I --> J[End Application];

Basic libao Audio Playback Workflow

Setting Up Your Development Environment

Before you can compile and run libao examples, you need to install the library and its development headers. On most Debian-based systems (like Ubuntu), you can do this using apt.

sudo apt update
sudo apt install libao-dev

Installing libao development files on Debian/Ubuntu

Playing a Raw PCM Stream

The simplest way to use libao is to feed it raw Pulse Code Modulation (PCM) data. This requires you to know the audio file's format (sample rate, channels, bit depth) beforehand. For demonstration, let's assume we have a WAV file and we've already parsed its header to get these details. The following C code snippet illustrates the core libao functions for opening a device and writing audio data.

#include <ao/ao.h>
#include <stdio.h>
#include <stdlib.h>

#define BITS 16

int main(int argc, char *argv[]) {
    ao_device *device;
    ao_sample_format format;
    int default_driver;
    char *buffer;
    int buf_size;
    FILE *infile;
    int num_bytes_read;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <audio_file.raw>\n", argv[0]);
        return 1;
    }

    ao_initialize();

    default_driver = ao_default_driver_id();

    format.bits = BITS;
    format.channels = 2; /* Stereo */
    format.rate = 44100; /* 44.1 kHz */
    format.byte_format = AO_FMT_LITTLE;
    format.matrix = 0;

    device = ao_open_live(default_driver, &format, NULL);
    if (device == NULL) {
        fprintf(stderr, "Error opening device.\n");
        return 1;
    }

    infile = fopen(argv[1], "rb");
    if (infile == NULL) {
        fprintf(stderr, "Error opening input file %s.\n", argv[1]);
        ao_close(device);
        ao_shutdown();
        return 1;
    }

    /* Buffer for 4KB of audio data */
    buf_size = 4096;
    buffer = (char *)calloc(buf_size, sizeof(char));

    while ((num_bytes_read = fread(buffer, 1, buf_size, infile)) > 0) {
        ao_play(device, buffer, num_bytes_read);
    }

    free(buffer);
    fclose(infile);
    ao_close(device);
    ao_shutdown();

    return 0;
}

Basic C program to play a raw PCM audio stream using libao

Compiling and Running the Example

To compile the C code, you need to link against the libao library. Use pkg-config to get the correct compiler flags.

gcc -o play_raw play_raw.c $(pkg-config --cflags --libs libao)
./play_raw your_audio_file.raw

Compiling and running the libao example

If you don't have a raw PCM file, you can convert a WAV file to raw PCM using sox or ffmpeg for testing purposes. For example, to convert a WAV to 16-bit, stereo, 44.1kHz little-endian raw PCM:

ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ar 44100 -ac 2 output.raw

Converting a WAV file to raw PCM using ffmpeg

Integrating with WAV/AIFF Parsers

For a robust audio player, you would typically use a library to parse WAV or AIFF headers to extract the audio format information. Libraries like libsndfile are excellent for this purpose, as they handle various audio file formats and provide a unified API to read audio data. Once you've read the header and determined the sample rate, channels, and bit depth, you can pass this information to ao_open_live.

sequenceDiagram
    participant App as Application
    participant File as Audio File (WAV/AIFF)
    participant Libao as libao Library
    participant SoundCard as Sound Card

    App->>File: Open file
    App->>File: Read header (get format)
    App->>Libao: ao_initialize()
    App->>Libao: ao_default_driver_id()
    App->>Libao: ao_open_live(driver, format, NULL)
    Libao->>SoundCard: Initialize audio device
    loop while data available
        App->>File: Read audio data block
        App->>Libao: ao_play(device, buffer, size)
        Libao->>SoundCard: Write audio data
    end
    App->>Libao: ao_close(device)
    Libao->>SoundCard: Release audio device
    App->>Libao: ao_shutdown()
    App->>File: Close file

Sequence Diagram for Playing WAV/AIFF with libao and a Parser