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>
This commit is contained in:
@@ -17,7 +17,8 @@ Core AES67 parts (PTP, TX, SDP/SAP, network/VLAN, syslog, web UI base, config sc
|
|||||||
- 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).
|
- 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
|
## Project pipeline
|
||||||
source (cspot Spotify Connect | HLS player) -> decode (Vorbis/AAC/MP3) -> SRC 44.1k->48k -> volume/gain -> PSRAM ring buffer -> aes67_tx pull callback
|
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.
|
- 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).
|
- 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.
|
- Stereo L24/48k at 1 ms is about 3 Mbit/s on the wire; 100 Mb has plenty of headroom.
|
||||||
|
|||||||
+38
-2
@@ -1,5 +1,6 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "aes67_cfg.h"
|
#include "aes67_cfg.h"
|
||||||
@@ -28,6 +29,35 @@ static volatile bool s_playing; // ring output running (after prefill)
|
|||||||
static volatile bool s_flush; // consumer drops buffered audio on the next read
|
static volatile bool s_flush; // consumer drops buffered audio on the next read
|
||||||
static volatile bool s_paused; // Spotify paused: silence, buffer kept, no underrun
|
static volatile bool s_paused; // Spotify paused: silence, buffer kept, no underrun
|
||||||
static audio_conv_t *s_spotify_conv;
|
static audio_conv_t *s_spotify_conv;
|
||||||
|
// Player volume, applied after the ring so a change is heard at once (not a ring length later).
|
||||||
|
static volatile float s_target_gain = 1.0f;
|
||||||
|
static float s_gain = 1.0f; // TX task only; ramps to s_target_gain
|
||||||
|
static volatile int s_volume_pct = 100;
|
||||||
|
|
||||||
|
// 0..100 % -> amplitude: cubic curve (about -18 dB at 50 %, 0 % = mute).
|
||||||
|
static void set_volume(int pct)
|
||||||
|
{
|
||||||
|
pct = pct < 0 ? 0 : pct > 100 ? 100 : pct;
|
||||||
|
float r = pct / 100.0f;
|
||||||
|
s_volume_pct = pct;
|
||||||
|
s_target_gain = r * r * r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the volume, ramping linearly from the previous gain over this buffer (no zipper noise).
|
||||||
|
static void apply_gain(int32_t *buf, size_t frames)
|
||||||
|
{
|
||||||
|
float target = s_target_gain;
|
||||||
|
if (s_gain == target && target == 1.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float step = (target - s_gain) / frames;
|
||||||
|
for (size_t i = 0; i < frames; i++) {
|
||||||
|
s_gain += step;
|
||||||
|
buf[i * 2] = (int32_t)(buf[i * 2] * s_gain);
|
||||||
|
buf[i * 2 + 1] = (int32_t)(buf[i * 2 + 1] * s_gain);
|
||||||
|
}
|
||||||
|
s_gain = target;
|
||||||
|
}
|
||||||
|
|
||||||
static player_src_t mode_of(const char *m)
|
static player_src_t mode_of(const char *m)
|
||||||
{
|
{
|
||||||
@@ -55,6 +85,7 @@ static size_t player_read(int32_t *buf, size_t frames)
|
|||||||
s_playing = true;
|
s_playing = true;
|
||||||
}
|
}
|
||||||
size_t got = audio_ring_read(buf, frames);
|
size_t got = audio_ring_read(buf, frames);
|
||||||
|
apply_gain(buf, got);
|
||||||
if (got < frames) {
|
if (got < frames) {
|
||||||
s_playing = false; // ran dry: underrun (counted by TX), prefill again
|
s_playing = false; // ran dry: underrun (counted by TX), prefill again
|
||||||
}
|
}
|
||||||
@@ -114,10 +145,15 @@ static void spotify_event(spotify_event_t ev, int value)
|
|||||||
s_flush = true; // skip/seek: don't play out the old buffer
|
s_flush = true; // skip/seek: don't play out the old buffer
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SPOTIFY_EV_VOLUME:
|
case SPOTIFY_EV_VOLUME: {
|
||||||
ESP_LOGI(TAG, "Spotify volume %d%% (applied in step 7.5b4)", value * 100 / 65535);
|
// Spotify sends 0..65535; the app's slider stays the master of the player volume.
|
||||||
|
int pct = (value * 100 + 32767) / 65535;
|
||||||
|
set_volume(pct);
|
||||||
|
float g = s_target_gain;
|
||||||
|
ESP_LOGI(TAG, "volume %d%% (%.1f dB)", pct, g > 0 ? 20.0f * log10f(g) : -INFINITY);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void player_status(cJSON *st)
|
static void player_status(cJSON *st)
|
||||||
|
|||||||
Reference in New Issue
Block a user