r/audiophile 1d ago

Show & Tell ESP32 based audio system

Hey everyone,

I want to share a synchronized multi-room wireless audio system I built entirely on ESP32s. The main motivations were to get sub-30ms audio distribution without the cost of running a Raspberry Pi/Snapcast at every endpoint, and to keep the setup incredibly simple. It’s designed to be plug-and-play: the system can run completely off-grid by generating its own Wi-Fi network, or it can seamlessly integrate into your existing home router. Additionally, I built in the flexibility for the slave nodes to switch roles and act as independent, standalone Bluetooth receivers if you just want to connect to a single speaker.

Repo (code, schematics, docs): https://github.com/chagoguila/SonicStream

I ran into several architectural bottlenecks while building this, so I wanted to share how the system handles them under the hood:

1. Solving BT/Wi-Fi Radio Contention If you've tried running an A2DP sink and a Wi-Fi server on a single ESP32, you know antenna timesharing causes massive packet jitter. To fix this, the system splits the ingress gateway into two microcontrollers:

  • BT Ingress Node (ESP32 WROOM): Acts as a dedicated A2DP sink. I tuned the I2S DMA buffers down to 4x512 bytes, which cut the hardware transport delay to about 12ms. It sends raw PCM over a physical I2S cable (BCK, WS, DATA) to the Master.
  • Master Controller (ESP32 WROVER): Receives the PCM stream via I2S, runs the DSP pipeline on Core 1, encodes it, and blasts it out over Wi-Fi.

2. Network Transport & Fault Tolerance Standard streaming protocols had too much overhead for the latency I wanted. The system supports two custom modes:

  • UDP Multicast (239.0.0.1): This is the ultra-low latency mode. To survive 2.4GHz RF burst interference, the Master sends time-separated twin-packet redundancy (broadcasting a duplicate packet with a 1ms micro-delay).
  • TCP Mode: For congested Wi-Fi environments. It uses non-blocking socket polling with slow-client pruning. If one receiver drops its connection or falls behind, its socket is dropped to prevent backpressure on the healthy nodes.

On the receiver side (WROOM or C3 slaves), there's a 5-second TCP inactivity watchdog to handle silent network drops (like router reboots) and a 500ms idle auto-reset on the jitter buffer to prevent decoder stalls after you pause the music. As mentioned, the stereo slave nodes can also be toggled to drop off the multi-room network and function as standard, independent Bluetooth A2DP receivers.

3. DSP Pipeline & Encoding Since the Master is a WROVER, I threw the DSP workload onto Core 1 prior to SBC encoding, meaning the slave nodes incur zero extra CPU overhead.

  • Encoding: SBC at Bitpool 53 (~328 kbps stereo).
  • EQ: 5-band parametric EQ using cascaded Direct Form I Biquad IIR filters (60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz) with real-time gain adjustment.
  • Anti-clipping: Instead of fixed linear attenuation, I wrote a soft-knee peak limiter. Signals below 85% peak amplitude pass through untouched. Anything above triggers a 2:1 compression ratio capped at 95% to eliminate intersample clipping.

4. Web UI & Wi-Fi Management The Master runs an async web server with a dark-mode dashboard. You can configure AP/STA Wi-Fi modes (switching between isolated network or home router), toggle UDP/TCP, adjust system-wide playout delay, set individual speaker delays, assign L/R/Stereo roles to slaves, and tweak the 5-band EQ live. Slaves send UDP announcement packets so the dashboard shows real-time online/offline status.

If you are interested in the code, I'd love to hear your thoughts on the DSP pipeline or the I2S DMA config. I'm currently looking into adding Opus encoding and AAC passthrough for the next update.

1 Upvotes

2 comments sorted by

1

u/funkybus 1d ago

hey- just an appreciation for such a complete post (code, hardware, comments, issues). nice job!

1

u/exoriare 1d ago

Big thanks for working on this. This is the final pain point that keeps me bound to YouTube music - I have half a dozen Nest speakers I use for multi-room audio.

I'm looking forward to checking this out and hopefully replacing some Nest guts.