add testing

This commit is contained in:
Terrence
2024-09-14 14:58:03 +08:00
parent 140ed56ee9
commit e46016b3fc
3 changed files with 107 additions and 31 deletions

View File

@@ -4,13 +4,9 @@ BiliBili 视频介绍 [【ESP32+SenseVoice+Qwen72B打造你的AI聊天伴侣
学习交流 QQ 群946599635
## 免开发环境烧录
## 硬件部分
https://github.com/78/xiaozhi-esp32/releases
固件使用的是作者友情提供的测试服,目前开放免费使用,请勿用于商业用途。
## 硬件
### DIY 所需硬件
- 开发板ESP32-S3-DevKitC-1
- 麦克风INMP441
@@ -19,23 +15,8 @@ https://github.com/78/xiaozhi-esp32/releases
- 400 孔面包板 2 块
- 导线若干
## 开发环境
- Cursor 或 VSCode
- 安装 ESP-IDF 插件,安装 SDK v5.3
- Ubuntu 比 Windows 更好,编译速度快,也免去驱动问题的困扰
## 项目配置与编译固件
- 目前只支持 ESP32 S3Flash 至少 8MB, PSRAM 至少 2MB注意默认配置只兼容 8MB PSRAM如果你使用 2MB PSRAM需要修改配置否则无法识别
- 配置 OTA Version URL 为 `https://api.tenclass.net/xiaozhi/ota/`
- 配置 WebSocket URL 为 `wss://api.tenclass.net/xiaozhi/v1/`
- 配置 WebSocket Access Token 为 `test-token`
- 如果 INMP441 和 MAX98357 接线跟默认配置不一样,需要修改 GPIO 配置
- 配置完成后,编译固件
## GPIO 接线指引
### GPIO 接线指引
以下是默认接线方案,如果你的接线跟默认不一样,请在项目配置中同步修改。
@@ -43,7 +24,7 @@ https://github.com/78/xiaozhi-esp32/releases
注意MAX98357 的 GND 和 VIN 接线隐藏在元件下方。INMP441 的 VDD 和 GND 不能接反,否则会烧毁麦克风。
### MAX98357 功放
#### MAX98357 功放
```
LRC -> GPIO 4
@@ -55,7 +36,7 @@ GND -> GND
VIN -> 3.3V 或 5V如果你的喇叭需要 5V应该将 VIN 接到 5V
```
### INMP441 麦克风
#### INMP441 麦克风
```
L/R -> GND
@@ -66,11 +47,37 @@ VDD -> 3.3V
GND -> GND
```
## 配置 WiFi
## 固件部分
### 免开发环境烧录
新手第一次操作建议先不要搭建开发环境,直接使用免开发环境烧录的固件。
点击 [这里](https://github.com/78/xiaozhi-esp32/releases) 下载最新版固件。
固件使用的是作者友情提供的测试服,目前开放免费使用,请勿用于商业用途。
### 搭建开发环境
- Cursor 或 VSCode
- 安装 ESP-IDF 插件,选择 SDK 版本 5.3 或以上
- Ubuntu 比 Windows 更好,编译速度快,也免去驱动问题的困扰
### 配置项目与编译固件
- 目前只支持 ESP32 S3Flash 至少 8MB, PSRAM 至少 2MB注意默认配置只兼容 8MB PSRAM如果你使用 2MB PSRAM需要修改配置否则无法识别
- 配置 OTA Version URL 为 `https://api.tenclass.net/xiaozhi/ota/`
- 配置 WebSocket URL 为 `wss://api.tenclass.net/xiaozhi/v1/`
- 配置 WebSocket Access Token 为 `test-token`
- 如果 INMP441 和 MAX98357 接线跟默认配置不一样,需要修改 GPIO 配置
- 配置完成后,编译固件
## 配置 Wi-Fi
按照上述接线,烧录固件,设备上电后,开发板上的 RGB 会闪烁蓝灯(部分开发板需要焊接 RGB 灯的开关才会亮),进入配网状态。
打开手机 WiFi连接上设备热点 `Xiaozhi-xxxx` 后,使用浏览器访问 `http://192.168.4.1`,进入配网页面。
打开手机 Wi-Fi连接上设备热点 `Xiaozhi-xxxx` 后,使用浏览器访问 `http://192.168.4.1`,进入配网页面。
选择你的路由器 WiFi输入密码点击连接设备会在 3 秒后自动重启,之后设备会自动连接到路由器。
@@ -82,6 +89,10 @@ GND -> GND
如果没有亮绿灯,或者蓝灯常亮,说明设备没有连接到服务器,请检查 WiFi 连接是否正常。
如果设备已经连接 Wi-Fi但是没有声音请检查是否接线正确。
在 v0.2.1 版本之后的固件,也可以按下连接 GPIO 1 的按钮(低电平有效),进行录音测试。
## 配置设备
如果上述步骤测试成功,设备会播报你的设备 ID你需要到 [小智测试服的控制面板](https://xiaozhi.tenclass.net/) 页面,添加设备。

View File

@@ -6,6 +6,7 @@
#include "model_path.h"
#include "SystemInfo.h"
#include "cJSON.h"
#include "driver/gpio.h"
#define TAG "Application"
@@ -150,9 +151,14 @@ void Application::SetChatState(ChatState state) {
builtin_led.SetBlue();
builtin_led.TurnOn();
break;
case kChatStateTesting:
ESP_LOGI(TAG, "Chat state: testing");
builtin_led.SetRed();
builtin_led.TurnOn();
break;
}
const char* state_str[] = { "idle", "connecting", "listening", "speaking", "wake_word_detected", "unknown" };
const char* state_str[] = { "idle", "connecting", "listening", "speaking", "wake_word_detected", "testing", "unknown" };
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (ws_client_ && ws_client_->IsConnected()) {
cJSON* root = cJSON_CreateObject();
@@ -327,6 +333,49 @@ void Application::SendWakeWordData() {
wake_word_opus_.clear();
}
void Application::CheckTestButton() {
if (gpio_get_level(GPIO_NUM_1) == 0) {
if (chat_state_ == kChatStateIdle) {
SetChatState(kChatStateTesting);
test_resampler_.Configure(CONFIG_AUDIO_INPUT_SAMPLE_RATE, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE);
}
} else {
if (chat_state_ == kChatStateTesting) {
SetChatState(kChatStateIdle);
// 创建新线程来处理音频播放
xTaskCreate([](void* arg) {
Application* app = static_cast<Application*>(arg);
app->PlayTestAudio();
vTaskDelete(NULL);
}, "play_test_audio", 4096, this, 1, NULL);
}
}
}
void Application::PlayTestAudio() {
// 写入音频数据到扬声器
auto packet = new AudioPacket();
packet->type = kAudioPacketTypeStart;
audio_device_.QueueAudioPacket(packet);
for (auto& pcm : test_pcm_) {
packet = new AudioPacket();
packet->type = kAudioPacketTypeData;
packet->pcm.resize(test_resampler_.GetOutputSamples(pcm.iov_len / 2));
test_resampler_.Process((int16_t*)pcm.iov_base, pcm.iov_len / 2, packet->pcm.data());
audio_device_.QueueAudioPacket(packet);
heap_caps_free(pcm.iov_base);
}
// 清除测试PCM数据
test_pcm_.clear();
// 停止音频设备
packet = new AudioPacket();
packet->type = kAudioPacketTypeStop;
audio_device_.QueueAudioPacket(packet);
}
void Application::AudioDetectionTask() {
auto chunk_size = esp_afe_sr_v1.get_fetch_chunksize(afe_detection_data_);
ESP_LOGI(TAG, "Audio detection task started, chunk size: %d", chunk_size);
@@ -346,6 +395,17 @@ void Application::AudioDetectionTask() {
// Store the wake word data for voice recognition, like who is speaking
StoreWakeWordData((uint8_t*)res->data, res->data_size);
CheckTestButton();
if (chat_state_ == kChatStateTesting) {
iovec iov = {
.iov_base = heap_caps_malloc(res->data_size, MALLOC_CAP_SPIRAM),
.iov_len = (size_t)res->data_size
};
memcpy(iov.iov_base, res->data, res->data_size);
test_pcm_.push_back(iov);
continue;
}
if (res->wakeup_state == WAKENET_DETECTED) {
xEventGroupClearBits(event_group_, DETECTION_RUNNING);
SetChatState(kChatStateConnecting);
@@ -458,9 +518,9 @@ void Application::AudioDecodeTask() {
}
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
int target_size = frame_size * CONFIG_AUDIO_OUTPUT_SAMPLE_RATE / opus_decode_sample_rate_;
int target_size = test_resampler_.GetOutputSamples(frame_size);
std::vector<int16_t> resampled(target_size);
opus_resampler_.Process(packet->pcm.data(), frame_size, resampled.data(), target_size);
test_resampler_.Process(packet->pcm.data(), frame_size, resampled.data());
packet->pcm = std::move(resampled);
}
}

View File

@@ -27,7 +27,8 @@ enum ChatState {
kChatStateConnecting,
kChatStateListening,
kChatStateSpeaking,
kChatStateWakeWordDetected
kChatStateWakeWordDetected,
kChatStateTesting
};
class Application {
@@ -76,6 +77,8 @@ private:
int opus_duration_ms_ = 60;
int opus_decode_sample_rate_ = CONFIG_AUDIO_OUTPUT_SAMPLE_RATE;
OpusResampler opus_resampler_;
OpusResampler test_resampler_;
std::vector<iovec> test_pcm_;
TaskHandle_t wake_word_encode_task_ = nullptr;
StaticTask_t wake_word_encode_task_buffer_;
@@ -91,6 +94,8 @@ private:
void StoreWakeWordData(uint8_t* data, size_t size);
void EncodeWakeWordData();
void SendWakeWordData();
void CheckTestButton();
void PlayTestAudio();
void AudioFeedTask();
void AudioDetectionTask();