I have picked up the work from bridadan and jorgenkraghjakobsen towards a ESP32 Snapcast client. It is a full featured snapcast client which supports the codecs FLAC, OPUS and PCM 16bit audio streams with sample rates up to 48Khz maybe more, I didn’t test.
Please check out the task list and feel free to fill in.
I dropped the usage of ADF completely but copied stripped down, needed components to this project (using ESP-ADF v2.6). This was necessary because ADF was using flac in closed source precompiled library which made it impossible to get good results for multiroom syncing.
The codebase is split into components and build on ESP-IDF v5.5.1 (tested with v5.1.5 and v5.5.1, but any v5.x should work). I still have some refactoring on the todo list as the concept has started to settle and allow for new features can be added in a structured manner. In the code you will find parts that are only partly related features and still not on the task list. Also there is a lot of code clean up needed, as there is quite some dead code too.
Components
nc -klu 9999 to see the logs. Many thanks to AchimPietersThe snapclient functionality are implemented in a task included in main - but should be refactored to a component at some point.
I did my own syncing implementation which is different than jorgenkraghjakobsen’s approach in the original repository, at least regarding syncing itself. I tried to replicate the behavior of how badaix did it for his original snapclients.
The snapclient frontend handles communication with the server and after successfull hello handshake it dispatches packages from the server. Normally these packages contain messages in the following order:
Each WIRE_CHUNK of audio data comes with a timestamp in server time and clients can use information from TIME and SERVER_SETTING messages to determine when playback has to be started. We handle this using a buffer with a length that compensate for playback-delay, network jitter and DAC to speaker (determined through SERVER_SETTING).
In this implementation I have separated the sync task to a backend on the other end of a freeRTOS queue. Now the front end just needs to pass on the decoded audio data to the queue with the server timestamp and chunk size. The backend reads timestamps and waits until the audio chunk has the correct playback-delay to be written to the DAC amplifer speaker through i2s DMA. When the backend pipeline is in sync, any offset gets corrected by inserting a single sample every chunk_ms, which is determined by the server.
You will need an ESP32 or ESP32-S2 and an I2S DAC. For pinout see the default config options in menuconfig (Audio Board).
Clone this repo:
git clone https://github.com/CarlosDerSeher/snapclient
cd snapclient
Update third party code (opus, flac, esp-dsp, improv_wifi):
git submodule update --init
Copy one of the template sdkconfig files and rename it to sdkconfig…
…on Linux:
cp sdkconfig_lyrat_v4.3 sdkconfig
…on Windows:
copy sdkconfig_lyrat_v4.3 sdkconfig
If you’re on Linux : follow official Espressif instructions
For debian based systems you’ll need to do the following:
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
mkdir -p ~/esp
cd ~/esp
git clone -b v5.5.1 --recursive https://github.com/espressif/esp-idf.git
cd ~/esp/esp-idf
./install.sh esp32
. ./export.sh
Configure your platform:
idf.py menuconfig
Choose configuration options to match your setup
? to have more info.idf.py build flash monitor
esptool.py --chip esp32 merge_bin -o merged.bin --flash_size 4MB --flash_freq 80m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0xd000 build/ota_data_initial.bin 0x10000 build/snapclient.bin 0x370000 build/storage.bin
Setup a snapcast server on your network
On a linux box:
Install Snapcast and start the server. You won’t need the following command if you installed it as a service.
./snapserver
Pipe some audio to the snapcast server fifo
mplayer http://ice1.somafm.com/secretagent-128-aac -ao pcm:file=/tmp/snapfifo -af format=s16LE -srate 48000
Test the server config on other known platform
./snapclient from the snapcast repo
Android : snapclient from the app play store
Update your client(s) over the air.
Note:
In commits 98c439d and 4fcf3a6 the partition table has been altered, so there is a chance that systems running versions of the Firmware < v0.0.3 (before 2025-12-28 22:58:54) will encounter troubles during OTA upgrade. In tests we found OTA works without any issues but it hasn’t been tested for every possible hardware combination. So be prepared to flash using serial connection if something goes sideways.
On a linux box:
cd snapclient
idf.py build
curl snapclient.local:8032 --data-binary @- < build/snapclient.bin
Replace snapclient.local with your clients IP address. If you have multiple clients you could use the Android or Web App to find out your clients IPs.
You are very welcome to help and provide Pull Requests to the project. Use develop branch for your PRs as this is the place where new features will go.
We strongly suggest you activate pre-commit hooks in this git repository before starting to hack and make commits.
Assuming you have pre-commit installed on your machine (using pip install
pre-commit or, on a debian-like system, sudo apt install pre-commit), type:
:~/snapclient$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
Then on every git commit, a few sanity/formatting checks will be performed.
components/audio_hal/driver/es8388/es8388.c which results in loud noise between i2s channel initialization and i2s enabling. The issue was caused by three lines setting undocumented registers. This was fixed by a simple hack in this repo: we modified the file to comment out these lines.ensure_noiseless function in player.c, that streams silence for a short period of time, once. It was originally added to fix the aforementioned noise issue. It remains in the codebase (although the es8388 driver is fixed now) because it prevents random clicks and pops during playback, especially when PLAYER: pcm chunk queue not created messages occur. The root cause of the clicks and the reason why this hack works are currently unknown.