Step 7.4: HLS audio on AES67 (44.1 -> 48 kHz into the ring)

- decoder: s16 PCM -> stereo int32 -> esp_ae_rate_cvt 44.1 -> 48 kHz
  (32-bit, complexity 3; bypassed at 48 kHz; mono duplicated) -> ring.
  esp_audio_effects pinned to ~1.3.0 (1.4+ needs P4 rev >= 3).
- hls: each segment is downloaded completely into PSRAM (max 4 MB), then
  decoded; the connection is not held open while the decoder waits for
  ring space at playback speed.
- player: player_write() blocks while the ring is full and gives up when
  the source changes; the temporary 440 Hz producer is removed.
- Verified with Triple J Hottest: 441344 -> 480375 frames (10.008 s) and
  440320 -> 479260 (9.985 s) per segment; RTP 15000 packets, 0 gaps,
  peak -10 dBFS / RMS -22 dBFS; ring ~4.0 s, 0 underruns over ~50 s;
  heap 422 KB, PSRAM 27.5 MB free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:52:19 +10:00
parent 8a6cb53e3b
commit 3ebb27e4bb
6 changed files with 122 additions and 41 deletions
+13 -17
View File
@@ -1,6 +1,5 @@
#include "player.h"
#include <math.h>
#include <string.h>
#include "aes67_cfg.h"
@@ -72,24 +71,22 @@ void player_apply(const cJSON *source)
ESP_LOGI(TAG, "source: %s", SRC_NAME[src]);
}
/* ----- Step 7.1: temporary test producer (440 Hz into the ring), replaced by the HLS player ----- */
static void test_producer(void *arg)
// Source side (HLS task): write converted 48 kHz frames, waiting for space at playback speed.
// Gives up when the source is no longer HLS.
size_t player_write(const int32_t *frames, size_t n)
{
static int32_t chunk[480 * CHANNELS];
uint32_t phase = 0;
const double amp = pow(10.0, -18.0 / 20.0) * 2147483647.0;
while (1) {
if (s_src != SRC_HLS || audio_ring_space() < 480) {
vTaskDelay(pdMS_TO_TICKS(10));
continue;
size_t done = 0;
while (done < n) {
if (s_src != SRC_HLS) {
return done;
}
for (int i = 0; i < 480; i++, phase++) {
int32_t v = (int32_t)(amp * sin(2.0 * M_PI * 440.0 * (phase % RATE) / RATE));
chunk[i * 2] = chunk[i * 2 + 1] = v;
size_t w = audio_ring_write(frames + done * CHANNELS, n - done);
if (!w) {
vTaskDelay(pdMS_TO_TICKS(20));
}
audio_ring_write(chunk, 480);
done += w;
}
return done;
}
static void player_status(cJSON *st)
@@ -113,12 +110,11 @@ esp_err_t player_init(void)
s_src = (src_t)-1;
player_apply(src);
cJSON_Delete(src);
xTaskCreate(test_producer, "test_prod", 3072, NULL, 5, NULL);
err = decoder_init();
if (err != ESP_OK) {
return err;
}
err = hls_start(decoder_feed); // 7.3: decode and log; resampling + ring in 7.4
err = hls_start(decoder_feed);
if (err != ESP_OK) {
return err;
}