Files
aes67-ESP32-P4/docs/hardware-and-design-notes.md
T
bsncubed cd747aec40 Step 7.5b4a: player volume (Spotify app slider), applied after the ring
- player: volume 0..100 %, amplitude = (v/100)^3 (about -18 dB at 50 %,
  0 = mute), applied in the TX pull callback after the ring so a change
  is heard at once (the ring holds 4 s); ramped over one packet to avoid
  zipper noise. Spotify VOLUME events (0..65535) set it.
- Docs: pipeline order ring -> volume/gain, curve described.
- Verified from a Mac: RMS followed the slider immediately (100 % about
  -10 dBFS; steps to about -24.5 and -28 dBFS match the cubic curve for
  ~57 % / ~50 %); user: "sounds about right". Mute at 0 % not yet
  confirmed (check via /api/player/volume later).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:52:57 +10:00

4.8 KiB
Raw Blame History

ESP32-P4-ETH AES67 streamer — hardware & project notes

Core AES67 parts (PTP, TX, SDP/SAP, network/VLAN, syslog, web UI base, config schema, component layout) are in aes67-core-base.md. This file covers the board and the project-specific parts.

Board: Waveshare ESP32-P4-ETH (bought as ESP32-P4-POE-ETH, i.e. with PoE add-on module)

  • ESP32-P4, dual-core RISC-V HP @ 360 MHz + LP core, 32 MB in-package PSRAM, 32 MB NOR flash (confirmed with esptool.py flash_id)
  • No Wi-Fi/BT on this variant (the -WIFI6- variant adds an ESP32-C6). Ethernet only.
  • Ethernet: IP101GRI PHY, RMII, 100 Mb, 50 MHz ref clock from PHY (25 MHz xtal), PHY addr 1
    • REF_CLK 50 (EMAC_CLK_EXT_IN), TX_EN 49, TXD0 34, TXD1 35, CRS_DV 28, RXD0 29, RXD1 30, MDC 31, MDIO 52, PHY_RST/power 51
  • Audio: ES8311 codec (I2C 0x18), NS4150B amp (PA enable GPIO53), onboard mic
    • I2S MCLK 13, BCLK 12, LRCK 10, DOUT(to codec) 9, DIN(from codec) 11 — Waveshare wiki lists 9/11 swapped vs ESPHome/espp; verify
    • I2C SDA 7, SCL 8 (shared with DSI touch / CSI SCCB)
  • Also: MIPI-CSI + MIPI-DSI FPC, USB 2.0 HS OTG (4-pin), USB-C UART, microSD 4-bit (CLK43 CMD44 D0-D3 39-42), 2x20 header
  • ESP-IDF >= 5.3.1 per Waveshare.
  • Chip revision caveat (buyer review): some boards ship engineering silicon v1.x (not v3.x). IDF 5.5+ defaults to rev3 — enable CONFIG_ESP32P4_SELECTS_REV_LESS_V3 (check esptool.py chip_id output first). Binaries are not interchangeable between rev <3 and rev 3.
  • Crystal-based clock: fine as PTP GM on a closed network (±tens of ppm).
  • Sources: Waveshare wiki (waveshare.com/wiki/ESP32-P4-ETH), ESPHome device page (devices.esphome.io/devices/waveshare-esp32-p4-eth), espp board docs (esp-cpp.github.io/espp/dev_boards/waveshare/esp32_p4_eth.html).

Project pipeline

source (cspot Spotify Connect | HLS player) -> decode (Vorbis/AAC/MP3) -> SRC 44.1k->48k -> PSRAM ring buffer -> volume/gain -> aes67_tx pull callback

  • Volume is applied after the ring (in the pull callback) so a change is heard at once, not one ring length (4 s) later; ramped over one packet to avoid zipper noise. Curve: amplitude = (volume/100)^3 (about -18 dB at 50 %, 0 = mute). The built-in test tone (mode "tone") is not affected.
  • Spotify Connect: cspot (feelfreelinux/cspot), needs Premium, zeroconf via mDNS (on the internet interface when VLAN split). Spotify occasionally changes auth for 3rd-party clients — expect breakage risk.
  • HLS: HTTPS (mbedTLS), m3u8 parse (master->media playlist), segment fetch, TS demux or fMP4/ADTS, AAC decode (esp_audio_codec / esp-gmf).
  • Stereo L24/48k at 1 ms is about 3 Mbit/s on the wire; 100 Mb has plenty of headroom.

Source mode "auto" (Spotify with HLS failover)

  • Spotify Connect stays advertised the whole time. The HLS player runs whenever Spotify is not active.
  • Fail over to HLS when there is no Spotify session for failover_delay_s seconds. With failover_on_pause, a paused or stopped Spotify session also counts as inactive.
  • Switch back to Spotify as soon as it starts playing. Stop HLS so it doesn't use bandwidth or CPU.
  • Short crossfade or ramp (~20–50 ms) at the switch to avoid clicks. The AES67 stream and SDP are unaffected.
  • Firmware needs Spotify events: cspot connect, disconnect, play, pause.

Project config group

  • source: {mode: spotify|hls|auto|tone|off, spotify_name, spotify_bitrate, hls_url, autoplay, gain_db, failover_delay_s, failover_on_pause, spotify_client_id, spotify_client_secret}
  • spotify_client_id / spotify_client_secret: each user's own Spotify developer app (developer.spotify.com, Premium account), needed by the maintained cspot fork (philippe44/cspot) since Spotify's 2025 API restrictions. The secret is write-only (never returned by GET /api/config). spotify_state reports "no client credentials" while either is missing.
  • Project status fields: active_source, source_state, spotify_state, buffer_ms
  • mode "tone": the core's 1 kHz / -18 dBFS test tone, phase-locked to PTP (commissioning, e.g. Riedel import tests). "off": silence.

Player control API (project routes)

  • GET /api/player -> {source, forced, state(playing|paused|stopped|buffering|idle), artist, title, album, position_ms, duration_ms, volume(0-100), can:{pause,next,prev,seek}}
  • POST /api/player/{play|pause|toggle|stop|next|prev} (no body); every POST returns the same JSON as GET /api/player
  • POST /api/player/seek {ms}; POST /api/player/volume {value} or {delta}
  • POST /api/player/source {source: spotify|hls|off|config}: runtime override, not saved; "config" returns to the configured mode
  • POST /api/player/url {url}: play an m3u8 now (runtime, not saved)
  • On Spotify, commands go to cspot (the Spotify app stays in sync, including volume). On HLS (live): pause = stop fetching, resume = rejoin at live edge; next/prev/seek return 409 and can reflects that.
  • Volume = runtime player volume; source.gain_db is a fixed trim on top.
  • No auth yet: LAN only.