随着deepseek的火爆,原来的一些如某精灵和某度音箱显得太低级了。你想拥有一台支持真人语音交互的智能音箱吗?在物联网和人工智能飞速发展的今天,智能语音交互技术已经成为了众多设备的核心功能之一。“小智 AI 聊天机器人”作为一款具有代表性的开源项目,其语音识别唤醒功能更是备受关注。本文将深入剖析该项目中语音识别唤醒功能的实现原理和具体步骤。
一、项目背景与概述
“小智 AI 聊天机器人”是一个基于 ESP32 的物联网应用程序,集成了语音交互、固件升级、设备状态管理等多种功能。其中,语音识别唤醒功能是其实现自然交互的重要基础,允许用户通过特定的唤醒词来激活设备,开启语音对话。
开源项目地址:https://github.com/78/xiaozhi-esp32
后台服务: https://github.com/xinnan-tech/xiaozhi-esp32-server
文档地址: https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb
二、语音识别唤醒功能实现步骤
(一)初始化唤醒词检测
在应用程序启动阶段,若配置中启用了唤醒词检测(CONFIG_USE_WAKE_WORD_DETECT
),会对唤醒词检测模块进行初始化。具体操作包括初始化音频前端(AFE)配置、加载唤醒词模型,并创建一个音频检测任务。以下是相关代码片段:
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.Initialize(codec->input_channels(), codec->input_reference());
wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) {
Schedule([this, &wake_word]() {
// 处理唤醒词检测到后的逻辑
});
});
wake_word_detect_.StartDetection();
#endif
(二)WakeWordDetect::Initialize
方法
该方法负责初始化音频前端配置,加载唤醒词模型,并创建音频检测任务。它会读取唤醒词模型列表,筛选出符合条件的模型,并提取唤醒词。同时,配置音频前端的参数,如输入格式、AEC 模式等。
void WakeWordDetect::Initialize(int channels, bool reference) {
// 初始化通道和参考信息
channels_ = channels;
reference_ = reference;
int ref_num = reference_ ? 1 : 0;
// 加载唤醒词模型
srmodel_list_t *models = esp_srmodel_init("model");
for (int i = 0; i < models->num; i++) {
if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL) {
wakenet_model_ = models->model_name[i];
auto words = esp_srmodel_get_wake_words(models, wakenet_model_);
// 提取唤醒词
std::stringstream ss(words);
std::string word;
while (std::getline(ss, word, ';')) {
wake_words_.push_back(word);
}
}
}
// 配置音频前端输入格式
std::string input_format;
for (int i = 0; i < channels_ - ref_num; i++) {
input_format.push_back('M');
}
for (int i = 0; i < ref_num; i++) {
input_format.push_back('R');
}
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
afe_config->aec_init = reference_;
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
afe_config->afe_perferred_core = 1;
afe_config->afe_perferred_priority = 1;
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_iface_ = esp_afe_handle_from_config(afe_config);
afe_data_ = afe_iface_->create_from_config(afe_config);
// 创建音频检测任务
xTaskCreate([](void* arg) {
auto this_ = (WakeWordDetect*)arg;
this_->AudioDetectionTask();
vTaskDelete(NULL);
}, "audio_detection", 4096, this, 3, nullptr);
}
(三)音频数据输入
在应用程序的音频输入处理函数 Application::InputAudio
中,会将音频数据输入到唤醒词检测模块。若唤醒词检测正在运行,则调用 WakeWordDetect::Feed
方法将数据插入输入缓冲区。
void Application::InputAudio() {
auto codec = Board::GetInstance().GetAudioCodec();
std::vector<int16_t> data;
if (!codec->InputData(data)) {
return;
}
if (codec->input_sample_rate() != 16000) {
// 重采样逻辑
}
#if CONFIG_USE_WAKE_WORD_DETECT
if (wake_word_detect_.IsDetectionRunning()) {
wake_word_detect_.Feed(data);
}
#endif
// ...
}
(四)WakeWordDetect::Feed
方法
该方法将音频数据插入输入缓冲区,并在缓冲区数据足够时将其输入到 AFE 中进行处理。
void WakeWordDetect::Feed(const std::vector<int16_t>& data) {
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_) * channels_;
while (input_buffer_.size() >= feed_size) {
afe_iface_->feed(afe_data_, input_buffer_.data());
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + feed_size);
}
}
(五)音频检测任务
WakeWordDetect::AudioDetectionTask
函数会持续从 AFE 中获取数据,一旦检测到唤醒词,就会停止检测并调用回调函数进行后续处理。
void WakeWordDetect::AudioDetectionTask() {
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d",
feed_size, fetch_size);
while (true) {
xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
if (res == nullptr || res->ret_value == ESP_FAIL) {
continue;
}
// 存储唤醒词数据
StoreWakeWordData((uint16_t*)res->data, res->data_size / sizeof(uint16_t));
if (res->wakeup_state == WAKENET_DETECTED) {
StopDetection();
last_detected_wake_word_ = wake_words_[res->wake_word_index - 1];
if (wake_word_detected_callback_) {
wake_word_detected_callback_(last_detected_wake_word_);
}
}
}
}
(六)唤醒词编码与发送
当检测到唤醒词后,会调用 WakeWordDetect::EncodeWakeWordData
方法对唤醒词数据进行编码,并通过协议将编码后的数据发送到服务器。
// ...
wake_word_detect_.EncodeWakeWordData();
if (!protocol_->OpenAudioChannel()) {
wake_word_detect_.StartDetection();
return;
}
std::vector<uint8_t> opus;
// 编码并发送唤醒词数据到服务器
while (wake_word_detect_.GetWakeWordOpus(opus)) {
protocol_->SendAudio(opus);
}
// 设置聊天状态为唤醒词检测到
protocol_->SendWakeWordDetected(wake_word);
// ...
三、总结
通过以上步骤,“小智 AI 聊天机器人”实现了语音识别唤醒功能。从初始化唤醒词检测、输入音频数据,到持续检测音频数据,再到检测到唤醒词后的编码与发送,整个过程紧密协作,确保了设备能够准确地识别用户的唤醒词并做出响应。这一功能的实现不仅提升了用户体验,也为智能语音交互技术在物联网设备中的应用提供了一个优秀的范例。