Add asr support.

This commit is contained in:
Colin 2026-01-06 22:48:09 +08:00
parent 60f1d97871
commit 11186066c7
1 changed files with 491 additions and 0 deletions

491
main/asr.c Normal file
View File

@ -0,0 +1,491 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include "driver/sdmmc_host.h"
#include "esp_crt_bundle.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "esp_log.h"
#include "esp_sntp.h"
#include "esp_tls.h"
#include "esp_vfs_fat.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "netdb.h"
#include "nvs_flash.h"
#include "sdmmc_cmd.h"
#include "time.h"
/* SD card GPIOs */
#define SD_CMD_IO (48)
#define SD_CLK_IO (47)
#define SD_DAT0_IO (21)
/* SD card & recording configurations */
#define RECORD_TIME_SEC (20)
#define SD_MOUNT_POINT "/sdcard"
// WiFi配置
#define WIFI_SSID "Liang"
#define WIFI_PASS "wifi1234"
// API配置
#define API_URL "https://api.siliconflow.cn/v1/audio/transcriptions"
#define BEARER_TOKEN "sk-qxdrdtbbzlrfkiwyorkjvtnzxtqrfbcjqwnuaffpgauuvswu"
#define MODEL_NAME "FunAudioLLM/SenseVoiceSmall"
// SD卡配置
#define MOUNT_POINT "/sdcard"
#define AUDIO_FILE_NAME "demo.wav"
#define AUDIO_FILE "/sdcard/demo.wav"
static const char* TAG = "AUDIO_TRANS";
// 边界字符串
static const char* BOUNDARY =
"------------------------735323031399963166993862150";
static const char* CONTENT_TYPE =
"multipart/form-data; "
"boundary=------------------------735323031399963166993862150";
// WiFi事件处理
static EventGroupHandle_t wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "WiFi disconnected, reconnecting...");
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
}
}
static void wifi_init_sta(void) {
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL));
wifi_config_t wifi_config = {
.sta =
{
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "WiFi initialization complete");
}
esp_err_t init_sdcard(void) {
sdmmc_card_t* sdmmc_card = NULL;
ESP_LOGI(TAG, "Mounting SD card");
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
.allocation_unit_size = 8 * 1024};
ESP_LOGI(TAG, "Initializing SD card");
ESP_LOGI(TAG, "Using SDMMC peripheral");
sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
sdmmc_slot_config_t slot_config =
SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
slot_config.width = 1; // 设置为1线SD模式
slot_config.clk = SD_CLK_IO;
slot_config.cmd = SD_CMD_IO;
slot_config.d0 = SD_DAT0_IO;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻
ESP_LOGI(TAG, "Mounting filesystem");
esp_err_t ret;
while (1) {
ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &sdmmc_host, &slot_config,
&mount_config, &sdmmc_card);
if (ret == ESP_OK) {
break;
} else if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). ",
esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
ESP_LOGI(
TAG, "Card size: %lluMB, speed: %dMHz",
(((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >>
20,
sdmmc_card->max_freq_khz / 1000);
return ESP_OK;
}
// 读取文件到缓冲区
static esp_err_t read_file_to_buffer(const char* filename, uint8_t** buffer,
size_t* size) {
FILE* f = fopen(filename, "rb");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file: %s", filename);
return ESP_FAIL;
}
fseek(f, 0, SEEK_END);
*size = ftell(f);
fseek(f, 0, SEEK_SET);
*buffer = malloc(*size);
if (*buffer == NULL) {
fclose(f);
ESP_LOGE(TAG, "Failed to allocate memory for file");
return ESP_FAIL;
}
size_t read_size = fread(*buffer, 1, *size, f);
fclose(f);
if (read_size != *size) {
free(*buffer);
ESP_LOGE(TAG, "Failed to read file: read %zu bytes, expected %zu",
read_size, *size);
return ESP_FAIL;
}
ESP_LOGI(TAG, "Read file %s, size: %zu bytes", filename, *size);
return ESP_OK;
}
// HTTP事件处理器
esp_err_t _http_event_handler(esp_http_client_event_t* evt) {
switch (evt->event_id) {
case HTTP_EVENT_ON_DATA:
if (!esp_http_client_is_chunked_response(evt->client)) {
ESP_LOGI(TAG, "HTTP Response: %.*s", evt->data_len, (char*)evt->data);
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "HTTP request finished");
break;
case HTTP_EVENT_ERROR:
ESP_LOGE(TAG, "HTTP request error");
break;
default:
break;
}
return ESP_OK;
}
// 构建multipart请求体
static char* build_multipart_body(const uint8_t* file_data, size_t file_size,
const char* filename, size_t* body_len) {
char* model_part = NULL;
char* file_part = NULL;
char* body = NULL;
// 构建model部分
asprintf(&model_part,
"--%s\r\n"
"Content-Disposition: form-data; name=\"model\"\r\n"
"\r\n"
"%s\r\n",
BOUNDARY, MODEL_NAME);
// 构建file部分头部
asprintf(&file_part,
"--%s\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n",
BOUNDARY, filename);
// 构建结束边界
char* end_boundary = NULL;
asprintf(&end_boundary, "\r\n--%s--\r\n", BOUNDARY);
// 计算总长度
*body_len =
strlen(model_part) + strlen(file_part) + file_size + strlen(end_boundary);
// 分配内存并构建完整body
body = malloc(*body_len);
if (!body) {
ESP_LOGE(TAG, "Failed to allocate memory for request body");
goto cleanup;
}
char* ptr = body;
// 复制model部分
memcpy(ptr, model_part, strlen(model_part));
ptr += strlen(model_part);
// 复制file部分头部
memcpy(ptr, file_part, strlen(file_part));
ptr += strlen(file_part);
// 复制文件数据
memcpy(ptr, file_data, file_size);
ptr += file_size;
// 复制结束边界
memcpy(ptr, end_boundary, strlen(end_boundary));
cleanup:
free(model_part);
free(file_part);
free(end_boundary);
return body;
}
// 发送音频转录请求
static void send_audio_transcription(const char* filepath) {
uint8_t* file_data = NULL;
size_t file_size = 0;
// 读取音频文件
ESP_LOGI(TAG, "Reading audio file: %s", filepath);
if (read_file_to_buffer(filepath, &file_data, &file_size) != ESP_OK) {
ESP_LOGE(TAG, "Failed to read audio file");
return;
}
// 构建multipart请求体
size_t body_len = 0;
char* body =
build_multipart_body(file_data, file_size, AUDIO_FILE_NAME, &body_len);
if (!body) {
ESP_LOGE(TAG, "Failed to build request body");
free(file_data);
return;
}
// 配置HTTP客户端
esp_http_client_config_t config = {
.url = API_URL,
.method = HTTP_METHOD_POST,
.event_handler = _http_event_handler,
.buffer_size = 4096,
.buffer_size_tx = 4096,
.timeout_ms = 60000, // Increased timeout to 60 seconds
.skip_cert_common_name_check = false, // Don't skip cert name check
.keep_alive_enable = true,
.use_global_ca_store = true,
.crt_bundle_attach =
esp_crt_bundle_attach, // Use certificate bundle if available
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// 设置请求头
char auth_header[128];
snprintf(auth_header, sizeof(auth_header), "Bearer %s", BEARER_TOKEN);
esp_http_client_set_header(client, "Authorization", auth_header);
esp_http_client_set_header(client, "Content-Type", CONTENT_TYPE);
esp_http_client_set_header(client, "Accept", "application/json");
esp_http_client_set_header(client, "Content-Length", "");
// 设置POST数据
esp_http_client_set_post_field(client, body, body_len);
// 执行请求
ESP_LOGI(TAG, "Sending request to %s", API_URL);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP Status: %d", status_code);
if (status_code == 200) {
ESP_LOGI(TAG, "Request successful");
} else {
ESP_LOGE(TAG, "Request failed with status: %d", status_code);
}
} else {
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
}
// 清理
esp_http_client_cleanup(client);
free(body);
free(file_data);
}
// 创建一个简单的测试,先确认网络和证书工作
static void debug_network_test(void) {
ESP_LOGI(TAG, "Starting network debug test...");
// 1. 测试DNS解析
struct hostent* he = gethostbyname("api.siliconflow.cn");
if (he == NULL) {
ESP_LOGE(TAG, "DNS resolution failed");
} else {
ESP_LOGI(TAG, "DNS resolved successfully");
}
// 2. 测试到服务器的TCP连接
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create socket");
return;
}
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr("8.8.8.8"); // Google DNS
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(53);
if (connect(sock, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) != 0) {
ESP_LOGE(TAG, "Socket connection failed");
} else {
ESP_LOGI(TAG, "Socket connection successful");
close(sock);
}
}
// 根CA证书api.siliconflow.cn的根CA可直接复制使用
const char* rootCACertificate = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)EOF";
void initTime(void) {
// 1. 初始化SNTP
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
// 使用多个NTP服务器以提高成功率
esp_sntp_setservername(0, "ntp.aliyun.com");
esp_sntp_setservername(1, "cn.ntp.org.cn");
esp_sntp_setservername(2, "pool.ntp.org");
// 东八区时区配置GMT+8
setenv("TZ", "CST-8", 1);
tzset();
esp_sntp_init();
// 2. 等待时间同步完成最多等待15秒
int retry = 0;
const int max_retry = 15;
while (esp_sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET &&
retry < max_retry) {
ESP_LOGI(TAG, "等待NTP时间同步...(第%d次重试", retry + 1);
vTaskDelay(pdMS_TO_TICKS(1000)); // ESP-IDF的延时函数替代Arduino的delay
retry++;
}
time_t now = 0;
struct tm timeinfo = {0};
time(&now);
localtime_r(&now, &timeinfo); // 转换为本地时间(东八区)
ESP_LOGI(TAG, "NTP同步成功当前时间%04d-%02d-%02d %02d:%02d:%02d",
timeinfo.tm_year + 1900, // 年份从1900开始计算
timeinfo.tm_mon + 1, // 月份从0开始计算
timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min,
timeinfo.tm_sec);
}
void sendHttpsRequest(const uint8_t* audioData, size_t audioSize) {
// This function is not used in ESP-IDF context, so we'll leave it as a
// placeholder The actual HTTPS request is handled by send_audio_transcription
// function
ESP_LOGI(TAG, "sendHttpsRequest is not used in ESP-IDF implementation");
}
void app_main(void) {
// 初始化NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化全局CA证书存储
ESP_ERROR_CHECK(esp_tls_init_global_ca_store());
// 初始化WiFi
ESP_LOGI(TAG, "Initializing WiFi...");
wifi_init_sta();
// 等待WiFi连接
ESP_LOGI(TAG, "Waiting for WiFi connection...");
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, false, true,
portMAX_DELAY);
debug_network_test();
initTime();
// 初始化SD卡
ESP_LOGI(TAG, "Initializing SD card...");
if (init_sdcard() != ESP_OK) {
ESP_LOGE(TAG, "SD card initialization failed");
return;
}
// 检查文件是否存在
struct stat st;
if (stat(AUDIO_FILE, &st) != 0) {
ESP_LOGE(TAG, "Audio file not found: %s", AUDIO_FILE);
return;
}
ESP_LOGI(TAG, "Audio file found, size: %ld bytes", st.st_size);
// 发送请求
send_audio_transcription(AUDIO_FILE);
// 卸载SD卡
esp_vfs_fat_sdcard_unmount(MOUNT_POINT, NULL);
ESP_LOGI(TAG, "SD card unmounted");
}