diff --git a/components/audio_router/CMakeLists.txt b/components/audio_router/CMakeLists.txt index 04b91dd..08a7908 100644 --- a/components/audio_router/CMakeLists.txt +++ b/components/audio_router/CMakeLists.txt @@ -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 ) diff --git a/components/audio_router/audio_router.c b/components/audio_router/audio_router.c index 3f6c605..86689d0 100644 --- a/components/audio_router/audio_router.c +++ b/components/audio_router/audio_router.c @@ -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 #include +#include #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; +} diff --git a/components/audio_router/include/audio_router.h b/components/audio_router/include/audio_router.h index 32047b5..4e89eaa 100644 --- a/components/audio_router/include/audio_router.h +++ b/components/audio_router/include/audio_router.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #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