# P4 AES67 Streamer: user guide The P4 AES67 Streamer turns Spotify Connect or an internet radio stream (HLS / m3u8) into an **AES67 multicast stream**, locked to your PTP clock. Any AES67 receiver on the network can play it: a Riedel Artist 4-wire port, a Dante device in AES67 mode, a DAW, and so on. It runs on a **Waveshare ESP32-P4-ETH** board, powered over PoE, and is set up entirely from a web page. Contents: [Getting started](#getting-started) · [The web page](#the-web-page) · [Sources](#sources) · [Spotify](#spotify) · [AES67 output](#aes67-output) · [PTP](#ptp) · [Network](#network) · [Logging](#logging) · [Firmware updates](#firmware-updates) · [Player API](#player-api) · [Troubleshooting](#troubleshooting) · [Limitations](#limitations) --- ## Getting started 1. **Connect** the board's Ethernet port to a PoE switch port, or to a normal port and power it over USB-C. Use the same network (or VLAN) as your AES67 devices and your PTP grandmaster. 2. **Wait about 20 seconds.** The board gets an address by DHCP and locks to PTP. 3. **Open the web page** at **http://p4-aes67.local/**. Alternatively, look up the board's IP in your router's DHCP list (the name is `p4-aes67`) and open `http:///`. 4. **Pick a source** under *Source* and click **Save** (see [Sources](#sources)). 5. **On the receiver**, either find the stream via SAP, or copy the SDP from the page's *SDP* section (see [AES67 output](#aes67-output)). Out of the box the board streams **2-channel, 24-bit, 48 kHz audio with 1 ms packets to 239.69.1.10:5004** and announces it by SAP under the name "P4 AES67". > **More than one board?** Each board needs its own hostname, stream name and multicast address, or they clash. Change them under *Network* and *AES67 output*, or let `tools/flash_board.py` ask for them when you flash a new board. --- ## The web page The page is split into sections, from top to bottom: | Section | What it's for | |---|---| | **Status** | Firmware version, IP / MAC, link speed, active source, Spotify state, buffer, packets sent, underruns, uptime, memory, chip temperature. | | **PTP status** | Whether the clock is locked, which grandmaster it follows, offset and path delay. *Details* shows more. | | **Player** | What's playing now, transport buttons, progress bar (drag to seek), volume. | | **Source**, **PTP**, **AES67 output**, **Network**, **Logging** | Settings. They take effect only when you click **Save**. | | **SDP** | The stream description, to copy or download for receivers. | | **Firmware** | Update the firmware (see [Firmware updates](#firmware-updates)). | **Saving:** *Save*, *Revert* (throw away unsaved edits) and *Reboot* stay at the bottom of the window while you scroll through the settings. Messages such as "Saved." or error texts appear next to them. Invalid values are rejected with the reason, and nothing is saved. **Light / dark:** the page is dark by default. The **◐** button at the top right switches to light, and your browser remembers the choice. --- ## Sources Choose the source under **Source → Source**: | Source | What it plays | |---|---| | **Spotify Connect** | The board appears as a speaker in the Spotify app (default). Needs your own Spotify app credentials; see [Spotify](#spotify). | | **HLS / M3U8 URL** | An internet radio stream, e.g. `https://…/playlist.m3u8`. Enter it under *Stream URL*. | | **Spotify, fail over to HLS** | Spotify when someone plays to it, otherwise the stream (see below). | | **Test tone** | 1 kHz sine at −18 dBFS, locked to PTP. For line-up and receiver tests. | | **Pink noise** | Pink noise at −18 dBFS RMS (peaks around −6 dBFS), the same signal on both channels. For level and EQ checks. | | **Off** | Silence. The stream keeps running. | ### Failover (Spotify, fail over to HLS) - **Spotify playing:** the board plays Spotify, switching over within about a second of pressing play. - **No Spotify connection:** after *Failover delay* seconds (default 5), the board switches to the HLS stream. - **Paused:** with **Also fail over when paused** ticked, a paused Spotify counts as "not playing" and the stream takes over after the delay. Unticked (default), a pause gives silence and the board stays on Spotify. - **Fades:** switches fade out and in over 30 ms, so there are no clicks. - **Resuming:** Spotify is held while the stream plays. When you press play again, the song continues where you paused. ### Volume and gain - **Volume (Player section):** the everyday volume. With Spotify it follows the app's volume slider, and the other way round. - **Output gain (dB):** a fixed trim on top of the volume, −60 to +12 dB. Above 0 dB, loud passages can clip at full scale. - **Neither affects** the test tone or pink noise. Those always come out at their calibrated level. --- ## Spotify Since 2025, Spotify requires every Spotify Connect device to use a **developer app registered to your own account**. You need **Spotify Premium**. Setup takes about five minutes: 1. Go to **https://developer.spotify.com/dashboard** and log in. 2. **Create app**: - Name and description: anything, e.g. "P4 AES67". - Redirect URI: `http://127.0.0.1:8888/callback`. Spotify requires one, but this device never uses it. - API: tick **Web API**, then save. 3. Open the app's **Settings**. Copy the **Client ID**, and click *View client secret* to copy the **Client secret**. 4. On the board's page under **Source**, paste them into **Spotify client ID** and **Spotify client secret**, then **Save**. The secret is write-only: after saving, the field shows empty. Leave it empty to keep the stored secret, or type a new one to replace it. **Connecting:** make sure your phone or computer is on the **same network** as the board. In the Spotify app, open the device list and pick **P4 AES67** (or the name you set under *Spotify device name*). Play, pause, skip, seek and volume all work from the app. The web page shows the track and position and can control playback too. - **Spotify bitrate:** 96, 160 or 320 kbit/s (default 320). - **Spotify device name:** the name shown in the app. Changing it disconnects a running session. - **Status → Spotify** shows the state: e.g. *waiting for Spotify app*, *connected*, *no client credentials*, *login failed*. --- ## AES67 output | Setting | Default | Notes | |---|---|---| | Stream name (s=) | P4 AES67 | The name receivers show. | | Enabled | on | Off stops sending. | | Discovery | SAP | **SAP**: announced every 30 s on 239.255.255.255:9875, so receivers that support SAP find it by themselves. **Manual**: no announcements; use the SDP. | | RTP multicast IP / port | 239.69.1.10 : 5004 | Must be unique per stream on your network. | | TTL | 32 | | | DSCP (media) | 34 (AF41) | QoS marking for the audio packets. | | Channels | 2 | 1 or 2. | | Mono sum | off | Sends (left + right) / 2 as a 1-channel stream. Forces *Channels* to 1; it can't clip. | | Bit depth | L24 | L24 or L16. | | Sample rate | 48000 | **Use 48000 with Spotify and HLS** (see [Limitations](#limitations)). | | Packet time | 1 ms | 0.125, 0.25, 0.333, 1 or 4 ms. 1 ms is the AES67 default and works with nearly all receivers. | | Payload type / SSRC / time stamp offset | 96 / 0 / 0 | Only change these if a receiver needs it. | **Getting the SDP to a receiver:** - **SAP:** receivers that listen for SAP (Riedel, many AES67 devices) list the stream by its name. - **By hand:** in the *SDP* section, **Copy** or **Download .sdp** and import it on the receiver. The board also serves it at `http://p4-aes67.local/stream.sdp`. - **When the SDP changes:** after a setting that changes the stream, the SDP gets a new version number. Re-import it on receivers that were set up by hand. --- ## PTP The board locks its media clock to the PTP grandmaster on the network (IEEE 1588-2008, UDP/IPv4, E2E). *PTP status* shows the lock state and the grandmaster. | Setting | Default | Notes | |---|---|---| | Mode | multicast | **hybrid** sends the delay requests unicast (fewer multicast packets). | | Role | Auto | **TimeReceiver only**: never becomes grandmaster. **Auto**: becomes grandmaster only if there is none (fallback). **Preferred TimeTransmitter**: tries to be grandmaster. | | Domain | 0 | Must match your grandmaster. | | Priority 1 / 2 | 250 / 250 | Lower wins the grandmaster election. | | Intervals, timeout, DSCP (46) | | Presets below set them. | | HW timestamping | on | Leave on (much more accurate). | **Presets:** - **Riedel SIC defaults:** TimeReceiver only, priorities 128/126, Riedel's intervals. Use this when a Riedel system provides the clock. - **AES67 media profile defaults:** the faster intervals of the AES67 profile. A preset only fills in the fields; click **Save** to apply them. Audio starts only when PTP is locked, usually about 20 s after power-up. When the board is itself the grandmaster (no other clock on the network), its clock starts at 1 January 1970. That's fine for AES67, but it isn't real time. --- ## Network - **Hostname:** the name in DHCP, mDNS (`.local`) and LLDP (the switch's neighbour list). Changing it applies at once. - **DHCP (default):** with DHCP on, the greyed-out IP, netmask, gateway and DNS fields show the addresses DHCP handed out. - **Static IP:** untick **DHCP**. The current addresses stay in the fields as a starting point; edit them and **Save**. The board **reboots** to apply them, and the page switches to the new address after 20 s. The page checks that the netmask is valid, that the IP isn't the subnet's network or broadcast address, and that the gateway is in the subnet. - **A wrong static address makes the board unreachable** over the network. Double-check before saving; see [Troubleshooting](#troubleshooting) for recovery. - With a static IP, your router's DNS name for the board can go stale. `http://.local/` keeps working. --- ## Logging The board can send its log to a syslog server (UDP): - Tick **Send to syslog** and enter the server (hostname or IP) and port (default 514). - **Minimum level:** Error, Warning, Info or Debug. - **Facility and format:** RFC 5424 or RFC 3164 (BSD). - **Send test message** checks the path. The log shows PTP lock/unlock, source switches, stream errors and firmware updates, which makes it useful for troubleshooting. --- ## Firmware updates **Over the network (normal way):** in the **Firmware** section, choose the new `aes67_p4.bin` and click **Upload & install**. The board installs it to its second firmware slot and reboots into it. - The new firmware must **prove itself** within 60 s: network up and web page answering. Only then is it kept. If it fails or crashes, the board automatically goes back to the previous firmware. - While the new firmware is on trial, the page offers **Keep this firmware** and **Roll back to previous**. - During the upload the Spotify session ends and the stream pauses. Reconnect from the app afterwards. - From a computer: `curl -f --data-binary @aes67_p4.bin -H 'Content-Type: application/octet-stream' http://p4-aes67.local/api/ota` **Over USB (new board, or recovery):** `tools/flash_board.py` (needs ESP-IDF or `pip install esptool`, plus a build). It: - checks the board (chip revision, 32 MB flash), - optionally erases it (this clears all settings), - flashes the firmware and finds the board's IP, - asks for a hostname, stream name and multicast address. The firmware `.bin` comes from building the project (`idf.py build` → `build/aes67_p4.bin`). This build is for ESP32-P4 chips of **revision v1.x** (the current Waveshare boards). A board with a v3 chip needs a build for that revision; the flash script refuses it with a hint. --- ## Player API Everything the Player section does is also available over HTTP, e.g. for a control panel or Companion. Every command returns the current player state as JSON. ``` GET /api/player state, source, track, position, volume POST /api/player/play | pause | toggle | stop | next | prev POST /api/player/seek {"ms": 90000} POST /api/player/volume {"value": 0-100} or {"delta": -5} POST /api/player/source {"source": "spotify" | "hls" | "off" | "config"} POST /api/player/url {"url": "https://…/playlist.m3u8"} ``` Example: `curl -X POST http://p4-aes67.local/api/player/next` - **With Spotify:** the app follows every command. *stop* equals *pause*. *prev* restarts the song if it has played more than 3 s, like the Spotify apps. - **With HLS (live radio):** *pause* stops downloading, and *play* rejoins the live stream. *next*, *prev* and *seek* return an error (409). - **`source` and `url`:** temporary overrides; they're not saved, and a reboot or `"source": "config"` returns to the saved setting. `url` plays a stream right away. - **Security:** there is no password. Keep the device on a trusted network. The full device API (status, config, SDP, firmware) is described in `docs/aes67-core-base.md`. --- ## Troubleshooting | Problem | What to try | |---|---| | **Can't find the page** | Try `http://p4-aes67.local/`, or the IP from your router's DHCP list. If the board has a static IP you no longer know, use `tools/flash_board.py` over USB with erase (resets all settings). | | **Board doesn't appear in the Spotify app** | Phone or computer on the same network? *Status → Spotify* should say *waiting for Spotify app*. *no client credentials*: enter ID and secret. *login failed*: check them, and check that the account is Premium. | | **"Can't connect" in Spotify on the first try** | Try again; a second attempt usually works. | | **Spotify controls do nothing** after a long idle session | Quit and reopen the Spotify app, then connect again. | | **No audio at the receiver** | *PTP status* locked? *Status → AES67 TX* counting packets? The receiver on the same grandmaster and domain? After changing stream settings, re-import the SDP. | | **Stutter or dropouts** | *Status → underruns* rising? With HLS, check the internet connection. During a firmware upload short stutters are normal. | | **HLS stream doesn't start** | Check the URL in a browser. *Status → Source* shows *buffering* while it loads, usually for a few seconds. | --- ## Limitations These are known limitations in the current firmware: - **96 kHz:** only the test tone and pink noise work correctly at 96 kHz. Spotify and HLS are 48 kHz sources and must be streamed at **48000**. At 96 kHz they play at the wrong speed. - **Autoplay stream on boot:** the checkbox has no effect yet. HLS always starts on boot. - **VLAN split** (separate VLANs for AES67 and internet) isn't available yet. - **No password** on the web page or API. - **HLS clock drift:** the radio station's clock and PTP drift slightly apart. Over many hours of continuous playback, the stream can skip ahead to catch up with the live edge.