Wireless Dolby Atmos spatial audio pipeline for home cinema. Renders object-based audio and distributes to network speakers via Snapcast.
This project bridges the Cavern spatial audio engine with Snapcast for wireless multi-room audio playback. It uses a file-based mode for reliable Dolby Atmos (TrueHD) rendering.
Media File (TrueHD/E-AC-3/DTS)
↓
FFmpeg → truehdd → DAMF (cached)
↓
CavernPipeClient (file-based mode)
↓ [Unix Socket]
CavernPipeServer (spatial rendering)
↓ [PCM 6ch/16-bit/48kHz]
PipeToFifo → /tmp/snapcast-out
↓
Snapserver → Network (TCP/1704)
↓
Snapclients (ESP32/Speakers)
- ✅ Dolby Atmos TrueHD support via truehdd → DAMF conversion
- ✅ File-based mode - reliable, no streaming container issues
- ✅ Automatic caching - TrueHD converted once, cached forever
- ✅ Spatial rendering - 12ch Atmos objects → 6ch/8ch output
- ✅ Multi-room sync - Snapcast synchronized playback
# macOS
brew install dotnet-sdk snapcast ffmpeg
# Linux
sudo apt-get install dotnet-sdk-8.0 snapserver snapclient ffmpeg
# Build truehdd (TrueHD decoder)
git clone https://github.com/truehdd/truehdd.git /tmp/truehdd
cd /tmp/truehdd && cargo build --releaseChoose one of the following methods:
# Download all required binaries from GitHub Release
mkdir -p bin
cd bin
# CavernPipeServer (patched version with file-based mode)
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/CavernPipeServer.dll
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/CavernPipeServer.runtimeconfig.json
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/CavernPipeServer.deps.json
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/CavernPipeServer.Logic.dll
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/Cavern.dll
curl -LO https://github.com/Glider95/Cavern-snapserver-essential/releases/download/v1.0.0/Cavern.Format.dll
cd ..
# Build the client components (requires .NET 8 SDK)
./scripts/build.shSee docs/BUILD.md for complete instructions on building CavernPipeServer with patches applied.
After setup, your bin/ directory should contain:
bin/
├── CavernPipeServer.dll # Server (download or build)
├── CavernPipeServer.runtimeconfig.json
├── CavernPipeServer.deps.json
├── CavernPipeServer.Logic.dll # Patched logic
├── Cavern.dll # Cavern engine
├── Cavern.Format.dll # Cavern formats
├── CavernPipeClient.dll # Client (built locally)
├── CavernPipeClient.runtimeconfig.json
├── CavernPipeClient.deps.json
├── PipeToFifo.dll # FIFO bridge (built locally)
├── PipeToFifo.runtimeconfig.json
└── PipeToFifo.deps.json
./scripts/build.sh# Terminal 1
./scripts/run.sh# Terminal 2 - Play a movie (auto-converts TrueHD to DAMF)
./scripts/cavern-wireless.sh ~/Movies/demo.mkv
# Or play a cached DAMF file directly
./scripts/play.sh ~/.cavern-wireless/cache/<hash>.atmos# On your ESP32/speaker device
snapclient -h <server_ip>| Script | Purpose |
|---|---|
run.sh |
Start CavernPipeServer + Snapserver |
play.sh <file> |
Play media file (auto-detects format) |
cavern-wireless.sh <file> |
Full pipeline with TrueHD conversion |
build.sh |
Build all components |
Environment variables for run.sh and play.sh:
OUTPUT_CHANNELS=6 # 2, 6 (5.1), or 8 (7.1)
SAMPLE_RATE=48000 # 48000 Hz (standard)
BIT_DEPTH=16 # 16 or 24-bit
# Example:
OUTPUT_CHANNELS=6 ./scripts/run.shSnapserver config: config/snapserver.conf
- TrueHD files are converted to DAMF format using
truehdd - DAMF files are cached in
~/.cavern-wireless/cache/ - CavernPipeClient sends file path to server (negative UpdateRate = file mode)
- CavernPipeServer opens file directly, renders spatial audio
- PCM output flows through FIFO to Snapserver
- Snapclients receive synchronized audio
Client Server
| |
|-- Handshake (UpdateRate=-1024) ->| File-based mode
|-- Path length (4 bytes) --------->|
|-- Path bytes -------------------->|
| |
|<-- PCM chunk 1 ------------------| 64KB chunks
|<-- PCM chunk 2 ------------------|
|<-- ... --------------------------|
|<-- Length=0 (EOF) ---------------|
├── bin/ # REQUIRED: Binaries (not in git, see Setup)
│ ├── CavernPipeServer.dll
│ ├── CavernPipeClient.dll
│ ├── PipeToFifo.dll
│ └── *.runtimeconfig.json
├── scripts/
│ ├── run.sh # Start infrastructure
│ ├── play.sh # Play media files
│ ├── cavern-wireless.sh # Full automation
│ └── build.sh # Build components
├── src/
│ ├── CavernPipeClient/ # Protocol bridge
│ └── PipeToFifo/ # FIFO writer
├── config/
│ └── snapserver.conf # Snapserver config
├── docs/
│ ├── PROTOCOL.md # CavernPipe protocol
│ └── TROUBLESHOOTING.md # Common issues
├── patches/ # Patched Cavern files
│ └── CavernPipeServer.Logic/
│ ├── CavernPipeProtocol.cs
│ ├── CavernPipeRenderer.cs
│ └── PipeHandler.cs
└── README.md
The patches/ folder contains modified Cavern files for file-based mode support:
- CavernPipeProtocol.cs: Accept negative UpdateRate for file mode
- CavernPipeRenderer.cs: Add
ReadHeader()andOpenFileFromPath() - PipeHandler.cs: Handle file-based handshake and chunked transfers
Apply patches to upstream Cavern before building.
Converted files stored in ~/.cavern-wireless/cache/:
<md5_hash>.atmos # DAMF header
<md5_hash>.atmos.audio # PCM audio data
<md5_hash>.atmos.metadata # Object positions
<md5_hash>.truehd # Extracted TrueHD (temporary)
| Issue | Workaround |
|---|---|
| Streaming mode has container parsing issues | Use file-based mode (default) |
| TrueHD requires conversion | Auto-converted and cached on first play |
| Minor artifacts/jitter | Known limitation of current implementation |
- VoidXH/Cavern - Spatial audio engine
- truehdd/truehdd - TrueHD decoder
- badaix/snapcast - Multi-room audio
- CavernPipeClient, PipeToFifo: MIT
- Cavern: See upstream license
- Snapcast: GPLv3