fix: share soundcard between darkice and ISR via ALSA dsnoop

hw:0,0 is an exclusive ALSA device — darkice holding it caused arecord
to fail silently (stderr was /dev/null), leaving all recordings at 0 bytes
with no errors in the log.

asound.conf: defines a dsnoop virtual device 'shared_mic' that opens
hw:0,0 once and lets multiple processes capture simultaneously.

docker-compose.yml: mount asound.conf into the container as
/etc/asound.conf; add ipc: host so the container shares the host IPC
namespace (dsnoop uses System V shared memory which does not cross the
container IPC boundary without this).

config.example.ini: document the dsnoop setup and shared-device pattern.
README, CLAUDE.md: document the full setup procedure.
This commit is contained in:
2026-04-26 14:21:31 +02:00
parent 357b3e6ed9
commit e67e27f047
5 changed files with 65 additions and 3 deletions
+9 -2
View File
@@ -82,5 +82,12 @@ The `output_directory` value is used as-is: a relative path like `recordings` re
## Docker ## Docker
Two services share a `./recordings` bind mount: Two services share a `./recordings` bind mount:
- `recorder` — runs `isr.py`; volume mounted at `/app/recordings` (matches `output_directory = recordings`); maps `/dev/snd` for ALSA access; `stop_grace_period: 30s` so open files are closed before SIGKILL - `recorder` — runs `isr.py`; volume at `/app/recordings`; mounts `asound.conf` as `/etc/asound.conf`; maps `/dev/snd`; `ipc: host` for dsnoop shared memory; `stop_grace_period: 30s`
- `web` — runs `web.py`; same `./recordings` bind mounted read-only at `/recordings`; exposes port 8080 - `web` — runs `web.py`; same `./recordings` read-only at `/recordings`; exposes port 8080
**Sharing the soundcard with darkice (or any other ALSA app):**
ALSA `hw:` devices are exclusive. `asound.conf` defines a `dsnoop` virtual device `shared_mic` that both processes use instead:
1. `sudo cp asound.conf /etc/asound.conf` on the host
2. Change darkice config to `device = shared_mic`
3. Set `device = shared_mic` in `config.ini`
4. `ipc: host` in `docker-compose.yml` is already set — required for dsnoop shared memory to cross the container boundary
+18 -1
View File
@@ -182,7 +182,24 @@ arecord -l # list capture hardware
ls -la /dev/snd # check device nodes ls -la /dev/snd # check device nodes
``` ```
**Stream-only deployments:** If you don't use soundcard recording, remove the `devices` block from `docker-compose.yml` — the image works fine without it. **Sharing the soundcard with another app (e.g. darkice):** ALSA `hw:` devices are exclusive — only one process can hold them at a time. `asound.conf` in this repo defines a `dsnoop` virtual device (`shared_mic`) that lets multiple processes capture simultaneously:
```bash
# 1. Deploy the ALSA config to the host (once)
sudo cp asound.conf /etc/asound.conf
# 2. Change darkice (or any other app) to use device "shared_mic" instead of hw:0,0
# 3. In config.ini set: device = shared_mic
# docker-compose.yml already mounts asound.conf and sets ipc: host
# (ipc: host is required so the container shares the host IPC namespace for dsnoop shared memory)
# 4. Restart everything
sudo systemctl restart darkice
docker compose down && docker compose up -d --build
```
**Stream-only deployments:** If you don't use soundcard recording, remove the `devices` block and the `asound.conf` volume mount and `ipc: host` line from `docker-compose.yml` — the image works fine without them.
**Log file in Docker:** The recorder always logs to stdout, so `docker compose logs -f` shows live output. To persist logs on the host, set `log_file = /app/recordings/recorder.log` in `config.ini` (the `recordings` directory is the bind mount). **Log file in Docker:** The recorder always logs to stdout, so `docker compose logs -f` shows live output. To persist logs on the host, set `log_file = /app/recordings/recorder.log` in `config.ini` (the `recordings` directory is the bind mount).
+20
View File
@@ -0,0 +1,20 @@
# Shared ALSA capture device using dsnoop.
# Allows multiple processes (e.g. darkice and ISR) to read from hw:0,0 simultaneously.
#
# Deploy:
# sudo cp asound.conf /etc/asound.conf (host — for darkice)
# docker-compose.yml mounts it into the recorder container automatically.
#
# Both darkice and ISR must use "shared_mic" as their device name instead of "hw:0,0".
pcm.shared_mic {
type dsnoop
ipc_key 1024
ipc_perm 0666
slave {
pcm "hw:0,0"
channels 2
rate 48000
format S16_LE
}
}
+16
View File
@@ -144,3 +144,19 @@ format = auto
# sample_rate = 44100 # sample_rate = 44100
# channels = 2 # channels = 2
# format = flac # format = flac
#
# SHARING A DEVICE WITH ANOTHER APP (e.g. darkice):
# ALSA hw: devices are exclusive — only one process can open them at a time.
# Use the dsnoop virtual device defined in asound.conf to share the hardware:
#
# 1. sudo cp asound.conf /etc/asound.conf (once, on the host)
# 2. Change the other app (darkice etc.) to use device "shared_mic" too
# 3. Use device = shared_mic in ISR config (docker-compose.yml mounts asound.conf automatically)
#
# [usb_mic_shared]
# type = soundcard
# device = shared_mic
# backend = alsa
# sample_rate = 48000
# channels = 2
# format = flac
+2
View File
@@ -4,9 +4,11 @@ services:
volumes: volumes:
- ./config.ini:/app/config.ini:ro - ./config.ini:/app/config.ini:ro
- ./recordings:/app/recordings # matches output_directory = recordings in config.ini - ./recordings:/app/recordings # matches output_directory = recordings in config.ini
- ./asound.conf:/etc/asound.conf:ro # dsnoop shared-capture config
# Soundcard (ALSA) access — comment out if you only record streams # Soundcard (ALSA) access — comment out if you only record streams
devices: devices:
- /dev/snd:/dev/snd - /dev/snd:/dev/snd
ipc: host # share IPC namespace with host so dsnoop shared memory works
restart: unless-stopped restart: unless-stopped
stop_grace_period: 30s # allow time for open files to be closed cleanly stop_grace_period: 30s # allow time for open files to be closed cleanly