279 lines
6.9 KiB
Markdown
279 lines
6.9 KiB
Markdown
# Static ALSA Library for Zig
|
|
|
|
A minimal, self-contained static build of ALSA (Advanced Linux Sound Architecture) designed for embedding in Zig applications.
|
|
|
|
## What This Library Provides
|
|
|
|
### Included Components
|
|
- **Core ALSA**: Configuration, error handling, async operations
|
|
- **PCM (Audio Playback/Recording)**: Hardware access, format conversion, routing
|
|
- **Control**: Hardware mixer controls, card enumeration
|
|
- **Timer**: Hardware timer access
|
|
- **Built-in Plugins**: Essential audio processing without dynamic loading
|
|
|
|
### Built-in PCM Plugins
|
|
- `hw` - Direct hardware access
|
|
- `copy` - Simple audio copying
|
|
- `linear` - Linear format conversion
|
|
- `null` - Null audio sink
|
|
- `empty` - Empty audio source
|
|
- `adpcm` - ADPCM compression
|
|
- `alaw` - A-law compression
|
|
- `mulaw` - μ-law compression
|
|
- `iec958` - S/PDIF digital audio
|
|
- `lfloat` - Linear floating point conversion
|
|
- `mmap_emul` - Memory mapping emulation
|
|
- `route` - Audio channel routing
|
|
|
|
## Limitations
|
|
|
|
### What's NOT Included
|
|
- **Dynamic Plugin Loading**: No `dlopen()` support - only built-in plugins work
|
|
- **Complex Plugins**: No `dmix`, `dsnoop`, `pulse`, `jack` plugins
|
|
- **Rate Conversion**: No `rate` plugin (would need external rate converter)
|
|
- **Advanced Routing**: No `multi`, `asym` plugins
|
|
- **Software Volume**: No `softvol` plugin
|
|
- **File I/O**: No `file` plugin for audio recording to disk
|
|
|
|
### Audio Limitations
|
|
- Only direct hardware access (`hw:X,Y`) and built-in conversions
|
|
- No automatic sample rate conversion
|
|
- No software mixing of multiple streams
|
|
- No PulseAudio/JACK integration
|
|
|
|
## Integration into Zig Projects
|
|
|
|
### 1. Add as Dependency
|
|
|
|
In your `build.zig.zon`:
|
|
```zig
|
|
.dependencies = .{
|
|
.alsa = .{
|
|
.path = "../path/to/0-alsa-lib",
|
|
},
|
|
},
|
|
```
|
|
|
|
### 2. Link in build.zig
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
pub fn build(b: *std.Build) void {
|
|
const exe = b.addExecutable(.{
|
|
.name = "my-audio-app",
|
|
.root_source_file = b.path("src/main.zig"),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
});
|
|
|
|
// Add ALSA dependency
|
|
const alsa_dep = b.dependency("alsa", .{});
|
|
const alsa_lib = alsa_dep.artifact("asound");
|
|
|
|
exe.linkLibC();
|
|
exe.linkLibrary(alsa_lib);
|
|
exe.addIncludePath(alsa_dep.path("zig-out/include"));
|
|
|
|
b.installArtifact(exe);
|
|
}
|
|
```
|
|
|
|
### 3. Basic Usage Example
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
const c = @cImport({
|
|
@cInclude("alsa/asoundlib.h");
|
|
});
|
|
|
|
pub fn main() !void {
|
|
var pcm: ?*c.snd_pcm_t = null;
|
|
|
|
// Open PCM device for recording
|
|
const result = c.snd_pcm_open(
|
|
&pcm,
|
|
"hw:0,0", // Card 0, Device 0
|
|
c.SND_PCM_STREAM_CAPTURE,
|
|
0
|
|
);
|
|
|
|
if (result < 0) {
|
|
std.log.err("Cannot open audio device: {s}", .{c.snd_strerror(result)});
|
|
return;
|
|
}
|
|
defer _ = c.snd_pcm_close(pcm);
|
|
|
|
// Configure PCM parameters
|
|
var params: ?*c.snd_pcm_hw_params_t = null;
|
|
_ = c.snd_pcm_hw_params_malloc(¶ms);
|
|
defer c.snd_pcm_hw_params_free(params);
|
|
|
|
_ = c.snd_pcm_hw_params_any(pcm, params);
|
|
_ = c.snd_pcm_hw_params_set_access(pcm, params, c.SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
_ = c.snd_pcm_hw_params_set_format(pcm, params, c.SND_PCM_FORMAT_S16_LE);
|
|
_ = c.snd_pcm_hw_params_set_channels(pcm, params, 1);
|
|
|
|
var rate: c_uint = 16000;
|
|
_ = c.snd_pcm_hw_params_set_rate_near(pcm, params, &rate, null);
|
|
_ = c.snd_pcm_hw_params(pcm, params);
|
|
|
|
// Read audio data
|
|
var buffer: [1024]i16 = undefined;
|
|
const frames_read = c.snd_pcm_readi(pcm, &buffer, buffer.len / 2);
|
|
std.log.info("Read {} frames", .{frames_read});
|
|
}
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### ALSA Configuration File
|
|
|
|
The library uses ALSA's configuration system. The configuration file defines how device names are resolved and what plugins are available.
|
|
|
|
**Critical**: Without proper configuration, even basic `hw:X,Y` device access may fail.
|
|
|
|
```bash
|
|
# Set custom config path (recommended)
|
|
export ALSA_CONFIG_PATH=/path/to/your/alsa.conf
|
|
|
|
# Run your application
|
|
./my-audio-app
|
|
|
|
# Or disable config entirely (hardware-only, may not work)
|
|
export ALSA_CONFIG_PATH=/dev/null
|
|
```
|
|
|
|
### Example alsa.conf (Minimal Working Configuration)
|
|
|
|
This is the minimal configuration needed for `hw:X,Y` device access:
|
|
|
|
```
|
|
# Essential: Define the hw PCM type
|
|
pcm.hw {
|
|
@args [ CARD DEV SUBDEV ]
|
|
@args.CARD {
|
|
type string
|
|
default 0
|
|
}
|
|
@args.DEV {
|
|
type integer
|
|
default 0
|
|
}
|
|
@args.SUBDEV {
|
|
type integer
|
|
default -1
|
|
}
|
|
type hw
|
|
card $CARD
|
|
device $DEV
|
|
subdevice $SUBDEV
|
|
}
|
|
|
|
# Optional: Define default devices
|
|
pcm.!default {
|
|
type hw
|
|
card 0
|
|
device 0
|
|
}
|
|
|
|
ctl.!default {
|
|
type hw
|
|
card 0
|
|
}
|
|
|
|
# Optional: Define named devices
|
|
pcm.microphone {
|
|
type hw
|
|
card 3
|
|
device 0
|
|
}
|
|
```
|
|
|
|
### Configuration Behavior
|
|
|
|
- **With minimal alsa.conf**: `hw:X,Y` device access works
|
|
- **Without alsa.conf** (`ALSA_CONFIG_PATH=/dev/null`): May fail with "Unknown PCM" errors
|
|
- **With system config**: May fail if it references plugins not built into this static library
|
|
|
|
## Device Selection
|
|
|
|
### Hardware Device Format
|
|
```
|
|
hw:CARD,DEVICE
|
|
```
|
|
|
|
Examples:
|
|
- `hw:0,0` - Card 0, Device 0 (usually built-in audio)
|
|
- `hw:1,0` - Card 1, Device 0 (usually USB audio)
|
|
- `hw:3,0` - Card 3, Device 0 (specific USB device)
|
|
|
|
### Finding Available Devices
|
|
|
|
```bash
|
|
# List audio cards
|
|
cat /proc/asound/cards
|
|
|
|
# List PCM devices
|
|
cat /proc/asound/pcm
|
|
|
|
# Test device access
|
|
aplay -l # playback devices
|
|
arecord -l # capture devices
|
|
```
|
|
|
|
## Build Configuration
|
|
|
|
The library is configured for static linking with these key settings:
|
|
|
|
```zig
|
|
// In build.zig - these are already set
|
|
.HAVE_LIBDL = 1, // Keep dlmisc.c but use static symbols
|
|
.BUILD_PCM_PLUGIN_* = "1", // Enable built-in plugins
|
|
// PIC undefined // Enable static plugin symbols
|
|
```
|
|
|
|
### Compiler Flags
|
|
- `-DALSA_STATIC` - Static library build
|
|
- `-DBUILD_STATIC_ONLY` - No shared library support
|
|
- `-UPIC` - Undefine PIC to enable built-in plugin symbols
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **"Unknown PCM device"**
|
|
- Device doesn't exist or wrong card/device number
|
|
- Check `/proc/asound/cards` and `/proc/asound/pcm`
|
|
|
|
2. **"No such device or address"**
|
|
- Device is busy or doesn't support the requested format
|
|
- Try different sample rate/format or check if device is in use
|
|
|
|
3. **"Cannot open shared library [builtin]"**
|
|
- PIC is defined - this shouldn't happen with this build
|
|
- Indicates dynamic loading attempt (build configuration error)
|
|
|
|
### Debug Environment Variables
|
|
|
|
```bash
|
|
# Enable ALSA debug output
|
|
export ALSA_DEBUG=1
|
|
|
|
# Use minimal config
|
|
export ALSA_CONFIG_PATH=/dev/null
|
|
|
|
# Specify custom config
|
|
export ALSA_CONFIG_PATH=./my-alsa.conf
|
|
```
|
|
|
|
## Performance Characteristics
|
|
|
|
- **Memory**: ~200KB static library
|
|
- **Latency**: Direct hardware access (no software mixing overhead)
|
|
- **CPU**: Minimal - only format conversion overhead for built-in plugins
|
|
- **Compatibility**: Works with any ALSA-compatible hardware
|
|
|
|
## License
|
|
|
|
This static build follows the original ALSA library licensing (LGPL 2.1+).
|