您的位置:首页 - 文章 -  - 正文

6.语音板使用科大讯飞离线命令词识别

0x00 离线命令词识别简介

语音识别技术(Auto Speech Recognize,简称ASR),就是把人的自然语言音频数据转换成文本数据的技术。理论上在线ASR是可以把所有的语音转换成对应的文本,但是我们这里只是介绍离线ASR的应用,就是只在本地识别个数受限制的语音。这里的离线ASR识别就是离线的命令词识别,又可以叫语法识别,它是基于语法规则,可以将与语法一致的自然语言音频转换为文本输出的技术。语法识别的结果值域只在语法文件所列出的规则里,故有很好的匹配率。另外,语法识别结果携带了结果的置信度,应用可以根据置信分数,决定这个结果是否有效。语法识别多用于要更准确结果且有限说法的语音控制,如在家庭环境中空调、电视、电灯的控制。因为这些设备的只有几个确定的常用固定功能。所以做出离线的识别就非常适合,这里在使用离线语法识别时,需要先编写一个语法文件,然后通过调用QISRBuildGrammar接口编译本地语法文件,以及获得语法ID,并在会话时,传入语法ID,以使用该语法,在之后的会话中,继续使用此语法进行识别,无需再次构建。


0x01 下载离线命令词识别SDK

大家可以去讯飞开放平台上,登录自己账户后就可以下载对应的离线命令词SDK了,网址如下:

https://www.xfyun.cn/sdk/dispatcher

下载科大讯飞离线命令词识别SDK

当下载好SDK后,我们就可以将其发送到树莓派板子上,大家可以使用如下的命令进行操作:

scp Linux_aitalk1226_5d5b***.zip corvin@192.168.*.*:~/

这里需要注意的是发送的文件名和树莓派板的IP地址,大家需要根据自己的情况来做相应修改即可,如下图所示:

将SDK发送到树莓派板子上

0x02 编译离线命令词识别源码

在树莓派上我们首先需要将离线命令词识别的SDK压缩包解压,完整的解压命令如下:

unzip -q Linux_aitalk1226_5d5b9efd.zip -d xf_aitalk/

离线唤醒SDK源码组成

接下来就是替换离线命令词识别的动态库了,由于默认的SDK不提供树莓派版本的动态库。这里使用的动态库和第5篇文章中的离线唤醒词识别是一个动态库,如果你已经在上一篇中下载了该动态库,那么这里就不用再下载了。另外,这个动态库其实和后面文章要介绍的离线语音合成功能用的也是一样的。就是说下载这一个动态库,就可以在树莓派上实现:离线唤醒、离线命令词识别、离线语音合成这三个功能了。同样,在这里如果要下载树莓派版本的动态库的话也是要收费的,因为如果大家下载使用的话,会占用我的科大讯飞语音唤醒装机量的。是需要收费的,希望可以理解:

隐藏内容需要支付: ¥30
立即购买 升级VIP

当下载好该离线命令词识别库后,就可以将其放到树莓派中来使用了,这里下载后该库是可以永久使用的,没有时间限制:

树莓派版本动态库

接下来就可以来修改编译相关的脚本和makefile文件了,首先来修改一下bash脚本,修改过程如下:

修改编译脚本

接下来就是修改一下Makefile文件了,因为在make.sh中我们修改了编译时加载库的路径。主要就是修改13行的LDFLAGS,最终修改后的Makefile文件如下:

 1#common makefile header
2
3DIR_INC = ../../include
4DIR_BIN = ../../bin
5DIR_LIB = ../../libs
6
7TARGET    = asr_offline_record_sample
8BIN_TARGET = $(DIR_BIN)/$(TARGET)
9
10CROSS_COMPILE = 
11CFLAGS = -g -Wall -I$(DIR_INC)
12
13LDFLAGS := -L$(DIR_LIB)/
14LDFLAGS += -lmsc -lrt -ldl -lpthread -lasound -lstdc++
15
16OBJECTS := $(patsubst %.c,%.o,$(wildcard *.c))
17
18$(BIN_TARGET) : $(OBJECTS)
19    $(CROSS_COMPILE)gcc $(CFLAGS) $^ -o $@ $(LDFLAGS)
20
21%.o : %.c
22    $(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@
23clean:
24    @rm -f *.o $(BIN_TARGET)
25
26.PHONY:clean
27
28#common makefile foot

在修改好make.sh和Makefile文件后,我们就可以来开始编译程序了,编译过程也很简单,我们直接在源码目录下执行make.sh脚本就可以了:


0x03 运行测试程序

当我们执行完make.sh编译脚本后,就可以在bin目录下生成可执行文件asr_offline_record_sample。但是在执行之前,我们需要认识一下这个巴科斯范式格式的语法文件,因为这个语法文件就是我们后面要离线识别的命令词了。如下图所示:

查看语法文件

这里的call.bnf就是使用巴科斯范式实现的语法文件,最终上述语法文件将检测的是[<want>]<dial>这样的语音。也就是说<want>规则中的:我想、我要、请、帮我...,这些可以说也可以不说。但是<dial>这个规则必须要说,而<dial>规则是由<dialpre><contact>[<dialsuf>]组成的。这个dialpre是必须要说的,它将检测用户有没有说出来:打电话给、打给、拨打、呼叫....这些语音。后面的<contact>就是检测的联系人,这里的联系人只有一个"丁伟"。最后的这个<dialsuf>是可说可不说,这是因为我们平时要给某人打电话的语法就是:“打电话给丁伟”这样的语法,当然还可以换另外一种表达方式,那就是“拨打丁伟的电话”,所以这里是两种语法规则都支持的。

在了解了语法文件后,我们还需要查看下示例源码,因为这里的语法文件中的规则,我们是可以动态的更新的。就是说我们不用重新启动程序,就可以实时的修改检测的语法规则,更新语法规则的API如下:

更新本地语法词典的API

那接下来看一下在asr_offline_record_sample.c源码中是如何更新本次语法词典文件的,调用该API的代码如下:

 1int update_lex_cb(int ecode, const char *info, void *udata)
2{
3    UserData *lex_data = (UserData *)udata;
4
5    if (NULL != lex_data) {
6        lex_data->update_fini = 1;
7        lex_data->errcode = ecode;
8    }
9
10    if (MSP_SUCCESS == ecode)
11        printf("更新词典成功!\n");
12    else
13        printf("更新词典失败!%d\n", ecode);
14
15    return 0;
16}
17
18int update_lexicon(UserData *udata)
19{
20    const char *lex_content                   = "丁伟\n黄辣椒";
21    unsigned int lex_cnt_len                  = strlen(lex_content);
22    char update_lex_params[MAX_PARAMS_LEN]    = {NULL}; 
23
24    snprintf(update_lex_params, MAX_PARAMS_LEN - 1, 
25        "engine_type = local, text_encoding = UTF-8, \
26        asr_res_path = %s, sample_rate = %d, \
27        grm_build_path = %s, grammar_list = %s, "
,
28        ASR_RES_PATH,
29        SAMPLE_RATE_16K,
30        GRM_BUILD_PATH,
31        udata->grammar_id);
32    return QISRUpdateLexicon(LEX_NAME, lex_content, lex_cnt_len, update_lex_params, update_lex_cb, udata);
33}

可以看到在update_lexicon()函数中,将lex_content变量赋值为“丁伟\n黄辣椒”,那就是将在call.bnf语法文件中的<contact>规则,从“丁伟”更新到了“丁伟\n黄辣椒”。意思是说,我们在第一次识别的时候只能识别到“打电话给丁伟”这样的句子,如果没有更新参数之前说“打电话给黄辣椒”是无法识别的,因为默认的call.bnf语法文件中只有“丁伟”。但是当我们在update_lexicon()函数中调用了QISRUpdateLexicon()函数后,我们就可以将<contact>变量更新为“丁伟\n黄辣椒”了。

那在了解整个示例程序的流程后,我们就可以来测试离线命令词识别的效果了,如下视频所示:

测试科大讯飞离线命令词识别

0x04 实现自定义的命令词识别

前面的一节介绍的是科大讯飞提供的示例代码,在了解背后的原理后,我们就可以来自己修改源码,实现自己想要的功能了。那我们在这里可以利用离线命令词识别这个功能,实现一个家庭里电灯的控制。由于我们的语音板上自带有一个可以编程控制的LED灯,那么我们就把这颗LED灯当做家里的电灯来使用。这样我们利用离线命令词识别,就可以实现一个电灯控制的示例了。那首先第一件事,我们就是要创建我们自己的语法文件了。在编写语法文件之前,我们还是要先来学习一下这种巴科斯范式的语法规则。大家可以在SDK源码目录下的docs中找到该完整的手册,我这里给大家简单介绍下需要的知识点,如果要想学习完整的语法编写规则还是要好好的学习下文档。

语音识别的语法定义了语音识别所支持的命令词的集合,Aitalk 4.0产品利用巴科斯范式(BNF)描述语音识别的语法。语法文档被编译成识别网络后,将被送往语音识别器。语音识别器提取输入语音的特征信息并在识别网络上进行路径匹配,最终识别出用户说话的内容。因此语法是语音识别系统的输入之一,它是现阶段语音识别得以应用的必要条件。下面是一个精简版本的打电话的识别语法文件:

精简版本的打电话识别语法

该语法使识别引擎支持的用法如下:“找一下张三”、“打电话给张三”、“找一下李四”、“打电话给李四”。凡是用户说出这个范围中的任意一句话,均可以被识别系统识别。如果用户说的话不在上述范围之内,那识别系统将拒绝识别。可见语法使用一种结构描述了用户可能说出的语言范围和构成模式。

1.记号:对应英文为 Token,其描述了用户语音所对应的文本内容,类似于“说法“,如“中 国|美国” 表示支持中国|美国两个记号的并列。

2.语义:对应英文为 Sementic,表示用户说法所对应的用户所关心的内容,在应用开发中, 可以将部分语义内置于语法文本中,以方便应用程序的处理。如在语音拨号的场景中,记号对应为“火警”,而语义为“ 119”。通过在语法中定义“火警”的语义,当用户说法为 “火警”时,识别引擎将“ 119”返回给应用程序,而无须在应用程序内部再进行文本的解释工作,大大方便了应用程序的开发。

3.规则:规则定义了一系列记号及其相互关系的集合,且可以包含其它子规则。通过指定规则的唯一的名称,使其它的规则可以通过名称引用该规则。

4.槽:槽是一种特殊的规则。 槽描述了一系列记号的并列关系,且不包含任何子规则。 利用 Aitalk SDK,用户可以在程序运行过程中实时增减槽中的记号。借用此功能,用户通过定义槽,描述语法中的频繁变化的内容,如通讯录中的人名,而无须更改语法文件。

在这里我们编写一个自己的语法识别文件,我们命名为control.bnf,具体的内容如下:

1#BNF+IAT 1.0 UTF-8;
2!grammar control;
3!slot <devices>;
4!slot <operate>;
5
6!start <cmdstart>;
7<cmdstart>:<operate><devices>;
8<operate>:打开!id(1)|关闭!id(2);
9<devices>:灯!id(3)|风扇!id(4);

可以看出来,我们这里打算控制两个设备。分别是灯和风扇,那这里的语法就可以识别:“打开灯”、“打开风扇”、“关闭灯”、“关闭风扇”四个命令了。这里需要注意一下!id()的作用,它其实就是起到一个字符串的标识的作用,当id=1的时候就知道是“打开”,当id=2的时候就知道是“关闭”。那我们接下来就可以来修改下asr_offline_record_sample.c这个源码文件了,修改后的具体代码如下:

  1/*
2* 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
3*/

4#include <stdlib.h>
5#include <stdio.h>
6#include <string.h>
7#include <errno.h>
8#include <unistd.h>
9#include <wiringPi.h>
10
11#include "../../include/qisr.h"
12#include "../../include/msp_cmn.h"
13#include "../../include/msp_errors.h"
14#include "speech_recognizer.h"
15
16
17#define FRAME_LEN    640
18#define    BUFFER_SIZE 4096
19#define SAMPLE_RATE_16K     (16000)
20#define SAMPLE_RATE_8K      (8000)
21#define MAX_GRAMMARID_LEN   (32)
22#define MAX_PARAMS_LEN      (1024)
23
24#define LED_PIN 26
25#define FAN_PIN 25
26
27#define ON         1
28#define OFF        2
29#define LED_DEVICE 3
30#define FAN_DEVICE 4
31
32static int recEndFlag = 0//是否识别出结果的标志
33
34const char * ASR_RES_PATH        = "fo|res/asr/common.jet"//离线语法识别资源路径
35const char * GRM_BUILD_PATH      = "res/asr/GrmBuilld"//构建离线语法识别网络生成数据保存路径
36const char * GRM_FILE            = "control.bnf"//构建离线识别语法网络所用的语法文件
37const char * LEX_NAME            = "contact"//更新离线识别语法的contact槽(语法文件为此示例中使用的call.bnf)
38const char * led_str             = "灯";
39const char * fan_str             = "风扇";
40const char * open_str            = "打开";
41const char * close_str           = "关闭";
42
43typedef struct _UserData {
44    int     build_fini; //标识语法构建是否完成
45    int     update_fini; //标识更新词典是否完成
46    int     errcode; //记录语法构建或更新词典回调错误码
47    char    grammar_id[MAX_GRAMMARID_LEN]; //保存语法构建返回的语法ID
48}UserData;
49
50int build_grammar(UserData *udata)//构建离线识别语法网络
51int run_asr(UserData *udata)//进行离线语法识别
52
53void controlDevice(int deviceID, int flag)
54
{
55    if(LED_DEVICE == deviceID) //control led light
56    {
57        if(ON == flag) //light on
58        {
59            digitalWrite(LED_PIN, LOW);
60        }
61        else //light off
62        {
63            digitalWrite(LED_PIN, HIGH);
64        }
65    }
66    else //control fan device
67    {
68        if(ON == flag) //fan on
69        {
70            digitalWrite(FAN_PIN, HIGH);
71        }
72        else //fan off
73        {
74            digitalWrite(FAN_PIN, LOW);
75        }
76    }
77}
78
79int build_grm_cb(int ecode, const char *info, void *udata)
80
{
81    UserData *grm_data = (UserData *)udata;
82
83    if (NULL != grm_data) {
84        grm_data->build_fini = 1;
85        grm_data->errcode = ecode;
86    }
87
88    if (MSP_SUCCESS == ecode && NULL != info) {
89        printf("构建语法成功! 语法ID:%s\n", info);
90        if (NULL != grm_data)
91            snprintf(grm_data->grammar_id, MAX_GRAMMARID_LEN - 1, info);
92    }
93    else
94        printf("构建语法失败!%d\n", ecode);
95
96    return 0;
97}
98
99int build_grammar(UserData *udata)
100
{
101    FILE *grm_file                           = NULL;
102    char *grm_content                        = NULL;
103    unsigned int grm_cnt_len                 = 0;
104    char grm_build_params[MAX_PARAMS_LEN]    = {NULL};
105    int ret                                  = 0;
106
107    grm_file = fopen(GRM_FILE, "rb");
108    if(NULL == grm_file) {
109        printf("打开\"%s\"文件失败![%s]\n", GRM_FILE, strerror(errno));
110        return -1;
111    }
112
113    fseek(grm_file, 0, SEEK_END);
114    grm_cnt_len = ftell(grm_file);
115    fseek(grm_file, 0, SEEK_SET);
116
117    grm_content = (char *)malloc(grm_cnt_len + 1);
118    if (NULL == grm_content)
119    {
120        printf("内存分配失败!\n");
121        fclose(grm_file);
122        grm_file = NULL;
123        return -1;
124    }
125    fread((void*)grm_content, 1, grm_cnt_len, grm_file);
126    grm_content[grm_cnt_len] = '\0';
127    fclose(grm_file);
128    grm_file = NULL;
129
130    snprintf(grm_build_params, MAX_PARAMS_LEN - 1,
131        "engine_type = local, \
132        asr_res_path = %s, sample_rate = %d, \
133        grm_build_path = %s, "
,
134        ASR_RES_PATH,
135        SAMPLE_RATE_16K,
136        GRM_BUILD_PATH
137        );
138    ret = QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, build_grm_cb, udata);
139
140    free(grm_content);
141    grm_content = NULL;
142
143    return ret;
144}
145
146static void show_result(char *stringchar is_over)
147
{
148    char *check = NULL;
149    int device_id = 0;
150    int operate_id = 0;
151
152    printf("\rResult: [ %s ]"string);
153    if(is_over)
154        putchar('\n');
155
156    check = strstr(string, led_str);
157    if(check != NULL)
158    {
159        device_id = 3;
160    }
161    check = strstr(string, fan_str);
162    if(check != NULL)
163    {
164        device_id = 4;
165    }
166    check = strstr(string, open_str);
167    if(check != NULL)
168    {
169       operate_id = 1;
170    }
171    check = strstr(string, close_str);
172    if(check != NULL)
173    {
174        operate_id = 2;
175    }
176
177    if((device_id != 0) && (operate_id != 0))
178    {
179        controlDevice(device_id, operate_id);
180    }
181}
182
183static char *g_result = NULL;
184static unsigned int g_buffersize = BUFFER_SIZE;
185
186void on_result(const char *result, char is_last)
187
{
188    if (result) {
189        size_t left = g_buffersize - 1 - strlen(g_result);
190        size_t size = strlen(result);
191        if (left < size) {
192            g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
193            if (g_result)
194                g_buffersize += BUFFER_SIZE;
195            else {
196                printf("mem alloc failed\n");
197                return;
198            }
199        }
200        strncat(g_result, result, size);
201        show_result(g_result, is_last);
202    }
203}
204void on_speech_begin()
205
{
206    if (g_result)
207    {
208        free(g_result);
209    }
210    g_result = (char*)malloc(BUFFER_SIZE);
211    g_buffersize = BUFFER_SIZE;
212    memset(g_result, 0, g_buffersize);
213
214    printf("Start Listening...\n");
215}
216void on_speech_end(int reason)
217
{
218    recEndFlag = 1;
219    if (reason == END_REASON_VAD_DETECT)
220        printf("\nSpeaking done \n");
221    else
222        printf("\nRecognizer error %d\n", reason);
223}
224
225/* demo recognize the audio from microphone */
226static void demo_mic(const char* session_begin_params)
227
{
228    int errcode;
229    struct speech_rec iat;
230    struct speech_rec_notifier recnotifier = {
231        on_result,
232        on_speech_begin,
233        on_speech_end
234    };
235
236    recEndFlag = 0;
237    errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
238    if (errcode) {
239        printf("speech recognizer init failed\n");
240        return;
241    }
242    errcode = sr_start_listening(&iat);
243    if (errcode) {
244        printf("start listen failed %d\n", errcode);
245    }
246
247    /*mic always recording */
248    while(1)
249    {
250        printf("listening ...\n");
251        if(recEndFlag)
252        {
253            break;
254        }
255        sleep(1);
256    }
257    errcode = sr_stop_listening(&iat);
258    if (errcode) {
259        printf("stop listening failed %d\n", errcode);
260    }
261
262    sr_uninit(&iat);
263}
264
265int run_asr(UserData *udata)
266
{
267    char asr_params[MAX_PARAMS_LEN]    = {NULL};
268    const char *rec_rslt               = NULL;
269    const char *session_id             = NULL;
270    const char *asr_audiof             = NULL;
271    FILE *f_pcm                        = NULL;
272    char *pcm_data                     = NULL;
273    long pcm_count                     = 0;
274    long pcm_size                      = 0;
275    int last_audio                     = 0;
276
277    int aud_stat                       = MSP_AUDIO_SAMPLE_CONTINUE;
278    int ep_status                      = MSP_EP_LOOKING_FOR_SPEECH;
279    int rec_status                     = MSP_REC_STATUS_INCOMPLETE;
280    int rss_status                     = MSP_REC_STATUS_INCOMPLETE;
281    int errcode                        = -1;
282    int aud_src                        = 0;
283
284    //离线语法识别参数设置
285    snprintf(asr_params, MAX_PARAMS_LEN - 1,
286        "engine_type = local, \
287        asr_res_path = %s, sample_rate = %d, \
288        grm_build_path = %s, local_grammar = %s, \
289        result_type = xml, result_encoding = UTF-8, "
,
290        ASR_RES_PATH,
291        SAMPLE_RATE_16K,
292        GRM_BUILD_PATH,
293        udata->grammar_id
294        );
295
296    demo_mic(asr_params);
297    return 0;
298}
299
300int main(int argc, char* argv[])
301
{
302    const char *login_config    = "appid = 5d5b9efd"//登录参数
303    UserData asr_data;
304    int ret                     = 0 ;
305
306    wiringPiSetup();
307    pinMode(LED_PIN, OUTPUT);
308    pinMode(FAN_PIN, OUTPUT);
309
310    ret = MSPLogin(NULLNULL, login_config); //第一个参数为用户名,第二个参数为密码,传NULL即可,第三个参数是登录参数
311    if (MSP_SUCCESS != ret) {
312        printf("登录失败:%d\n", ret);
313        goto exit;
314    }
315
316    memset(&asr_data, 0sizeof(UserData));
317    printf("构建离线识别语法网络...\n");
318    ret = build_grammar(&asr_data);  //第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
319    if (MSP_SUCCESS != ret) {
320        printf("构建语法调用失败!\n");
321        goto exit;
322    }
323    while (1 != asr_data.build_fini)
324        usleep(300 * 1000);
325    if (MSP_SUCCESS != asr_data.errcode)
326        goto exit;
327    printf("离线识别语法网络构建完成,开始识别...\n");
328    while(1)
329    {
330        ret = run_asr(&asr_data);
331        if (MSP_SUCCESS != ret) {
332            printf("离线语法识别出错: %d \n", ret);
333            goto exit;
334        }
335    }
336
337exit:
338    MSPLogout();
339    printf("请按任意键退出...\n");
340    getchar();
341    return 0;
342}

从上面代码可以得知,我们加载的是自己创建的语法文件control.bnf。在开始进行语音识别的时候,就会根据我们的语法文件来进行识别了。那我们接下来就可以来编译,这里由于使用了wiringPi的库来实现对树莓派上的IO口控制,所以在makefile中gcc的编译参数中需要加上lwiringPi的编译选项就可以了。

最后我们来测试一下,看看我们自己的语法文件能否来控制灯和风扇,在这里的风扇控制我是外接了一个继电器模块,通过树莓派上的GPIO.25来控制继电器的通断,从而可以控制风扇的通电、断电。可以先看一下测试设备,如下图所示:

连接各测试设备

下面来看视频演示效果:

自定义语法文件识别效果演示

在这里需要注意的是离线语音识别返回的结果格式,这里返回的是xml格式的结果。其实还可以修改为json格式,大家可以根据自己的编程习惯来选择对应的来解析。我这里默认选择的是xml格式,那反馈的xml格式结果就是如下图所示这样:

识别引擎反馈的xml格式结果

如果你想要反馈json格式的结果该怎么修改代码呢?这里就需要修改下run_asr()函数中调用语音识别时候的参数了,如下图所示:

修改反馈结果的格式

假如修改为json格式,那反馈的结果是什么样子呢?如下所示:

 1corvin@raspberrypi:~/xf_aitalk/bin $ ./asr_offline_record_sample 
2构建离线识别语法网络...
3构建语法成功! 语法ID:control
4离线识别语法网络构建完成,开始识别...
5Start Listening...
6listening ...
7listening ...
8listening ...
9listening ...
10listening ...
11Result: [ {
12  "sn":1,
13  "ls":true,
14  "bg":0,
15  "ed":0,
16  "ws":[{
17      "bg":0,
18      "slot":"<operate>",
19      "cw":[{
20          "w":"关闭",
21          "sc":100,
22          "id":2,
23          "gm":0
24        }]
25    },{
26      "bg":0,
27      "slot":"<devices>",
28      "cw":[{
29          "w":"风扇",
30          "sc":94,
31          "id":4,
32          "gm":0
33        }]
34    }],
35  "sc":98
36} ]
37
38Speaking done 
39listening ...
40Not started or already stopped.

最后就是对反馈的结果来进行解析了,我们反馈的xml格式和json格式结果其实都有对应的库来帮助我们解析各字段的值。但是在这里我使用的是一种比较low的方式,就是从反馈的结果中直接查找有没有对应的字符串。我这里只是给大家做演示而已,在正式的代码中不能使用这么low的方式啊。下面就是我从反馈结果中检测各字段的方法,就是使用了strstr()这个函数,在字符串中检测是否存在子字符串,仅供参考:

 1static void show_result(char *stringchar is_over)
2
{
3    char *check = NULL;
4    int device_id = 0;
5    int operate_id = 0;
6
7    printf("\rResult: [ %s ]"string);
8    if(is_over)
9        putchar('\n');
10
11    check = strstr(string, led_str);
12    if(check != NULL)
13    {
14        device_id = 3;
15    }
16    check = strstr(string, fan_str);
17    if(check != NULL)
18    {
19        device_id = 4;
20    }
21    check = strstr(string, open_str);
22    if(check != NULL)
23    {
24       operate_id = 1;
25    }
26    check = strstr(string, close_str);
27    if(check != NULL)
28    {
29        operate_id = 2;
30    }
31
32    if((device_id != 0) && (operate_id != 0))
33    {
34        controlDevice(device_id, operate_id);
35    }
36}

最后,就是控制对应设备的开关了,这个实现就比较简单了。就是控制对应的GPIO高低即可,具体实现函数如下:

 1void controlDevice(int deviceID, int flag)
2
{
3    if(LED_DEVICE == deviceID) //control led light
4    {
5        if(ON == flag) //light on
6        {
7            digitalWrite(LED_PIN, LOW);
8        }
9        else //light off
10        {
11            digitalWrite(LED_PIN, HIGH);
12        }
13    }
14    else //control fan device
15    {
16        if(ON == flag) //fan on
17        {
18            digitalWrite(FAN_PIN, HIGH);
19        }
20        else //fan off
21        {
22            digitalWrite(FAN_PIN, LOW);
23        }
24    }
25}

0x05 测试源码下载

我已经将所有的测试代码都上传到AIVoiceSystem这个代码仓库中,大家可以从以下链接中找到对应的代码,所有这些测试代码都是放在example目录下:

https://code.corvin.cn/corvin_zhang/AIVoiceSystem

当前在代码仓库中有两个分支,一个是master分支,主要是为非树莓派4板子使用的。另外一个分支是pi4-devel,这个是专门为树莓派4新开的分支代码。如果大家使用的是ROS小课堂发布的树莓派4的ROS系统镜像的话,那就可以使用这个分支的代码。

代码仓库

0x06 参考资料

[1].科大讯飞离线命令词识别Linux SDK文档. https://www.xfyun.cn/doc/asr/commandWord/Linux-SDK.html

[2].科大讯飞qisr.h文件参考手册. http://mscdoc.xfyun.cn/windows/api/iFlytekMSCReferenceManual/qisr_8h.html#a4be034ad09a1d6768fd0d47d06bb22f6

[3].科大讯飞离线语法编写指南. http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=7595


0x07 问题反馈

大家在按照教程操作过程中有任何问题,可以直接在文章末尾给我留言,或者关注ROS小课堂的官方微信公众号,在公众号中给我发消息反馈问题也行。我基本上每天都会处理公众号中的留言!当然如果你要是顺便给ROS小课堂打个赏,我也会感激不尽的,打赏30块还会被邀请进ROS小课堂的微信群,与更多志同道合的小伙伴一起学习和交流!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据