Projects   ›   Music Assistant   ›   SendSpin ESP32

SendSpin ESP32

Hardware

Waveshare ESP32-S3-Zero
PCM5102A DAC

ESP32 ↔ PCM5102A Pin Connection Table

PCM5102A Pin ESP32 Pin Function
VCC 5V Power input for the DAC module
GND GND Common ground
LCK GPIO2 I2S Word Select (Left/Right clock)
BCK GPIO1 I2S Bit Clock
DIN GPIO3 I2S Data Out from ESP32
SCL Not used PCM5102A can run without SCL on ESP32
FLT GND Filter select (optional)
FMT GND Audio data format?
DMP GND Deemphasis control
XMT 3.3V Keep DAC enabled (active high)

Notes

  • FLT/FMT/DMP must be pulled to ground. Without it, the audio was very noisy.

Software

The ESPHome project is build with version 2026.3.0-dev

Also verified it can be build on 2026.4.0, but never tested it on device. It also shows deprecation warnings during build process.

Project Overview

This configuration builds a SendSpin network audio player using:

  • ESP32-S3 (DevKitC-1)
  • ESP-IDF framework
  • External I2S DAC
  • ESPHome firmware
  • Streaming via SendSpin
  • Designed for use with Music Assistant

The device functions as:

  • A network-synced audio endpoint
  • A Home Assistant media player
  • A SendSpin group-capable client

Optimized for low latency, stable WiFi streaming, efficient PSRAM usage, and 44.1kHz playback.


System Architecture

Music Assistant
        ↓
 SendSpin
        ↓
ESPHome media_source
        ↓
speaker_source
        ↓
I2S Audio → External DAC

The device receives audio via SendSpin, buffers it, and outputs it via I2S to an external DAC.


ESPHome Config

substitutions:
  name: sendspin-esp32-s3-zero
  friendly_name: Sendspin ESP32
  task_stack_in_psram: "true"  # Test with this set to false!

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2025.12.0
  platformio_options:
    board_build.flash_mode: dio

esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  flash_size: 4MB
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_PARTITION_TABLE_SINGLE_APP: "y" 
      CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y"
      CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y"
      CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y"
      CONFIG_MBEDTLS_SSL_PROTO_TLS1_3: "y"
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "10"
      #CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y" # Try this one later!

psram:
  mode: quad
  speed: 80MHz

logger:

api:

ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: NONE

i2s_audio:
  i2s_lrclk_pin: 2
  i2s_bclk_pin: 1

light:
  - platform: esp32_rmt_led_strip
    id: status_led
    name: "Status LED"
    pin: GPIO21
    rgb_order: GRB
    num_leds: 1
    chipset: ws2812

external_components:
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: 4de44121a84474e89eda9f609c02a270d4b12152
    components: [mixer]
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: a2fbb0ba70bb589ee7e9acc70f76d66376b5be8a
    components: [resampler]
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: a2d98e1d5e020200db8f3caf27a74a939a661dc4
    components: [audio]
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: bff22983a390352360796f8e1363127e0cd1f898
    components: [media_player]
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: 2ea7188877f537c9b37f285b7df88ea2219e0d2f
    components: [mdns, sendspin]
  - source:
      type: git
      url: https://github.com/esphome/esphome
      ref: 4f1cce8d50a967cfcac24bb56360601d5af17a56
    components: [http_request, media_source, speaker_source]

sendspin:
  id: sendspin_hub
  task_stack_in_psram: true
  kalman_process_error: 0.1

media_source:
  - platform: sendspin
    id: sendspin_source

text_sensor:
  - platform: sendspin
    type: title
    name: Track Title
    id: track_title 
  - platform: sendspin
    type: artist
    name: Artist
    id: artist_name
  - platform: sendspin
    type: album
    name: Album
    id: album_name

speaker:
  - platform: i2s_audio
    id: sendspin_speaker
    sample_rate: 44100
    i2s_dout_pin: 3
    bits_per_sample: 16bit
    dac_type: external
    channel: stereo
    timeout: never
    buffer_duration: 60ms

media_player:
  - platform: sendspin
    id: sendspin_group_media_player

  - platform: speaker_source
    id: sendspin_player
    media_speaker: sendspin_speaker
    sources:
      - sendspin_source
    volume_increment: 5%
    volume_initial: 50%
    name: "${friendly_name} Player"

interval:
  - interval: 1s
    then:
      - if:
          condition:
            media_player.is_playing: sendspin_player
          then:
            - light.turn_on:
                id: status_led
                brightness: 50%
                red: 0%
                green: 100%
                blue: 0%
          else:
            - light.turn_on:
                id: status_led
                brightness: 30%
                red: 0%
                green: 0%
                blue: 100%

button:
  - platform: restart
    name: "Restart"

Substitutions

task_stack_in_psram:

  • true: More internal RAM available, slightly slower access
  • false: Faster task execution, higher internal RAM usage

Recommended: Test both if tuning for ultra-low latency.


ESP32 Configuration

Board: esp32-s3-devkitc-1
Flash Size: 4MB
Framework: ESP-IDF

Key SDK Options:

  • CONFIG_PARTITION_TABLE_SINGLE_APP: Maximizes app space
  • CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: Prevents internal RAM pressure
  • CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: Moves TLS to PSRAM
  • CONFIG_ESP_TASK_WDT_TIMEOUT_S=10: Prevents watchdog resets during streaming

Optional:

  • CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: Increases CPU to 240MHz for better performance.

PSRAM Configuration

Mode: Quad
Speed: 80MHz

Required for stable DMA buffering and preventing internal RAM exhaustion.


Connectivity

WiFi power_save_mode: NONE

Disables WiFi power saving to reduce latency and jitter. Highly recommended for audio streaming.


Audio Configuration

Sample Rate: 44100 Hz
Bit Depth: 16-bit
Channels: Stereo
Buffer Duration: 60ms

60ms output buffer results in low-latency playback while maintaining stability.
Lowering below 40ms may cause crackling or underruns.


SendSpin Configuration

kalman_process_error: 0.1

Lower values provide smoother synchronization.
Higher values provide faster reaction to timing corrections.
0.1 favors responsiveness.


Performance Characteristics

Estimated device-side latency:
Pause: ~60–120ms
Seek: ~100–200ms

Spotify track skip delay depends on upstream buffering in Music Assistant.


Tuning Guide

For lowest latency:

  1. Enable 240MHz CPU
  2. Test task_stack_in_psram: false
  3. Keep WiFi power save disabled
  4. Keep buffer between 40–60ms
  5. Match sample rate to stream (44.1kHz)

Maintenance

OTA updates enabled.
Restart button exposed to Home Assistant.
Logger active for debugging.