From 2eb97bc4de4aaa074458599089c73bed36cdc802 Mon Sep 17 00:00:00 2001 From: clement Date: Fri, 19 Jun 2026 17:50:41 +0200 Subject: [PATCH] feat(audio): gains runtime + filtre voix + CLI --- components/audio_router/audio_router.c | 67 +++++++++++++++++-- .../audio_router/include/audio_router.h | 7 ++ components/cli/CMakeLists.txt | 5 ++ components/cli/cli.c | 61 +++++++++++++++++ components/cli/include/cli.h | 17 +++++ main/CMakeLists.txt | 2 +- main/app_main.c | 2 + 7 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 components/cli/CMakeLists.txt create mode 100644 components/cli/cli.c create mode 100644 components/cli/include/cli.h diff --git a/components/audio_router/audio_router.c b/components/audio_router/audio_router.c index f9ba6a3..3e7097c 100644 --- a/components/audio_router/audio_router.c +++ b/components/audio_router/audio_router.c @@ -18,6 +18,11 @@ #include #include #include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif #define TAG "audio_router" @@ -39,9 +44,11 @@ static volatile tone_mode_t s_mode = TONE_NONE; static volatile bool s_tone_idle = true; static TaskHandle_t s_task = NULL; -/* --- Gain numerique SCO (avec saturation) --- */ -#define SCO_PLAY_GAIN 4 /* downlink : PCM mobile -> ecouteur */ -#define SCO_MIC_GAIN 5 /* uplink : micro -> mobile */ +/* --- Gain numerique SCO (reglable a la volee, avec saturation) --- */ +static volatile int s_play_gain = 4; /* downlink : PCM mobile -> ecouteur */ +static volatile int s_mic_gain = 8; /* uplink : micro -> mobile (filtre) */ +static volatile bool s_mic_filter = true; /* passe-bande voix sur le micro */ + static inline int16_t sat16(int32_t v) { if (v > 32767) return 32767; @@ -49,6 +56,47 @@ static inline int16_t sat16(int32_t v) return (int16_t)v; } +/* --- Biquad (RBJ) : passe-haut + passe-bas en serie sur le micro --- */ +typedef struct { float b0, b1, b2, a1, a2; float z1, z2; } biquad_t; +static biquad_t s_hp, s_lp; /* HP 300 Hz, LP 3400 Hz @ 16 kHz */ + +static void biquad_set(biquad_t *f, float fc, float fs, float q, bool highpass) +{ + float w0 = 2.0f * (float)M_PI * fc / fs; + float c = cosf(w0), s = sinf(w0); + float alpha = s / (2.0f * q); + float a0; + if (highpass) { + f->b0 = (1.0f + c) / 2.0f; f->b1 = -(1.0f + c); f->b2 = (1.0f + c) / 2.0f; + } else { + f->b0 = (1.0f - c) / 2.0f; f->b1 = (1.0f - c); f->b2 = (1.0f - c) / 2.0f; + } + a0 = 1.0f + alpha; + f->b0 /= a0; f->b1 /= a0; f->b2 /= a0; + f->a1 = (-2.0f * c) / a0; f->a2 = (1.0f - alpha) / a0; + f->z1 = f->z2 = 0.0f; +} + +static inline float biquad_run(biquad_t *f, float x) /* direct form II transposed */ +{ + float y = f->b0 * x + f->z1; + f->z1 = f->b1 * x - f->a1 * y + f->z2; + f->z2 = f->b2 * x - f->a2 * y; + return y; +} + +static void mic_dsp_init(void) +{ + biquad_set(&s_hp, 300.0f, 16000.0f, 0.707f, true); + biquad_set(&s_lp, 3400.0f, 16000.0f, 0.707f, false); +} + +void audio_router_set_play_gain(int g) { if (g >= 0 && g <= 32) s_play_gain = g; } +void audio_router_set_mic_gain(int g) { if (g >= 0 && g <= 32) s_mic_gain = g; } +void audio_router_set_mic_filter(bool on) { s_mic_filter = on; } +int audio_router_get_play_gain(void) { return s_play_gain; } +int audio_router_get_mic_gain(void) { return s_mic_gain; } + /* --- Compteurs de diagnostic SCO (temporaires) --- */ static volatile uint32_t s_dbg_in_bytes = 0; /* PCM recu du BT (feed_playback) */ static volatile uint32_t s_dbg_cap = 0; /* frames micro capturees */ @@ -96,7 +144,7 @@ static void tone_task(void *a) /* Frame complete : mono -> stereo (CVSD double pour 8k->16k) */ int frames = 0; for (int i = 0; i < target && frames < FRAME; i++) { - int16_t v = sat16((int32_t)sco_acc[i] * SCO_PLAY_GAIN); + int16_t v = sat16((int32_t)sco_acc[i] * s_play_gain); buf[frames * 2] = v; buf[frames * 2 + 1] = v; frames++; if (!s_sco_msbc && frames < FRAME) { buf[frames * 2] = v; buf[frames * 2 + 1] = v; frames++; @@ -204,7 +252,15 @@ static void sco_mic_task(void *a) int n = hal_i2s_capture_read_frame(mono, FRAME, &e); if (n <= 0) { vTaskDelay(pdMS_TO_TICKS(5)); continue; } s_dbg_cap++; - for (int i = 0; i < n; i++) mono[i] = sat16((int32_t)mono[i] * SCO_MIC_GAIN); + /* Filtre passe-bande voix (HP 300 Hz + LP 3400 Hz) puis gain. */ + for (int i = 0; i < n; i++) { + float x = (float)mono[i]; + if (s_mic_filter) { + x = biquad_run(&s_hp, x); + x = biquad_run(&s_lp, x); + } + mono[i] = sat16((int32_t)(x * (float)s_mic_gain)); + } if (s_sco_msbc) { /* 16 kHz direct : envoyer tel quel */ xRingbufferSend(s_sco_cap_rb, mono, (size_t)(n * (int)sizeof(int16_t)), 0); @@ -223,6 +279,7 @@ static void sco_mic_task(void *a) void audio_router_sco_begin(bool msbc) { s_sco_msbc = msbc; + mic_dsp_init(); /* (re)calcule les coeffs du filtre + reset l'etat */ 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(); diff --git a/components/audio_router/include/audio_router.h b/components/audio_router/include/audio_router.h index 4e89eaa..2b60720 100644 --- a/components/audio_router/include/audio_router.h +++ b/components/audio_router/include/audio_router.h @@ -10,6 +10,13 @@ extern "C" { /* Create the tone task (idempotent). Call once after hal_i2s_init(). */ void audio_router_init(void); +/* Gains SCO reglables a la volee (0..32) + filtre passe-bande voix sur le micro. */ +void audio_router_set_play_gain(int g); /* downlink : ecouteur */ +void audio_router_set_mic_gain(int g); /* uplink : micro -> mobile */ +void audio_router_set_mic_filter(bool on); +int audio_router_get_play_gain(void); +int audio_router_get_mic_gain(void); + /* Start a continuous 440 Hz dial tone (enables PA). */ void audio_router_dialtone_start(void); diff --git a/components/cli/CMakeLists.txt b/components/cli/CMakeLists.txt new file mode 100644 index 0000000..161badf --- /dev/null +++ b/components/cli/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "cli.c" + INCLUDE_DIRS "include" + REQUIRES audio_router +) diff --git a/components/cli/cli.c b/components/cli/cli.c new file mode 100644 index 0000000..96ebc79 --- /dev/null +++ b/components/cli/cli.c @@ -0,0 +1,61 @@ +/* + * cli.c — petit CLI serie (console UART0) pour regler les gains audio et le + * filtre micro a la volee, sans recompiler. Coexiste avec les logs ESP_LOG. + */ +#include "cli.h" +#include "audio_router.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include +#include +#include + +#define TAG "cli" + +static void handle(char *l) +{ + if (strncmp(l, "gmic", 4) == 0) { + audio_router_set_mic_gain(atoi(l + 4)); + ESP_LOGI(TAG, "gain micro = %d", audio_router_get_mic_gain()); + } else if (strncmp(l, "gspk", 4) == 0) { + audio_router_set_play_gain(atoi(l + 4)); + ESP_LOGI(TAG, "gain ecouteur = %d", audio_router_get_play_gain()); + } else if (strncmp(l, "filt", 4) == 0) { + int on = atoi(l + 4); + audio_router_set_mic_filter(on != 0); + ESP_LOGI(TAG, "filtre micro = %s", on ? "ON" : "OFF"); + } else if (strncmp(l, "status", 6) == 0) { + ESP_LOGI(TAG, "gspk=%d gmic=%d", + audio_router_get_play_gain(), audio_router_get_mic_gain()); + } else { + ESP_LOGI(TAG, "cmds: gmic N | gspk N | filt 0/1 | status"); + } +} + +static void cli_task(void *a) +{ + (void)a; + char line[48]; + int pos = 0; + ESP_LOGI(TAG, "CLI prete — gmic N / gspk N / filt 0/1 / status"); + for (;;) { + int c = getchar(); + if (c == EOF) { + vTaskDelay(pdMS_TO_TICKS(30)); + continue; + } + if (c == '\n' || c == '\r') { + if (pos > 0) { line[pos] = '\0'; handle(line); pos = 0; } + } else if (pos < (int)sizeof(line) - 1) { + line[pos++] = (char)c; + } + } +} + +void cli_start(void) +{ + xTaskCreate(cli_task, "cli", 3072, NULL, 3, NULL); +} diff --git a/components/cli/include/cli.h b/components/cli/include/cli.h new file mode 100644 index 0000000..3e24652 --- /dev/null +++ b/components/cli/include/cli.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Demarre un CLI serie (console UART) pour regler les gains/filtre a la volee : + * gmic <0..32> gain micro (uplink) + * gspk <0..32> gain ecouteur (downlink) + * filt <0|1> filtre passe-bande voix sur le micro + * status affiche les valeurs courantes + */ +void cli_start(void); + +#ifdef __cplusplus +} +#endif diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index e8a0fa4..88791ac 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "app_main.c" INCLUDE_DIRS "." - REQUIRES nvs_flash config_store hal_es8388 hal_i2s audio_router slic_ks0835 dtmf dialer hook_monitor call_manager bt_hfp + REQUIRES nvs_flash config_store hal_es8388 hal_i2s audio_router slic_ks0835 dtmf dialer hook_monitor call_manager bt_hfp cli ) diff --git a/main/app_main.c b/main/app_main.c index 5a5d10d..8510ef8 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -8,6 +8,7 @@ #include "dialer.h" #include "call_manager.h" #include "bt_hfp.h" +#include "cli.h" static const char *TAG = "rtc_phone"; @@ -28,5 +29,6 @@ void app_main(void) /* Pile BT HFP-HF — NVS déjà initialisé par config_store_init(). * call_manager_bt_event remplace le handler de log temporaire (Task 2). */ ESP_ERROR_CHECK(bt_hfp_init(call_manager_bt_event)); + cli_start(); ESP_LOGI(TAG, "call_manager + bt_hfp prets — appairer sur 'RTC_BL_PHONE'"); }