vibe code the initial impl
This commit is contained in:
+175
@@ -0,0 +1,175 @@
|
||||
// I2S capture: continuous 16 kHz mono read, builds 50%-overlapping frames
|
||||
// (previous hop + current hop) and hands them to the processing task through a
|
||||
// fixed pool of frame slots. No allocation after init.
|
||||
#include <string.h>
|
||||
#include "driver/i2s_std.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "audio_in.h"
|
||||
#include "board_config.h"
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
#define NUM_SLOTS 8
|
||||
#define SETTLE_HOPS 8 // discard the first hops: mic start-up transient
|
||||
#define AUDIO_TASK_CORE 1
|
||||
#define AUDIO_TASK_PRIO 10
|
||||
#define AUDIO_TASK_STACK 4096
|
||||
|
||||
static int16_t s_slots[NUM_SLOTS][FRAME_LEN];
|
||||
static QueueHandle_t s_free_q;
|
||||
static QueueHandle_t s_ready_q;
|
||||
static i2s_chan_handle_t s_rx;
|
||||
static int32_t s_raw[HOP_LEN];
|
||||
static int16_t s_hop_a[HOP_LEN], s_hop_b[HOP_LEN];
|
||||
static volatile uint32_t s_captured, s_dropped;
|
||||
static uint32_t s_seq;
|
||||
|
||||
esp_err_t audio_in_init(void)
|
||||
{
|
||||
s_free_q = xQueueCreate(NUM_SLOTS, sizeof(uint8_t));
|
||||
s_ready_q = xQueueCreate(NUM_SLOTS, sizeof(uint8_t));
|
||||
if (!s_free_q || !s_ready_q) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
for (uint8_t i = 0; i < NUM_SLOTS; i++) {
|
||||
xQueueSend(s_free_q, &i, 0);
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = 6;
|
||||
chan_cfg.dma_frame_num = 256; // 6 x 256 x 4 B -> ~96 ms of DMA buffering
|
||||
esp_err_t err = i2s_new_channel(&chan_cfg, NULL, &s_rx);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_new_channel: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// INMP441: Philips format, 24-bit sample left-justified in a 32-bit slot.
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(FS_HZ),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = I2S_BCLK_GPIO,
|
||||
.ws = I2S_WS_GPIO,
|
||||
.dout = I2S_GPIO_UNUSED,
|
||||
.din = I2S_DATA_GPIO,
|
||||
.invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false },
|
||||
},
|
||||
};
|
||||
#if CONFIG_BUZZ_I2S_SLOT_RIGHT
|
||||
std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;
|
||||
#else
|
||||
std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
|
||||
#endif
|
||||
err = i2s_channel_init_std_mode(s_rx, &std_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_channel_init_std_mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = i2s_channel_enable(s_rx);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_channel_enable: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
ESP_LOGI(TAG, "I2S RX up: %d Hz, BCLK=%d WS=%d DIN=%d, shift=%d",
|
||||
FS_HZ, CONFIG_BUZZ_I2S_BCLK_GPIO, CONFIG_BUZZ_I2S_WS_GPIO,
|
||||
CONFIG_BUZZ_I2S_DATA_GPIO, CONFIG_BUZZ_AUDIO_SHIFT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 32-bit slot -> int16. The 24-bit sample sits in bits [31:8]; shifting right by 16
|
||||
// would keep its top 16 bits (unity gain). CONFIG_BUZZ_AUDIO_SHIFT < 16 adds gain.
|
||||
// Arithmetic shift keeps the sign; clamp protects against overflow with gain.
|
||||
static inline int16_t to_i16(int32_t raw)
|
||||
{
|
||||
int32_t v = raw >> CONFIG_BUZZ_AUDIO_SHIFT;
|
||||
if (v > INT16_MAX) v = INT16_MAX;
|
||||
if (v < INT16_MIN) v = INT16_MIN;
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
static void audio_task(void *arg)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_task_wdt_add(NULL));
|
||||
int16_t *prev = s_hop_a, *cur = s_hop_b;
|
||||
uint32_t settle = SETTLE_HOPS;
|
||||
bool have_prev = false;
|
||||
uint32_t last_warn_ms = 0;
|
||||
|
||||
for (;;) {
|
||||
esp_task_wdt_reset(); // if I2S stops delivering data the WDT reboots us
|
||||
|
||||
size_t need = sizeof(s_raw), total = 0;
|
||||
while (total < need) {
|
||||
size_t got = 0;
|
||||
esp_err_t err = i2s_channel_read(s_rx, (uint8_t *)s_raw + total, need - total, &got, 200);
|
||||
total += got;
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "I2S read timeout (mic wired?)");
|
||||
} else if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_channel_read: %s", esp_err_to_name(err));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < HOP_LEN; i++) {
|
||||
cur[i] = to_i16(s_raw[i]);
|
||||
}
|
||||
|
||||
if (settle > 0) {
|
||||
settle--;
|
||||
} else if (have_prev) {
|
||||
uint8_t idx;
|
||||
if (xQueueReceive(s_free_q, &idx, 0) == pdTRUE) {
|
||||
memcpy(&s_slots[idx][0], prev, HOP_LEN * sizeof(int16_t));
|
||||
memcpy(&s_slots[idx][HOP_LEN], cur, HOP_LEN * sizeof(int16_t));
|
||||
if (xQueueSend(s_ready_q, &idx, 0) != pdTRUE) {
|
||||
xQueueSend(s_free_q, &idx, 0);
|
||||
s_dropped++;
|
||||
} else {
|
||||
s_captured++;
|
||||
}
|
||||
} else {
|
||||
s_dropped++;
|
||||
uint32_t now = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
|
||||
if (now - last_warn_ms > 2000) {
|
||||
last_warn_ms = now;
|
||||
ESP_LOGW(TAG, "processing behind: dropped %lu frames so far", (unsigned long)s_dropped);
|
||||
}
|
||||
}
|
||||
}
|
||||
have_prev = true;
|
||||
int16_t *t = prev; prev = cur; cur = t;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t audio_in_start(void)
|
||||
{
|
||||
BaseType_t ok = xTaskCreatePinnedToCore(audio_task, "audio", AUDIO_TASK_STACK, NULL,
|
||||
AUDIO_TASK_PRIO, NULL, AUDIO_TASK_CORE);
|
||||
return ok == pdPASS ? ESP_OK : ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
bool audio_in_get_frame(audio_frame_t *f, TickType_t wait)
|
||||
{
|
||||
uint8_t idx;
|
||||
if (xQueueReceive(s_ready_q, &idx, wait) != pdTRUE) {
|
||||
return false;
|
||||
}
|
||||
f->idx = idx;
|
||||
f->pcm = s_slots[idx];
|
||||
f->seq = s_seq++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void audio_in_release_frame(uint8_t idx)
|
||||
{
|
||||
xQueueSend(s_free_q, &idx, 0);
|
||||
}
|
||||
|
||||
uint32_t audio_in_frames_captured(void) { return s_captured; }
|
||||
uint32_t audio_in_frames_dropped(void) { return s_dropped; }
|
||||
Reference in New Issue
Block a user