多模态

This commit is contained in:
admin
2026-02-24 21:36:14 +08:00
parent e1e0ac8929
commit 68840e6ea4
23 changed files with 3368 additions and 44 deletions

View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
typedef struct {
uint32_t sample_rate;
uint8_t channels;
uint8_t bits_per_sample;
int output_volume;
float input_gain_db;
} voice_audio_open_config_t;
esp_err_t voice_audio_open(const voice_audio_open_config_t *cfg, char *err, size_t err_len);
void voice_audio_close(void);
bool voice_audio_is_open(void);
esp_err_t voice_audio_read_pcm(int16_t *pcm,
size_t samples,
uint32_t timeout_ms,
char *err,
size_t err_len);
esp_err_t voice_audio_write_pcm(const int16_t *pcm,
size_t samples,
uint32_t timeout_ms,
char *err,
size_t err_len);

View File

@@ -21,6 +21,8 @@
#define PRINTER_NOTIFY_UUID16 0xFFF1
#define PRINTER_WRITE_UUID16 0xFFF2
#define CCCD_UUID16 0x2902
#define PRINTER_NAME_PRIMARY "TQPrinter"
#define PRINTER_NAME_LEGACY "lyfPrinter"
#define EVT_CONNECTED BIT0
#define EVT_READY BIT1
@@ -35,7 +37,7 @@ static ble_frame_rx_cb_t s_rx_cb;
static uint8_t s_addr_type;
static ble_addr_t s_target_addr;
static char s_target_name[32] = "lyfPrinter";
static char s_target_name[32] = PRINTER_NAME_PRIMARY;
static bool s_match_any_compatible;
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
@@ -117,6 +119,21 @@ static bool normalized_name_match(const char *adv_name, const char *target_name)
return (strstr(adv, target) != NULL) || (strstr(target, adv) != NULL);
}
static bool is_known_printer_name(const char *name) {
return normalized_name_match(name, PRINTER_NAME_PRIMARY) ||
normalized_name_match(name, PRINTER_NAME_LEGACY);
}
static bool target_name_match_with_alias(const char *adv_name, const char *target_name) {
if (normalized_name_match(adv_name, target_name)) {
return true;
}
if (!is_known_printer_name(target_name)) {
return false;
}
return is_known_printer_name(adv_name);
}
static void reset_discovery_state(void) {
s_service_start = 0;
s_service_end = 0;
@@ -347,7 +364,7 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) {
if (s_match_any_compatible) {
bool uuid_match = adv_has_uuid16(&fields, PRINTER_SERVICE_UUID16);
bool name_match = has_name && normalized_name_match(name, "lyfPrinter");
bool name_match = has_name && is_known_printer_name(name);
if (!uuid_match && !name_match) {
return 0;
}
@@ -357,7 +374,7 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) {
event->disc.rssi,
uuid_match ? 1 : 0);
} else {
if (!has_name || !normalized_name_match(name, s_target_name)) {
if (!has_name || !target_name_match_with_alias(name, s_target_name)) {
return 0;
}
ESP_LOGI(TAG, "Found target device '%s' RSSI=%d", name, event->disc.rssi);

View File

@@ -0,0 +1,362 @@
#include "voice_audio.h"
#include <stdio.h>
#include <string.h>
#include "audio_codec_if.h"
#include "driver/i2s_std.h"
#include "esp_check.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "esp_idf_version.h"
#include "esp_log.h"
#include "es8311_codec.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !defined(CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE)
#include "driver/i2c_master.h"
#define VOICE_USE_IDF_I2C_MASTER
#else
#include "driver/i2c.h"
#endif
static const char *TAG = "voice_audio";
#ifdef CONFIG_TQ_VOICE_CODEC_PA_PIN_REVERSED
#define VOICE_CODEC_PA_REVERSED true
#else
#define VOICE_CODEC_PA_REVERSED false
#endif
static SemaphoreHandle_t s_lock;
#ifdef VOICE_USE_IDF_I2C_MASTER
static i2c_master_bus_handle_t s_i2c_bus;
#endif
static i2s_chan_handle_t s_i2s_tx;
static i2s_chan_handle_t s_i2s_rx;
static const audio_codec_ctrl_if_t *s_codec_ctrl_if;
static const audio_codec_gpio_if_t *s_codec_gpio_if;
static const audio_codec_data_if_t *s_codec_data_if;
static const audio_codec_if_t *s_codec_if;
static esp_codec_dev_handle_t s_codec_dev;
static bool s_initialized;
static bool s_opened;
static uint32_t s_sample_rate;
static uint8_t s_channels;
static uint8_t s_bits_per_sample;
static void fill_err(char *err, size_t err_len, const char *msg) {
if (err != NULL && err_len > 0) {
snprintf(err, err_len, "%s", msg != NULL ? msg : "unknown error");
}
}
static esp_err_t voice_audio_init_i2c(void) {
#ifdef VOICE_USE_IDF_I2C_MASTER
if (s_i2c_bus != NULL) {
return ESP_OK;
}
i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = CONFIG_TQ_VOICE_I2C_PORT,
.sda_io_num = CONFIG_TQ_VOICE_I2C_SDA_PIN,
.scl_io_num = CONFIG_TQ_VOICE_I2C_SCL_PIN,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
return i2c_new_master_bus(&bus_cfg, &s_i2c_bus);
#else
i2c_config_t i2c_cfg = {
.mode = I2C_MODE_MASTER,
.sda_io_num = CONFIG_TQ_VOICE_I2C_SDA_PIN,
.scl_io_num = CONFIG_TQ_VOICE_I2C_SCL_PIN,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
ESP_RETURN_ON_ERROR(i2c_param_config(CONFIG_TQ_VOICE_I2C_PORT, &i2c_cfg), TAG, "i2c_param_config failed");
return i2c_driver_install(CONFIG_TQ_VOICE_I2C_PORT, i2c_cfg.mode, 0, 0, 0);
#endif
}
static esp_err_t voice_audio_init_i2s(uint32_t sample_rate) {
if (s_i2s_tx != NULL && s_i2s_rx != NULL) {
return ESP_OK;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)CONFIG_TQ_VOICE_I2S_PORT,
I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = 6;
chan_cfg.dma_frame_num = 240;
ESP_RETURN_ON_ERROR(i2s_new_channel(&chan_cfg, &s_i2s_tx, &s_i2s_rx), TAG, "i2s_new_channel failed");
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = CONFIG_TQ_VOICE_I2S_MCLK_PIN,
.bclk = CONFIG_TQ_VOICE_I2S_BCLK_PIN,
.ws = CONFIG_TQ_VOICE_I2S_WS_PIN,
.dout = CONFIG_TQ_VOICE_I2S_DOUT_PIN,
.din = CONFIG_TQ_VOICE_I2S_DIN_PIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
esp_err_t err = i2s_channel_init_std_mode(s_i2s_tx, &std_cfg);
if (err != ESP_OK) {
return err;
}
err = i2s_channel_init_std_mode(s_i2s_rx, &std_cfg);
if (err != ESP_OK) {
return err;
}
ESP_RETURN_ON_ERROR(i2s_channel_enable(s_i2s_tx), TAG, "i2s tx enable failed");
ESP_RETURN_ON_ERROR(i2s_channel_enable(s_i2s_rx), TAG, "i2s rx enable failed");
return ESP_OK;
}
static esp_err_t voice_audio_build_codec_dev(void) {
if (s_codec_dev != NULL) {
return ESP_OK;
}
audio_codec_i2c_cfg_t i2c_cfg = {
.port = (uint8_t)CONFIG_TQ_VOICE_I2C_PORT,
.addr = (uint8_t)CONFIG_TQ_VOICE_CODEC_ADDR,
#ifdef VOICE_USE_IDF_I2C_MASTER
.bus_handle = s_i2c_bus,
#endif
};
s_codec_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
if (s_codec_ctrl_if == NULL) {
return ESP_ERR_NO_MEM;
}
audio_codec_i2s_cfg_t i2s_cfg = {
.port = (uint8_t)CONFIG_TQ_VOICE_I2S_PORT,
.rx_handle = s_i2s_rx,
.tx_handle = s_i2s_tx,
};
s_codec_data_if = audio_codec_new_i2s_data(&i2s_cfg);
if (s_codec_data_if == NULL) {
return ESP_ERR_NO_MEM;
}
s_codec_gpio_if = audio_codec_new_gpio();
if (s_codec_gpio_if == NULL) {
return ESP_ERR_NO_MEM;
}
es8311_codec_cfg_t codec_cfg = {
.ctrl_if = s_codec_ctrl_if,
.gpio_if = s_codec_gpio_if,
.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
.pa_pin = CONFIG_TQ_VOICE_CODEC_PA_PIN,
.pa_reverted = VOICE_CODEC_PA_REVERSED,
.master_mode = false,
.use_mclk = (CONFIG_TQ_VOICE_I2S_MCLK_PIN >= 0),
.digital_mic = false,
.invert_mclk = false,
.invert_sclk = false,
};
s_codec_if = es8311_codec_new(&codec_cfg);
if (s_codec_if == NULL) {
return ESP_ERR_NO_MEM;
}
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
.codec_if = s_codec_if,
.data_if = s_codec_data_if,
};
s_codec_dev = esp_codec_dev_new(&dev_cfg);
if (s_codec_dev == NULL) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
static esp_err_t voice_audio_prepare(const voice_audio_open_config_t *cfg) {
ESP_RETURN_ON_ERROR(voice_audio_init_i2c(), TAG, "i2c init failed");
ESP_RETURN_ON_ERROR(voice_audio_init_i2s(cfg->sample_rate), TAG, "i2s init failed");
ESP_RETURN_ON_ERROR(voice_audio_build_codec_dev(), TAG, "codec dev init failed");
return ESP_OK;
}
esp_err_t voice_audio_open(const voice_audio_open_config_t *cfg, char *err, size_t err_len) {
if (cfg == NULL || cfg->sample_rate == 0 || cfg->channels == 0 || cfg->bits_per_sample != 16) {
fill_err(err, err_len, "invalid voice audio config");
return ESP_ERR_INVALID_ARG;
}
if (s_lock == NULL) {
s_lock = xSemaphoreCreateMutex();
if (s_lock == NULL) {
fill_err(err, err_len, "create mutex failed");
return ESP_ERR_NO_MEM;
}
}
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
fill_err(err, err_len, "voice audio lock timeout");
return ESP_ERR_TIMEOUT;
}
if (s_opened) {
xSemaphoreGive(s_lock);
return ESP_OK;
}
esp_err_t init_err = voice_audio_prepare(cfg);
if (init_err != ESP_OK) {
fill_err(err, err_len, "voice audio peripheral init failed");
xSemaphoreGive(s_lock);
return init_err;
}
if (s_initialized && (s_sample_rate != cfg->sample_rate ||
s_channels != cfg->channels ||
s_bits_per_sample != cfg->bits_per_sample)) {
fill_err(err, err_len, "voice audio format changed after init");
xSemaphoreGive(s_lock);
return ESP_ERR_INVALID_STATE;
}
esp_codec_dev_sample_info_t fs = {
.sample_rate = cfg->sample_rate,
.channel = cfg->channels,
.bits_per_sample = cfg->bits_per_sample,
};
int rc = esp_codec_dev_open(s_codec_dev, &fs);
if (rc != ESP_CODEC_DEV_OK) {
fill_err(err, err_len, "esp_codec_dev_open failed");
xSemaphoreGive(s_lock);
return ESP_FAIL;
}
(void)esp_codec_dev_set_out_vol(s_codec_dev, cfg->output_volume);
(void)esp_codec_dev_set_in_gain(s_codec_dev, cfg->input_gain_db);
(void)esp_codec_dev_set_out_mute(s_codec_dev, false);
(void)esp_codec_dev_set_in_mute(s_codec_dev, false);
s_sample_rate = cfg->sample_rate;
s_channels = cfg->channels;
s_bits_per_sample = cfg->bits_per_sample;
s_initialized = true;
s_opened = true;
ESP_LOGI(TAG,
"voice audio opened: sr=%u ch=%u bits=%u",
(unsigned)s_sample_rate,
(unsigned)s_channels,
(unsigned)s_bits_per_sample);
xSemaphoreGive(s_lock);
return ESP_OK;
}
void voice_audio_close(void) {
if (s_lock == NULL) {
return;
}
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
return;
}
if (s_opened) {
(void)esp_codec_dev_set_in_mute(s_codec_dev, true);
(void)esp_codec_dev_set_out_mute(s_codec_dev, true);
(void)esp_codec_dev_close(s_codec_dev);
s_opened = false;
}
xSemaphoreGive(s_lock);
}
bool voice_audio_is_open(void) {
return s_opened;
}
esp_err_t voice_audio_read_pcm(int16_t *pcm,
size_t samples,
uint32_t timeout_ms,
char *err,
size_t err_len) {
if (pcm == NULL || samples == 0) {
fill_err(err, err_len, "invalid read args");
return ESP_ERR_INVALID_ARG;
}
if (s_lock == NULL || xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
fill_err(err, err_len, "voice read lock timeout");
return ESP_ERR_TIMEOUT;
}
if (!s_opened) {
fill_err(err, err_len, "voice audio not opened");
xSemaphoreGive(s_lock);
return ESP_ERR_INVALID_STATE;
}
int rc = esp_codec_dev_read(s_codec_dev, pcm, (int)(samples * sizeof(int16_t)));
xSemaphoreGive(s_lock);
if (rc != ESP_CODEC_DEV_OK) {
fill_err(err, err_len, "codec read failed");
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t voice_audio_write_pcm(const int16_t *pcm,
size_t samples,
uint32_t timeout_ms,
char *err,
size_t err_len) {
if (pcm == NULL || samples == 0) {
fill_err(err, err_len, "invalid write args");
return ESP_ERR_INVALID_ARG;
}
if (s_lock == NULL || xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
fill_err(err, err_len, "voice write lock timeout");
return ESP_ERR_TIMEOUT;
}
if (!s_opened) {
fill_err(err, err_len, "voice audio not opened");
xSemaphoreGive(s_lock);
return ESP_ERR_INVALID_STATE;
}
int rc = esp_codec_dev_write(s_codec_dev, (void *)pcm, (int)(samples * sizeof(int16_t)));
xSemaphoreGive(s_lock);
if (rc != ESP_CODEC_DEV_OK) {
fill_err(err, err_len, "codec write failed");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -32,10 +32,10 @@ static void wifi_event_handler(void *arg,
}
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < CONFIG_LYF_WIFI_MAXIMUM_RETRY) {
if (s_retry_num < CONFIG_TQ_WIFI_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGW(TAG, "retry to connect to the AP (%d/%d)", s_retry_num, CONFIG_LYF_WIFI_MAXIMUM_RETRY);
ESP_LOGW(TAG, "retry to connect to the AP (%d/%d)", s_retry_num, CONFIG_TQ_WIFI_MAXIMUM_RETRY);
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
@@ -53,13 +53,13 @@ static void wifi_event_handler(void *arg,
static esp_err_t start_sta_mode(void) {
wifi_config_t wifi_config = {0};
if (strlen(CONFIG_LYF_WIFI_SSID) == 0) {
ESP_LOGE(TAG, "CONFIG_LYF_WIFI_SSID is empty; STA-only mode requires valid SSID");
if (strlen(CONFIG_TQ_WIFI_SSID) == 0) {
ESP_LOGE(TAG, "CONFIG_TQ_WIFI_SSID is empty; STA-only mode requires valid SSID");
return ESP_ERR_INVALID_STATE;
}
strlcpy((char *)wifi_config.sta.ssid, CONFIG_LYF_WIFI_SSID, sizeof(wifi_config.sta.ssid));
strlcpy((char *)wifi_config.sta.password, CONFIG_LYF_WIFI_PASSWORD, sizeof(wifi_config.sta.password));
strlcpy((char *)wifi_config.sta.ssid, CONFIG_TQ_WIFI_SSID, sizeof(wifi_config.sta.ssid));
strlcpy((char *)wifi_config.sta.password, CONFIG_TQ_WIFI_PASSWORD, sizeof(wifi_config.sta.password));
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
@@ -77,13 +77,13 @@ static esp_err_t start_sta_mode(void) {
pdMS_TO_TICKS(15000));
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to AP SSID:%s", CONFIG_LYF_WIFI_SSID);
ESP_LOGI(TAG, "connected to AP SSID:%s", CONFIG_TQ_WIFI_SSID);
s_ready = true;
return ESP_OK;
}
if (bits & WIFI_FAIL_BIT) {
ESP_LOGW(TAG, "Failed to connect to SSID:%s", CONFIG_LYF_WIFI_SSID);
ESP_LOGW(TAG, "Failed to connect to SSID:%s", CONFIG_TQ_WIFI_SSID);
return ESP_FAIL;
}