feat(bt): audio_router mode SCO (pont PCM I2S)

- Ajoute TONE_SCO a l enum tone_mode_t
- Ring buffers FreeRTOS (play + cap, 4 KB chacun)
- sco_mic_task : capture I2S -> ring sortant (decimation CVSD 8k)
- tone_task : lit ring entrant, mono->stereo, underrun = silence
- 4 fonctions pub : sco_begin/end/feed_playback/take_capture
- CMakeLists.txt : + esp_ringbuf dans REQUIRES
- Header : + stdbool.h / stdint.h (types bool/uint8_t/uint32_t)
This commit is contained in:
clement
2026-06-19 13:14:57 +02:00
parent 578ef4ffb2
commit 1695539493
3 changed files with 129 additions and 2 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
idf_component_register(
SRCS "audio_router.c"
INCLUDE_DIRS "include"
REQUIRES esp_driver_i2s bsp tone_gen hal_i2s
REQUIRES esp_driver_i2s bsp tone_gen hal_i2s esp_ringbuf
)
+119 -1
View File
@@ -11,15 +11,24 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "driver/i2s_std.h"
#include "esp_log.h"
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#define TAG "audio_router"
typedef enum { TONE_NONE, TONE_DIAL, TONE_BUSY, TONE_RINGBACK } tone_mode_t;
typedef enum { TONE_NONE, TONE_DIAL, TONE_BUSY, TONE_RINGBACK, TONE_SCO } tone_mode_t;
#define SCO_RB_SIZE 4096 /* ~128 ms @ 16 kHz mono 16-bit */
static RingbufHandle_t s_sco_play_rb = NULL; /* PCM entrant -> écouteur (TX) */
static RingbufHandle_t s_sco_cap_rb = NULL; /* micro -> PCM sortant */
static volatile bool s_sco_active = false;
static volatile bool s_sco_msbc = true;
static TaskHandle_t s_sco_mic_task = NULL;
#define SR PLIP_SAMPLE_RATE /* 16000 — doit correspondre à hal_i2s */
#define FRAME 320 /* 20 ms @ 16 kHz */
@@ -47,6 +56,43 @@ static void tone_task(void *a)
continue;
}
/* --- Mode SCO : pont PCM entrant → I2S TX (mono→stéréo) --- */
if (m == TONE_SCO) {
/* Lire ~20 ms de PCM mono entrant depuis le ring de lecture */
size_t want = s_sco_msbc ? (FRAME) : (FRAME / 2); /* samples mono voulus */
size_t got_bytes = 0;
int16_t *in = (int16_t *)xRingbufferReceiveUpTo(s_sco_play_rb, &got_bytes, 0,
want * sizeof(int16_t));
int frames = 0;
if (in) {
int n = (int)(got_bytes / sizeof(int16_t));
for (int i = 0; i < n && frames < FRAME; i++) {
int16_t v = in[i];
buf[frames * 2] = v;
buf[frames * 2 + 1] = v;
frames++;
if (!s_sco_msbc && frames < FRAME) { /* CVSD 8k -> 16k : doubler */
buf[frames * 2] = v;
buf[frames * 2 + 1] = v;
frames++;
}
}
vRingbufferReturnItem(s_sco_play_rb, in);
}
/* Underrun : compléter avec du silence */
while (frames < FRAME) {
buf[frames * 2] = 0;
buf[frames * 2 + 1] = 0;
frames++;
}
size_t written = 0;
s_tone_idle = false;
i2s_channel_write(hal_i2s_spk_handle(), buf, sizeof(buf), &written,
pdMS_TO_TICKS(50));
t += 20;
continue;
}
bool on = true;
if (m == TONE_BUSY) {
on = (t % 1000) < 500;
@@ -116,3 +162,75 @@ void audio_router_tones_stop(void)
ESP_LOGW(TAG, "tones_stop: timeout waiting for tone task idle");
}
}
/* ------------------------------------------------------------------ */
/* SCO bridge — Phase 4 HFP */
/* ------------------------------------------------------------------ */
static void sco_mic_task(void *a)
{
(void)a;
int16_t mono[FRAME];
while (s_sco_active) {
int64_t e = 0;
int n = hal_i2s_capture_read_frame(mono, FRAME, &e);
if (n <= 0) { vTaskDelay(pdMS_TO_TICKS(5)); continue; }
if (s_sco_msbc) {
/* 16 kHz direct : envoyer tel quel */
xRingbufferSend(s_sco_cap_rb, mono, (size_t)(n * (int)sizeof(int16_t)), 0);
} else {
/* 16k -> 8k : décimer 1 sur 2 */
int16_t dec[FRAME / 2];
int m = 0;
for (int i = 0; i < n; i += 2) dec[m++] = mono[i];
xRingbufferSend(s_sco_cap_rb, dec, (size_t)(m * (int)sizeof(int16_t)), 0);
}
}
s_sco_mic_task = NULL;
vTaskDelete(NULL);
}
void audio_router_sco_begin(bool msbc)
{
s_sco_msbc = msbc;
if (!s_sco_play_rb) s_sco_play_rb = xRingbufferCreate(SCO_RB_SIZE, RINGBUF_TYPE_BYTEBUF);
if (!s_sco_cap_rb) s_sco_cap_rb = xRingbufferCreate(SCO_RB_SIZE, RINGBUF_TYPE_BYTEBUF);
hal_i2s_capture_begin();
hal_audio_pa_set(true);
audio_router_init(); /* s'assure que tone_task existe */
s_sco_active = true;
s_mode = TONE_SCO;
if (!s_sco_mic_task) {
xTaskCreatePinnedToCore(sco_mic_task, "sco_mic", 4096, NULL, 6, &s_sco_mic_task, 1);
}
ESP_LOGI(TAG, "SCO bridge ON (%s)", msbc ? "mSBC 16k" : "CVSD 8k");
}
void audio_router_sco_end(void)
{
s_sco_active = false;
s_mode = TONE_NONE;
hal_audio_pa_set(false);
hal_i2s_capture_end();
ESP_LOGI(TAG, "SCO bridge OFF");
}
void audio_router_sco_feed_playback(const uint8_t *pcm, uint32_t len)
{
if (s_sco_play_rb && s_sco_active) {
xRingbufferSend(s_sco_play_rb, pcm, len, 0);
}
}
uint32_t audio_router_sco_take_capture(uint8_t *pcm, uint32_t len)
{
if (!s_sco_cap_rb) return 0;
size_t got = 0;
uint8_t *d = (uint8_t *)xRingbufferReceiveUpTo(s_sco_cap_rb, &got, 0, len);
if (d) {
memcpy(pcm, d, got);
vRingbufferReturnItem(s_sco_cap_rb, d);
return (uint32_t)got;
}
return 0;
}
@@ -1,5 +1,8 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -19,6 +22,12 @@ void audio_router_ringback_start(void);
/* Stop tones; waits (<=200 ms) until the tone task leaves i2s_channel_write. */
void audio_router_tones_stop(void);
/* SCO bridge (Phase 4 HFP). audio_router stays the single I2S TX writer. */
void audio_router_sco_begin(bool msbc);
void audio_router_sco_end(void);
void audio_router_sco_feed_playback(const uint8_t *pcm, uint32_t len);
uint32_t audio_router_sco_take_capture(uint8_t *pcm, uint32_t len);
#ifdef __cplusplus
}
#endif