Files
aes67-ESP32-P4/main/player.c
T
bsncubed 8a6cb53e3b Step 7.3: decode HLS TS segments to PCM (own TS demux + AAC decoder)
- main/decoder: MPEG-TS demux (packets reassembled across HTTP chunks,
  PAT -> PMT -> first ADTS-AAC PID, PES headers stripped) feeding the
  esp_audio_codec simple AAC decoder (ADTS, AAC-Plus enabled for HE-AAC
  v1/v2 variants). 7.3 only counts and logs PCM per segment.
- esp_audio_codec pinned to ~2.5.0: 2.6+ needs P4 rev >= 3 (this board
  is rev 1.3). Noted in CLAUDE.md, also for esp_audio_effects < 1.4.
- The library's combined TS decoder lost ~8% of the frames (segments
  decoded to 7.9-9.6 s, "decode error -1"); with the own demux every
  segment is sample exact: 441344 / 440320 frames = 10.008 / 9.985 s,
  matching EXTINF 10.0078 / 9.9846 (431 / 430 AAC frames), no errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:48:12 +10:00

127 lines
4.0 KiB
C

#include "player.h"
#include <math.h>
#include <string.h>
#include "aes67_cfg.h"
#include "aes67_tx.h"
#include "aes67_web.h"
#include "audio_ring.h"
#include "decoder.h"
#include "hls.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define RATE 48000
#define CHANNELS 2
#define RING_FRAMES (4 * RATE) // 4 s in PSRAM
#define PREFILL_FRAMES (RATE) // 1 s before (re)starting output
static const char *TAG = "player";
typedef enum { SRC_TONE, SRC_OFF, SRC_HLS, SRC_SPOTIFY } src_t;
static const char *const SRC_NAME[] = { "tone", "off", "hls", "spotify" };
static volatile src_t s_src = SRC_OFF;
static volatile bool s_playing; // ring output running (after prefill)
static volatile bool s_flush; // consumer drops buffered audio on the next read
static src_t mode_of(const char *m)
{
return !strcmp(m, "tone") ? SRC_TONE : !strcmp(m, "hls") ? SRC_HLS :
!strcmp(m, "spotify") ? SRC_SPOTIFY : !strcmp(m, "auto") ? SRC_HLS /* failover: step 7 */ : SRC_OFF;
}
// AES67 TX pull callback (TX task, must not block). Silence while buffering or off: not an underrun.
static size_t player_read(int32_t *buf, size_t frames)
{
if (s_flush) {
audio_ring_flush();
s_flush = false;
s_playing = false;
}
if (s_src == SRC_OFF || s_src == SRC_SPOTIFY) { // Spotify: step 7 (cspot)
memset(buf, 0, frames * CHANNELS * sizeof(int32_t));
return frames;
}
if (!s_playing) {
if (audio_ring_level() < PREFILL_FRAMES) {
memset(buf, 0, frames * CHANNELS * sizeof(int32_t));
return frames;
}
s_playing = true;
}
size_t got = audio_ring_read(buf, frames);
if (got < frames) {
s_playing = false; // ran dry: underrun (counted by TX), prefill again
}
return got;
}
void player_apply(const cJSON *source)
{
src_t src = mode_of(cJSON_GetObjectItemCaseSensitive(source, "mode")->valuestring);
if (src == s_src) {
return;
}
s_src = src;
s_flush = true;
// The test tone is TX's own (phase-locked to PTP); everything else goes through player_read.
aes67_tx_set_source(src == SRC_TONE ? NULL : player_read);
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)
{
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;
}
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;
}
audio_ring_write(chunk, 480);
}
}
static void player_status(cJSON *st)
{
const char *state = s_src == SRC_TONE ? "playing" : s_src == SRC_OFF ? "idle" :
s_src == SRC_SPOTIFY ? "not implemented" : s_playing ? "playing" : "buffering";
cJSON_AddStringToObject(st, "active_source", SRC_NAME[s_src]);
cJSON_AddStringToObject(st, "source_state", state);
cJSON_AddStringToObject(st, "spotify_state", "not implemented");
cJSON_AddNumberToObject(st, "buffer_ms", (double)(audio_ring_level() * 1000 / RATE));
}
esp_err_t player_init(void)
{
esp_err_t err = audio_ring_init(RING_FRAMES, CHANNELS);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ring buffer: %s", esp_err_to_name(err));
return err;
}
cJSON *src = cfg_get("source");
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
if (err != ESP_OK) {
return err;
}
return status_register(player_status);
}