Extremely small single header lib for PCM playback under Linux

bzt 2377fb98ba Added transparent software resampling hai 1 ano
Makefile ac76908d66 Initial commit hai 1 ano
README.md 2377fb98ba Added transparent software resampling hai 1 ano
nanoalsa.h 2377fb98ba Added transparent software resampling hai 1 ano
test_async.c ac76908d66 Initial commit hai 1 ano
test_simple.c ac76908d66 Initial commit hai 1 ano

README.md

Nano ALSA

You've guessed it right: I needed a simple library but couldn't find any, so I wrote it.

I have a very serious problem with ALSA, and that's the user space part asoundlib. It is hilariously overengineered, bloated, isn't thread-safe (uses signals) and leaks memory pretty badly. And even if it weren't poorly written, most applications don't need any of its MIDI, sequencer, mixer, good-for-nothing plugin system features anyway, but you just cannot compile asoundlib without those! This makes it impossible to use ALSA in an embedded environment.

Nano ALSA is an MIT licensed, stb-style single header library, which can do one thing, and one thing only. It can be used to easily play PCM data, which is what most applications want. It does not load shared libraries in run-time, it does not use signals, it does not have a mixer nor a sequencer, just good ol' PCM playback all there is. K.I.S.S.

Dependencies: none, really. Needs libc of course, and the sound/asound.h header from ALSA Linux kernel headers, but just the C header file, not the shared library (I could have duplicated the sample format defines and the ioctl structs, but it's better this way because those might change with the kernel version).

Usage

Extremely easy to use, with 5 straightforward functions only. See test_simple.c and test_async.c for complete wav player examples.

Include nanoalsa.h in your source files, and in exactly one source file also define ALSA_IMPLEMENTATION. Quick example:

#define ALSA_IMPLEMENTATION                               /* get the single header library */
#include "nanoalsa.h"

alsa_t ctx;                                               /* internal state (few bytes only) */

alsa_open(&ctx, 0, 0, SNDRV_PCM_FORMAT_S16_LE, 44100, 2); /* specify requested characteristics */
alsa_write(&ctx, buf, numframes);                         /* play the audio */
alsa_close(&ctx);                                         /* free resources */

Configuration

If you really want to fine tune the latency then you can configure the library with the ALSA_BUFFER_SIZE and ALSA_PERIOD_SIZE defines, but you really don't need to mess with these, It Just Works (TM).

Normally if Nano ALSA fails to set up a configuration, then it fallbacks to 16 bit signed stereo, and resamples the PCM data from software. Specify ALSA_NORESAMPLE to avoid this.

Thread-safety

Yeah, totally thread-safe, no worries. You can't use multiple instances anyway, because the Linux kernel won't allow you non-exclusive access to the sound card device file. Will work perfectly for different cards though.

Error Codes

All functions return a simple numerical error code.

Define Description
ALSA_SUCCESS Everything was OK.
ALSA_ERR_INP Bad input parameter passed to the function
ALSA_ERR_DEV Not an ALSA device or sound card already in use
ALSA_ERR_HWPAR The ioctl to set the hardware parameters failed
ALSA_ERR_SWPAR The ioctl to set the software parameters failed
ALSA_ERR_MMAP Unable to map the sound card's registers
ALSA_ERR_PREP Unable to prepare the hardware channel
ALSA_ERR_THREAD Unable to start worker thread (async interface only)

API

Opening the Audio Stream

int alsa_open(alsa_t *ctx, int card, int device, int fmt, int freq, int chan);

Opens the PCM audio stream. The default ALSA device is card = 0, device = 0.

Argument Description
ctx The Nano ALSA instance
card ALSA card number, try 0
device ALSA device number, try 0
fmt Sample format, see SNDRV_PCM_FORMAT_x defines
freq The samples' frequency, for example 44100 or 48000
chan Number of channels, 1 mono, 2 stereo, 5 dolby etc.

Returns 0 on success, an error code otherwise. The most common error is ALSA_ERR_HWPAR, which means the sound card does not support the specified frequency or the requested sample format. Try a different one and convert your samples from software. (Note that unless ALSA_NORESAMPLE defined, Nano ALSA will try signed 16 bit stereo after the first failed attempt, and only reports an error if even that fails. If the fallback succeeds, then alsa_write will transparently convert the PCM data for you.)

Playing Audio

int alsa_write(alsa_t *ctx, void *buf, unsigned int numframes);

Plays the PCM samples in the buffer. Samples must be interleaved: one frame is as many samples as the number of channels. For a stereo sound, one frame would be left sample, right sample. Each sample must be in the format specified to alsa_open in fmt, and there must be as many samples as specified in chan. This repeats numframes times in the buffer, for example left, right, left, right, left, right, etc.

Argument Description
ctx The Nano ALSA instance
buf Buffer with interleaved PCM samples
numframes Number of frames (samples * channels) in the buffer

Returns 0 on success, otherwise the failed ioctl call's return value. More info can be found in libc's errno variable, altough most likely it will be set to EAGAIN. Not expected to fail.

Closing the Audio Stream

void alsa_close(alsa_t *ctx);

Closes the stream and frees all internal buffers.

Argument Description
ctx The Nano ALSA instance

Async API

There are two more functions, which can be used instead of alsa_write to play sound in the background. These are only available if you include pthread beforehand, and you link your program with -lpthread.

#include <pthread.h>
#define ALSA_IMPLEMENTATION
#include "nanoalsa.h"

Starting Playback

int alsa_start(alsa_t *ctx, alsa_callback_t callback, void *data);

This starts the playback in the background, so this function returns immediately. There can be only one worker thread running at any given time. The PCM data is filled in by the specified callback function.

Argument Description
ctx The Nano ALSA instance
callback The PCM data provider function
data Any data pointer that you want to be passed to the callback

Returns 0 on success, otherwise an error code. ALSA_ERR_INP is returned if there's already a worker thread running. Call alsa_stop first in this case.

The prototype of your callback function is as follows:

void (*alsa_callback_t)(void *buf, int samplesize, int channels, int frames, void *data);
Argument Description
buf Buffer to write the interleaved PCM samples to
samplesize Size of each sample in bytes
channels Number of channels, 1 mono, 2 stereo, 5 dolby etc.
frames Number of frames (samples * channels) to write
data Your callback's private data area (can be NULL)

Stopping Playback

int alsa_stop(alsa_t *ctx);

Stops the worker thread and silences audio.

Argument Description
ctx The Nano ALSA instance

Returns 0 on success.

That's all folks! Have fun!

bzt