diff --git a/CLAUDE.md b/CLAUDE.md index 299fb8c..0494e35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,5 +82,12 @@ The `output_directory` value is used as-is: a relative path like `recordings` re ## Docker 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 -- `web` — runs `web.py`; same `./recordings` bind mounted read-only at `/recordings`; exposes port 8080 +- `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` 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 diff --git a/README.md b/README.md index ff93e95..5674f39 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,24 @@ arecord -l # list capture hardware 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). diff --git a/asound.conf b/asound.conf new file mode 100644 index 0000000..41b7529 --- /dev/null +++ b/asound.conf @@ -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 + } +} diff --git a/config.example.ini b/config.example.ini index 24705ca..8430d3a 100644 --- a/config.example.ini +++ b/config.example.ini @@ -144,3 +144,19 @@ format = auto # sample_rate = 44100 # channels = 2 # 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 diff --git a/docker-compose.yml b/docker-compose.yml index a96307a..d043afb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,9 +4,11 @@ services: volumes: - ./config.ini:/app/config.ini:ro - ./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 devices: - /dev/snd:/dev/snd + ipc: host # share IPC namespace with host so dsnoop shared memory works restart: unless-stopped stop_grace_period: 30s # allow time for open files to be closed cleanly