本地唤醒是基于乐鑫的语音框架实现的离线语音唤醒功能呢,那么这个框架就是ESP-Skainet 是乐鑫开发的智能语音助手,可支持唤醒词引(WakeNet),离线语音命令识别引擎(MultiNet)和前端声学算法。那么问题来了为什么不用这个框架的离线语音命令呢?原因是在我测试来看这个离线的语音准确率相较于在线语音识别没有那么好,感兴趣的你们可以去尝试一下,另外一个原因就是比较耗费flash的资源。
百度语音识别就是百度智能云的在线短语音识别,百度短语音识别可以将60秒以下的音频识别为文字。适用于语音对话、语音控制、语音输入等场景。
接口类型: 通过 REST API 的方式提供的通用的 HTTP 接口。适用于任意操作系统,任意编程语言
**接口限制:**需要上传完整的录音文件,录音文件时长不超过60秒。浏览器由于无法跨域请求百度语音服务器的域名,因此无法直接调用API接口。
支持音频格式: pcm、wav、amr、m4a
音频编码要求: 采样率 16000、8000,16bit 位深,单声道
那么本次博文用的在线语音识别就是使用http post的方式将语音文件上传至百度智能云语音别的接口,然后的到处理结果
这里的APIkey和Secret key就是使用百度语音识别的密钥了,后面会用到,至此百度智能云的语音技术就创建完成了。
我的思路是这样的,使用乐鑫的离线语音唤醒框架,唤醒后呢会播放MP3提示音:叮咚,然后录音,再把录音内容通过http上传至百度智能云,然后前端回传识别结果,完成语音识别。
这里使用的开发板呢,是安信可的ESP32-Audio-Kit(A1s),当然在乐鑫其他的板子上也能使用。
在main文件夹下的component.mk文件下添加依赖
COMPONENT_EMBED_TXTFILES := dingdong.mp3
在main文件夹下的CMakeLists.txt文件下添加依赖
set(COMPONENT_EMBED_TXTFILES dingdong.mp3)
然后把MP3文件放入main文件夹下,这样就能把MP3嵌入在内部flash下,具体的实现代码看文末的源代码,这里就不做具体阐述了。
这里希望大家先去了解一下百度语音识别的技术文档REST文档,了解一下是怎样上传的
int get_access_token(void)
{
if (baidu_access_token == NULL) {
// Must freed `baidu_access_token` after used
baidu_access_token = baidu_get_access_token(CONFIG_BAIDU_ACCESS_KEY, CONFIG_BAIDU_SECRET_KEY);
}
if (baidu_access_token == NULL) {
ESP_LOGE(TAG, "Error issuing access token");
return ESP_FAIL;
}
snprintf(baidu_asr_url, 256, "%s%s", baidu_asr, baidu_access_token);
return ESP_OK;
}
这里使用了一个库函数,include进来就行
#include "baidu_access_token.h"
这样就获取了token
ESP_LOGI(TAG, "Initialize SR wn handle");
esp_wn_iface_t *wakenet;
model_coeff_getter_t *model_coeff_getter;
model_iface_data_t *model_wn_data;
get_wakenet_iface(&wakenet);
get_wakenet_coeff(&model_coeff_getter);
model_wn_data = wakenet->create(model_coeff_getter, DET_MODE_90);
int wn_num = wakenet->get_word_num(model_wn_data);
for (int i = 1; i <= wn_num; i++) {
char *name = wakenet->get_word_name(model_wn_data, i);
ESP_LOGI(TAG, "keywords: %s (index = %d)", name, i);
}
float wn_threshold = wakenet->get_det_threshold(model_wn_data, 1);
int wn_sample_rate = wakenet->get_samp_rate(model_wn_data);
int audio_wn_chunksize = wakenet->get_samp_chunksize(model_wn_data);
ESP_LOGI(TAG, "keywords_num = %d, threshold = %f, sample_rate = %d, chunksize = %d, sizeof_uint16 = %d", wn_num, wn_threshold, wn_sample_rate, audio_wn_chunksize, sizeof(int16_t));
int size = audio_wn_chunksize;
int16_t *buffer = (int16_t *)malloc(size * sizeof(short));
bool enable_wn = true;
while (1) {
if (enable_wn) {
raw_stream_read(raw_read, (char *)buffer, size * sizeof(short));
if (wakenet->detect(model_wn_data, (int16_t *)buffer) == WAKE_UP) {
start_play_mp3();
ESP_LOGI(TAG, "wake up");
enable_wn = false;
}
} else {
if (baidu_asr_url[0] != '\0') { //得到token
char *buff = (char *)heap_caps_malloc(96 * 1024, MALLOC_CAP_SPIRAM);
if (NULL == buff) {
ESP_LOGE(TAG, "Memory allocation failed!");
return;
}
memset(buff, 0, 96 * 1024);
for(size_t i = 0; i < 12; i++) {
raw_stream_read(raw_read, (char *)buff + i * 8 * 1024, 8 * 1024);
}
esp_http_client_config_t config = {
.url = baidu_asr_url,
.method = HTTP_METHOD_POST,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "audio/pcm;rate=16000");
esp_http_client_set_post_field(client, (const char *)buff, 96 * 1024);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
int max_len = 1 * 1024;
char *data = (char *)heap_caps_malloc(max_len, MALLOC_CAP_SPIRAM);
int read_index = 0, total_len = 0;
int read_len = 0;
//得到返回的数据
while (1) {
read_len = esp_http_client_read(client, data + read_index, max_len - read_index);
if (read_len <= 0) {
break;
}
read_index += read_len;
total_len += read_len;
data[read_index] = 0;
}
ESP_LOGI(TAG, "%d%s", read_len, data);
free(data);
data = NULL;
}
else {
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
free(buff);
enable_wn = true;
buff = NULL;
}
}
}
这么长一串代码内,原理其实很简单,首先初始化本地唤醒词,这里定义了一个flag来实现,唤醒后播放叮咚mp3文件,播放完后录音,录音后使用http上传,然后得到结果,这里重点说一下上传部分。
我这里使用的是外都RAM,这点需要特别注意,使用heap_caps_malloc()函数来分配内存,上传格式是16KHz 16bit 单声道,那么录音1秒钟所需要的内存是16000 Hz × 16 bit × 1 × 1 s = 256000bit,就是32Kbyte,那么我们录3秒就是需要96K内存,然后raw的录音缓存时8k,那么我们需要循环读取12次,然后根据格式通过HTTP上传。最终得到我们想要的结果。
这里分别填入WIFI的名称、密码,前面创建的API Key和Secret Key
Component config ->ESP32-specific->SPI RAM config->
这里要注意把spi ram的速度调为80Mhz,否则可能会出问题
打开esp_http_client.h文件
#include "esp_http_client.h"
将缓存buffer改为1024,否则接收回传数据会错误
#define DEFAULT_HTTP_BUF_SIZE (1024)
首先我们打开monitor,说Hi,乐鑫,然后听到叮咚音乐后,随便说一句,就能得到想要的结果。
那么到这一步恭喜你们迈入人工智能的大门,人工智能或许没那么复杂。
本博文源码:FEIYUE_ESP32 欢迎star!欢迎star!欢迎star!