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:
+15
-17
@@ -19,6 +19,7 @@
|
||||
#define LIVE_BACK 3 // start this many segments behind the live edge
|
||||
#define CHUNK 4096
|
||||
#define MAX_REDIRECTS 5
|
||||
#define SEG_MAX (4 * 1024 * 1024) // largest segment we accept (PSRAM)
|
||||
|
||||
static const char *TAG = "hls";
|
||||
|
||||
@@ -34,6 +35,7 @@ static char *s_text; // playlist buffer (PSRAM)
|
||||
static media_pl_t *s_pl; // parsed media playlist (PSRAM)
|
||||
static char s_url[URL_MAX]; // configured URL
|
||||
static char s_media_url[URL_MAX];
|
||||
static uint8_t *s_seg; // current segment (PSRAM)
|
||||
|
||||
// hls_url when source.mode needs HLS, else "".
|
||||
static void wanted_url(char *out, size_t n)
|
||||
@@ -209,30 +211,25 @@ static bool fetch_segment(long long seq, const char *url)
|
||||
if (!c) {
|
||||
return false;
|
||||
}
|
||||
static uint8_t buf[CHUNK];
|
||||
// Download completely first, then decode: the connection is not held open while the
|
||||
// decoder waits for space in the ring (a stalled TCP stream for ~10 s may get cut by the CDN).
|
||||
size_t total = 0;
|
||||
int r;
|
||||
bool ok = true, first = true;
|
||||
while ((r = esp_http_client_read(c, (char *)buf, sizeof(buf))) > 0) {
|
||||
if (s_sink && !s_sink(buf, r, first)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
int r = 0;
|
||||
bool ok = true;
|
||||
while (total < SEG_MAX && (r = esp_http_client_read(c, (char *)s_seg + total, SEG_MAX - total)) > 0) {
|
||||
total += r;
|
||||
if (!still_wanted()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r < 0) {
|
||||
if (r < 0 || total == SEG_MAX) {
|
||||
ok = false;
|
||||
}
|
||||
esp_http_client_close(c);
|
||||
esp_http_client_cleanup(c);
|
||||
int64_t us = esp_timer_get_time() - t0;
|
||||
ESP_LOGI(TAG, "segment %lld: %u bytes in %.2f s (%.1f Mbit/s)%s", seq, (unsigned)total, us / 1e6,
|
||||
us ? total * 8.0 / us : 0.0, ok ? "" : ", aborted");
|
||||
ESP_LOGD(TAG, "segment %lld: %u bytes in %.2f s (%.1f Mbit/s)%s", seq, (unsigned)total, us / 1e6,
|
||||
us ? total * 8.0 / us : 0.0, ok ? "" : ", failed");
|
||||
if (ok && s_sink) {
|
||||
ok = s_sink(s_seg, total, true); // blocks at playback speed while the ring is full
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -300,7 +297,8 @@ esp_err_t hls_start(hls_sink_t sink)
|
||||
s_sink = sink;
|
||||
s_text = heap_caps_malloc(PLAYLIST_MAX, MALLOC_CAP_SPIRAM);
|
||||
s_pl = heap_caps_malloc(sizeof(media_pl_t), MALLOC_CAP_SPIRAM);
|
||||
if (!s_text || !s_pl) {
|
||||
s_seg = heap_caps_malloc(SEG_MAX, MALLOC_CAP_SPIRAM);
|
||||
if (!s_text || !s_pl || !s_seg) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return xTaskCreate(hls_task, "hls", 10240, NULL, 4, NULL) == pdPASS ? ESP_OK : ESP_ERR_NO_MEM;
|
||||
|
||||
Reference in New Issue
Block a user