Extremely small single header lib for PCM playback under Linux
bzt 2377fb98ba Added transparent software resampling | vor 1 Jahr | |
---|---|---|
Makefile | vor 1 Jahr | |
README.md | vor 1 Jahr | |
nanoalsa.h | vor 1 Jahr | |
test_async.c | vor 1 Jahr | |
test_simple.c | vor 1 Jahr |
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).
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 */
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.
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.
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) |
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.)
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.
void alsa_close(alsa_t *ctx);
Closes the stream and frees all internal buffers.
Argument | Description |
---|---|
ctx |
The Nano ALSA instance |
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"
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 ) |
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