ESP32在线语音识别 词法解析
文章目录
- 在线语音识别的优势
- 一,语音识别流程图
- 二,录音
- 三,词法分析
在线语音识别的优势
在线语音识别结合语义分析,具有识别精准,灵活性高的特点,但是,其处理速度不如离线识别。
一,语音识别流程图
与离线识别不同的是,在esp32被唤醒后,会进行录音,录音结束后将音频发送到云端进行语音识别,并将返回的文本结果进行词法分析,得到特征值,根据特征值,执行相应的命令。
二,录音
esp32被唤醒后就开始录音,通过VAD音量检测,判断用户是否在讲话,若讲话停止则停止录音(或到达录音最大时间),并将数据通过http客户端发送到百度云语音识别接口。
这里展示主要的代码,录音的数据保存到recoder中:
//创建vad音量采集vad_handle_t vad_inst = vad_create(VAD_MODE_4, VAD_SAMPLE_RATE_HZ, VAD_FRAME_LENGTH_MS); //创建音量检测模型int16_t *vad_buff = (int16_t *)malloc(VAD_BUFFER_LENGTH * sizeof(short)); //录音bufferif (vad_buff == NULL){ESP_LOGE(TAG, "Memory allocation failed!");}int index = 0;int timeout = 0; //超过一定时间无声音则停止录音int total_rec = 0; //录音时间while (1){//读取流水线的音频缓存到buffer 960kraw_stream_read(raw_read, (char *)buffer, audio_wn_chunksize * sizeof(short)); if (enable_wn){//将音频数据输入唤醒模型if (wakenet->detect(model_wn_data, (int16_t *)buffer) == 1){ESP_LOGI(TAG, "wake up start listening");//匹配,唤醒LED_ON;enable_wn = false;}}else{//唤醒后,raw_stream_read继续读取音频到bufferif (recoder != NULL){//判断 达到录音最长或停止说话if (total_rec < (MAX_RECODER - 960) && timeout < RECODER_TIMEOUT){//继续录音//将buffer的音频复制到recodermemcpy(recoder + (index * audio_wn_chunksize * sizeof(short)), buffer, audio_wn_chunksize * sizeof(short));index++;//记录总音频数据大小total_rec += audio_wn_chunksize * sizeof(short); //max=131072}else{LED_OFF;//停止录音 准备将音频发送到百度apiESP_LOGI(TAG, "stop listening");memset(http_buff, 0, MAX_HTTP_LEN); //重置http buffmemset(url, 0, 200);//配置http_clientesp_http_client_config_t config = {.method = HTTP_METHOD_POST, //post方式.event_handler = http_event_handle, //注册http回调函数.user_data = (void *)http_buff, //传递参数};sprintf(url, BAIDU_ASR_URL, baidu_access_token); //将token组装到urlconfig.url = url;printf("start connect to url = %s\r\n", config.url);//http连接开始准备esp_http_client_handle_t client = esp_http_client_init(&config);esp_http_client_set_header(client, "Content-Type", "audio/pcm;rate=16000"); //设置http头部esp_http_client_set_post_field(client, (const char *)recoder, total_rec); //将录音添加到http bodyESP_LOGI(TAG, "start trasnlate");esp_http_client_perform(client); //执行http连接esp_http_client_close(client); //关闭清除 等待回调函数esp_http_client_cleanup(client);free(recoder); //释放录音内存recoder = NULL;index = 0;total_rec = 0;timeout = 0;enable_wn = true; //进入睡眠,等下次唤醒}}else{recoder = malloc(MAX_RECODER); //为录音分配内存}//复制buffer的音频数据到vad_buffmemcpy(vad_buff, buffer, VAD_BUFFER_LENGTH * sizeof(short));//将vad_buff的音频输入到声音检测模型vad_state_t vad_state = vad_process(vad_inst, vad_buff);//判断是否有声音if (vad_state == VAD_SPEECH){//讲话未结束timeout = 0;}else{//计时增加timeout++;}
三,词法分析
百度词法分析文档
百度词法分析定制说明
接收到语音识别返回的文本后,还需要对文本进行词法分析,解析出文本中包含的指令。这个功能使用的是百度的词法分析定制版。具体逻辑是:首先我们确定一个词汇集,如:{打开,开启,启动},并将该词汇集命名为OPEN,{空调,格力空调}命名为”AC“
例如,用户输入“打开空调”,词法分析就会得到以下结果
{"log_id": 4870567568319578302,"items": [{"loc_details": [],"byte_offset": 0,"uri": "","ne": "OPEN","basic_words": ["打开"],"item": "打开","pos": "","byte_length": 6,"formal": ""},{"loc_details": [],"byte_offset": 6,"uri": "","ne": "AC","basic_words": ["空调"],"item": "空调","pos": "","byte_length": 6,"formal": ""}],"text": "打开空调"
}
我们读取”ne“键中的内容,就能判断用户的意图。以下代码请求词法分析
/** 根据语音结果进行词性分析* 成功返回1*/
int Etymology_Analysis()
{//语音识别结果存放在 http_buffcJSON *root = cJSON_Parse(http_buff);if(root==NULL){ESP_LOGI(TAG,"cjson parse error");return 0;}cJSON *item=cJSON_GetObjectItem(root, "err_no");if(item->valueint!=0){ESP_LOGI(TAG,"translate error,err_no:%d",item->valueint);cJSON_Delete(root);return 0;}item = cJSON_GetObjectItem(root, "result");item = cJSON_GetArrayItem(item,0);char *result = cJSON_GetStringValue(item); //获取语音识别文本//将文本添加进json字符串char *post_data = malloc(POST_DATA_LEN);//将rersult组成json并存到post_data snprintf(post_data, POST_DATA_LEN, "{\"text\":\"%s\"}", result);ESP_LOGI(TAG, "POST DATA:%s", post_data);//清空http buff准备接收数据memset(http_buff, 0, MAX_HTTP_LEN);memset(url, 0, 200);//初始化http客户端 准备调用词性分析apiesp_http_client_config_t config={.method=HTTP_METHOD_POST, //post方式.event_handler=http_event_handle, //注册回调函数.user_data = (void *)http_buff, //传递参数};sprintf(url, BAIDU_ETY_URL, baidu_access_token); //将token加入urlconfig.url = url;esp_http_client_handle_t client = esp_http_client_init(&config);esp_http_client_set_header(client, "Content-Type", "application/json"); //设置http头esp_http_client_set_post_field(client,(const char*)post_data,strlen(post_data)); //将json字符串填入bodyprintf("start connect to url = %s\r\n",config.url);esp_http_client_perform(client); //开始连接int con_len = esp_http_client_get_content_length(client);ESP_LOGI(TAG, "Status = %d, content_length = %d", esp_http_client_get_status_code(client), con_len);//关掉http客户端esp_http_client_close(client);esp_http_client_cleanup(client);//删除cjsoncJSON_Delete(root);free(post_data); //释放postdata,留给下次return 1;
}
在获取到以上的JSON数据后,接下来就是提取“ne”中的内容,用下面的数据结构来辅助解析。
//词性解析出的元素如:打开lexical=Open,text="OPEN"
typedef struct
{enum Lexical lexical; //词性或特征char text[10]; //文本内容
} Ety_Element;
static Ety_Element ety_eles[10] = {0}; //一般一个命令的元素在10以内//命令结构体包括命令的对象,操作,数量,时间(未完成)
typedef struct
{int number;enum Object object; //only for "Aircon Bt Weather"enum AC_Option option; //only for "open close up down "} Audio_Order;
以下代码会解析每个词,并填充到ety_eles数组,每个单词对应一个ety_eles成员:
/** 解析每个单词的词性* 返回:单词数量*/
int parse_items()
{cJSON *root = cJSON_Parse(http_buff); //解析语音jsoncJSON *items = cJSON_GetObjectItem(root, "items");if(items == NULL){return 0;}int arry_size=cJSON_GetArraySize(items);//每个ety_eles存放一个单词 清空准备接收新的单词memset(ety_eles, 0, 10 * sizeof(Ety_Element));cJSON *item,*sub_item;char *character, *text; //词性及文本内容for (int i = 0; i < arry_size; i++){item = cJSON_GetArrayItem(items, i);//ne和pos都是描述词性,两者只能出现一个sub_item = cJSON_GetObjectItem(item, "pos");character = cJSON_GetStringValue(sub_item);//pos为空串时说明ne有效if (strncmp(character,"",1)==0){//ESP_LOGI(TAG, "pos is null");sub_item = cJSON_GetObjectItem(item, "ne");//!ne需要特殊处理character = cJSON_GetStringValue(sub_item);}//sub_item = cJSON_GetObjectItem(item, "item");printf("char = %s \r\n", character);//获取单词的词性if (strncmp(character, "NUM", 3) == 0){/*ety_eles[i].lexical = Num;sub_item = cJSON_GetObjectItem(item, "item");text = cJSON_GetStringValue(sub_item);strncpy(ety_eles[i].text, text, strlen(text)); //保存二位数字*/}else if(strncmp(character,"AC",2)==0){ety_eles[i].lexical = Aircon;}else if(strncmp(character,"BT",2)==0){ety_eles[i].lexical = Bt;}else if(strncmp(character,"WEA",3)==0){ety_eles[i].lexical = Weather;}else if(strncmp(character,"DOWN",4)==0){ety_eles[i].lexical = Down;}else if(strncmp(character,"UP",2)==0){ety_eles[i].lexical = Up;}else if(strncmp(character,"CLOSE",5)==0){ety_eles[i].lexical = Close;}else if(strncmp(character,"OPEN",4)==0){ety_eles[i].lexical = Open;}else if(strncmp(character,"TOMO",4)==0){ety_eles[i].lexical = Tomorrow;}else if(strncmp(character,"AFTTO",5)==0){ety_eles[i].lexical = Aftermotorrow;} else if(strncmp(character,"TODAY",4)==0){ety_eles[i].lexical = Today;}else if(strncmp(character,"TIME",4)==0){//TODO 如何解析中文的时间,暂时不做时间方面的功能ety_eles[i].lexical = TIME; }else if(strncmp(character,"n",1)==0){ety_eles[i].lexical = Nouns;}else if(strncmp(character,"w",1)==0){ety_eles[i].lexical = Word;}else if(strncmp(character,"v",1)==0){ety_eles[i].lexical = Verbs;}else if(strncmp(character,"m",1)==0){//eg:26度 100块 需要提取basiword的第一个sub_item = cJSON_GetObjectItem(item, "basic_words");sub_item = cJSON_GetArrayItem(sub_item, 0);text = cJSON_GetStringValue(sub_item); //数字字符串ety_eles[i].lexical = Mount;strncpy(ety_eles[i].text, text, strlen(text)); //保存数量}else if(strncmp(character,"r",1)==0){ety_eles[i].lexical = Pronouns;}else{ety_eles[i].lexical = Other;}//printf("ele char =%u,text=%s \r\n", ety_eles[i].lexical, ety_eles[i].text);}cJSON_Delete(root);return arry_size;
}
下面,根据得到的ety_eles数组,组装成一个Audio_Order类型的命令:
/** 组装一个语音命令* i:命令中的单词数量* */
Audio_Order build_order(int i)
{//初始化一个语音命令Audio_Order ord={.number=0,.object=obj_other,.option=AC_OPTION_MAX};//遍历单词,提取对命令有关的信息for (int x = 0; x < i; x++){//寻找操作对象switch(ety_eles[x].lexical){case Aircon:ord.object = obj_Ac;break;case Bt:ord.object = obj_Bt;break;case Weather:ord.object = obj_Weather;break;case Open:ord.option = AC_OPTION_OPEN;break;case Close:ord.option = AC_OPTION_CLOSE;break;case Up:ord.option = AC_OPTION_UP;break;case Down:ord.option = AC_OPTION_DOWN;break;case Num:ord.number = atoi(ety_eles[x].text);//字符串数字转整型数字//printf("num=%d\r\n", ord.number);break;case Mount:ord.number = atoi(ety_eles[x].text);//printf("num=%d\r\n", ord.number);case TIME:break;case Today:ord.number = 0;break;case Tomorrow:ord.number = 1;break;case Aftermotorrow:ord.number = 2;break;//其他属性忽略default:break;}}return ord;
}
有了Audio_Order命令,我们就能根据命令的内容作出反应。
ESP32在线语音识别 词法解析相关推荐
- ESP32接入百度智能云语音识别,实现在线语音识别
一.概述 使用ESP32接入百度智能云实现在线语音识别.实现最基本的语音识别功能还是很简单的,但还是遇到了一些小问题,在这记录一下. 使用了max9814麦克风模块用做语音输入,一个按键来控制 ...
- Java实现在线语音识别
本文为大家分享了Java实现在线语音识别的具体方法,供大家参考,具体内容如下 利用讯飞开发平台作为第三方库 首先需要在讯飞开发平台下载SDK,网址为,讯飞开发平台,这些SDK 下载都是免费的,当然你需 ...
- Java中实现在线语音识别(科大讯飞免费的SKD)、SDK下载和IDEA项目搭建、成功运行【完整代码】
一.下载语音听写(流式版)SDK 科大讯飞官网:https://www.xfyun.cn/ 1.1 实名认证 首先登陆讯飞开放平台:https://passport.xfyun.cn/login,微信 ...
- golang源码分析:编译过程词法解析的流程
golang编译 由于golang作为静态语言,当使用go build时就会生成对应的编译完成之后的文件,那这个编译过程大致会做什么事情呢,在golang中的编译大致有哪些流程. golang示例代码 ...
- PHP的词法解析器:re2c
http://www.phppan.com/2011/09/php-lexical-re2c/ PHP的词法解析器:re2c 胖胖 PHP 2011/09/26 1 条留言 147 views re2 ...
- re2c php,PHP的词法解析器:re2c
出处:http://www.phppan.com/2011/09/php-lexical-re2c/ 作者: 胖胖 re2c是一个扫描器制作工具,可以创建非常快速灵活的扫描器.它可以产生高效代码,基于 ...
- tensorflow 语音识别_调研报告|在线语音识别改进方法之序列区分性训练
这篇文章主要调研的是一种常见的改进在线语音识别的方法:序列区分性训练(Sequence Discriminative Training).相信有很多人已经在 CTC/CE 的训练上遇到了瓶颈,而一些新 ...
- 为提升在线语音识别效率,他创造了两种升级版算法模型
近日,阿里算法专家坤承携<使用改进版本的LATENCY-CONTROLLED BLSTM 算法模型提升在线语音识别效率>(IMPROVING LATENCY-CONTROLLED BLST ...
- 在线Excel文件解析转换成JSON格式
在线Excel文件解析转换成JSON格式 在线Excel文件解析转换成JSON格式 本工具可以将上传的Excel文件解析转换成JSON格式,支持下载 本工具可以将上传的Excel文件解析转换成JSON ...
最新文章
- Android Manager
- 深度学习框架PyTorch与TensorFlow,谁更胜一筹?
- IIS 部署asp.net Provisional headers are shown 在VS2005返回值,部署不返回值
- 看穿面试这件事儿……
- Python pandas库159个常用方法使用说明(转载)
- IDEA里的web.xml页面的Servlet名称报错下方出现红色下划线
- 系统操作手册_东芝CT操作手册——系统概述
- Ssm在线商城系统实战开发
- (转)HapMap简介
- RabbitMQ入门(2)--工作队列
- 搭建和测试 Redis 主备和集群
- 【iOS】打印方法名
- 浏览器Firefox新标签页默认打开地址设置
- c语言char str什意思,char *str与*str的区别
- win10开机的微软服务器,win10系统开机登录微软账户的操作方法
- ASK,OOK,FSK,GFSK简介
- 2020-04-08
- linux 安装守护进程supervisor
- 千峰教育——网络管理
- 如何将自己的代码上传到github
热门文章
- 3D-HEVC解码器一
- excel 统计函数笔记
- c语言使用循环下落方块,C语言 俄罗斯方块的实现1 全局变量
- COB-软封装的一些理解
- oracle查看某个分区的数据,查看oracle表的分区信息
- win10网页找不到服务器dns,教你win10打开网页提示无法解析服务器dns地址的解决教程。...
- java开发微信公众号:微信公众号对接
- 【三维激光扫描技术】原理、方法及实验图文教程目录
- java.sql.SQLException: Parameter index out of range (4 number of parameters, which is 2).
- Chrome浏览器安装本地插件