5b0d7c9ba4
- main/audio_out: one converter instance per source (own rate converter state): s16 any rate/channels -> 48 kHz stereo int32 -> ring. - player_write(src, ...) only writes for the active source, drops the rest; player_src_t is public in player.h. - HLS decoder uses audio_out (no behaviour change). - Verified: HLS still sample exact (441344 -> 480375 frames per 10.008 s segment), buffer ~4 s, 0 underruns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
2.8 KiB
C
91 lines
2.8 KiB
C
#include "audio_out.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "esp_ae_rate_cvt.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_log.h"
|
|
|
|
#define OUT_RATE 48000
|
|
#define CONV_FRAMES 4096 // per rate converter call
|
|
|
|
static const char *TAG = "audio_out";
|
|
|
|
struct audio_conv {
|
|
player_src_t src;
|
|
const char *name;
|
|
esp_ae_rate_cvt_handle_t cvt; // NULL: no conversion (source is 48 kHz)
|
|
uint32_t rate; // input rate the converter was opened for
|
|
int32_t *in32, *out32; // stereo int32 work buffers (PSRAM)
|
|
};
|
|
|
|
audio_conv_t *audio_conv_create(player_src_t src, const char *name)
|
|
{
|
|
audio_conv_t *c = calloc(1, sizeof(*c));
|
|
if (!c) {
|
|
return NULL;
|
|
}
|
|
c->src = src;
|
|
c->name = name;
|
|
c->in32 = heap_caps_malloc(CONV_FRAMES * 2 * sizeof(int32_t), MALLOC_CAP_SPIRAM);
|
|
c->out32 = heap_caps_malloc(CONV_FRAMES * 2 * 2 * sizeof(int32_t), MALLOC_CAP_SPIRAM);
|
|
if (!c->in32 || !c->out32) {
|
|
free(c->in32);
|
|
free(c->out32);
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static void set_rate(audio_conv_t *c, uint32_t rate)
|
|
{
|
|
if (c->cvt) {
|
|
esp_ae_rate_cvt_close(c->cvt);
|
|
c->cvt = NULL;
|
|
}
|
|
c->rate = rate;
|
|
if (rate == OUT_RATE) {
|
|
return;
|
|
}
|
|
esp_ae_rate_cvt_cfg_t cfg = {
|
|
.src_rate = rate, .dest_rate = OUT_RATE, .channel = 2, .bits_per_sample = 32,
|
|
.complexity = 3, .perf_type = ESP_AE_RATE_CVT_PERF_TYPE_SPEED,
|
|
};
|
|
if (esp_ae_rate_cvt_open(&cfg, &c->cvt) != ESP_AE_ERR_OK) {
|
|
ESP_LOGE(TAG, "%s: rate converter %lu -> %d Hz: open failed", c->name, (unsigned long)rate, OUT_RATE);
|
|
} else {
|
|
ESP_LOGI(TAG, "%s: rate converter %lu -> %d Hz", c->name, (unsigned long)rate, OUT_RATE);
|
|
}
|
|
}
|
|
|
|
size_t audio_conv_write(audio_conv_t *c, const int16_t *pcm, uint32_t frames, int ch, uint32_t rate)
|
|
{
|
|
if (rate != c->rate) {
|
|
set_rate(c, rate);
|
|
}
|
|
size_t produced = 0;
|
|
while (frames) {
|
|
uint32_t n = frames < CONV_FRAMES ? frames : CONV_FRAMES;
|
|
for (uint32_t i = 0; i < n; i++) { // to stereo int32 (full scale = INT32_MAX)
|
|
int32_t l = (int32_t)pcm[i * ch] << 16;
|
|
c->in32[i * 2] = l;
|
|
c->in32[i * 2 + 1] = ch > 1 ? (int32_t)pcm[i * ch + 1] << 16 : l;
|
|
}
|
|
const int32_t *out = c->in32;
|
|
uint32_t out_n = n;
|
|
if (c->cvt) {
|
|
out_n = CONV_FRAMES * 2;
|
|
if (esp_ae_rate_cvt_process(c->cvt, c->in32, n, c->out32, &out_n) != ESP_AE_ERR_OK) {
|
|
ESP_LOGW(TAG, "%s: rate conversion failed", c->name);
|
|
return produced;
|
|
}
|
|
out = c->out32;
|
|
}
|
|
produced += player_write(c->src, out, out_n);
|
|
pcm += n * ch;
|
|
frames -= n;
|
|
}
|
|
return produced;
|
|
}
|