百度AI简介

语音识别服务是百度AI众多服务中的一项,应用该服务,你可以将语音识别为文字,适用于手机应用语音交互、语音内容分析、智能硬件、呼叫中心智能客服等多种场景。下面,我将分享一个基于RT-Thread将该服务应用于STM32上的案例。

本期分享来自RT-Thread的社区小伙伴霹雳大乌龙,如果你也有文章愿意分享/希望获得官方的写作指导,可以发送文章/联系方式邮件至邮箱:xuqianqian@rt-thread.com  

一. 项目介绍

硬件:IoT Board(正点原子 - 潘多拉L475开发板)

平台:RT-Thread + 百度AI

  1. 使用RT-Thread的 stm32l475-atk-pandora BSP 或 RT-Thread IoT-Board SDK;

  2. 挂载elm FatFS文件系统,用于存放待识别音频;

  3. 初始化板载WIFI模块 AP6181 或 使用AT组件+ESP8266,使开发板具备网络功能;

  4. 使用Audio组件,实现录音功能,并将音频存入文件系统;

  5. 使用webclient软件包,将文件系统中的音频上传到百度AI服务端,识别后返回Json数据;

  6. 使用cJson软件包解析数据,根据解析出的数据作出响应动作(控制RGB灯);

  7. 将中文字库烧写进外部spi flash,使用SUFD+FAL软件包读写flash,实现LCD显示识别结果。

因为项目的重点以及难点在于百度语音识别,所以接下来的文章将着重讲解上述的4-7点,其他部分大家自行前往RT-Thread的文档中心学习。

二. 百度语音识别服务使用流程

在项目开始之前,我们需要先熟悉一遍百度语音服务的调用流程,不然直接写代码,你可能会一脸懵逼。百度语音识别简单来说就是百度AI通过API的方式给开发者提供一个通用的HTTP接口,开发者通过这个接口上传音频文件,服务器返回识别结果,就这么简单,具体怎么做我们接着往下看:

1. 首先我们要注册一个百度开发者账号,然后创建一个语音识别的应用

1.1 搜索 “百度AI” ,进入如下页面,点击右上方控制台(未注册的需注册):

1.2 点击“语音技术”,进入如下页面,点击“创建应用”:

1.3 填写相关信息后点击创建,创建成功后可以在应用列表看到你新创建的应用:

2.语音识别的过程

2.1 获取 Access Token:

向授权服务地址https://aip.baidubce.com/oauth/2.0/token 发送请求(推荐使用POST),并在URL中带上以下参数:

grant_type:必须参数,固定为client_credentials

client_id:必须参数,应用的API Key;

client_secret:必须参数,应用的Secret Key;

使用浏览器获取Access Token:

1https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=Gqt6jzFQDB1UrfVlkTBxr43k&client_secret=dsAQulSVgXEUq2xxyUGFegQOpUWVDpx2

● 使用Postman获取Access Token:

这里给大家推荐一个好用的软件Postman,其是一款功能强大的网页调试与发送网页HTTP请求的接口测试神器,使用它你可以方便的体验百度语音识别的完整流程:

2.2 使用Access Token进行语音识别

有了Access Token,就可以开始进行语音识别了,我们采用raw方式,上传本地文件(使用官方提供的16k采样率pcm文件样例),还是用Postman来测试:

语音识别调用地址:https://vop.baidu.com/pro_api

语音数据的采样率和压缩格式在 HTTP-HEADER 里的Content-Type 表明,例:Content-Type: audio/pcm;rate=16000

填写URL参数

● 上传本地音频文件,发送识别,返回结果

自此完整的百度语音识别流程就结束了,更多详细内容,大家参考百度AI文档中心的相关部分:https://ai.baidu.com/docs#/ASR-API/77e2b22e

三. 动手实践

实践开始之前,先确保你已经做了以下两件事:

  1. 注册百度开发者账号,并创建了一个语音识别应用,而且成功获取了Access Token;

  2. 建立一个基于你自己的STM32平台的RT-Thread工程,它必须具备Finsh控制台,文件系统,网络功能(不明白的参见RT-Thread文档中心,网络功能推荐使用AT组件+ESP8266,因为这是最简单快捷的方法)

如果上面说的准备事项没有问题,那么请继续往下看:这次我们将先跳过录音功能,而使用事先准备好的音频文件进行语音识别并控制板载RGB灯(项目介绍的5,6点),所以你还需要准备一些音频文件,比如“红灯开”,“蓝灯关”。。。我是用手机录的音频,然后使用ffmpeg工具将音频转为百度语音官方认为最适合的16k采样率pcm文件,最后将这些音频文件放进sd卡中,我们的文件系统也是挂载在sd卡上的。

本次工程会用到RT-Thread的两个软件包:webclient cJson 软件包,你需要使用ENV工具将这两个软件包添加进工程里。

添加webclient和cJson软件包

好了,开始写代码:

  1/* bd_speech_rcg.c */
  2
  3#include <rtthread.h>
  4#include <bd_speech_rcg.h>
  5
  6#include <sys/socket.h> //网络功能需要的头文件
  7#include <webclient.h>  //webclient软件包头文件
  8#include <dfs_posix.h>  //文件系统需要的头文件
  9#include <cJSON.h>      //CJSON软件包头文件
 10
 11/* 使用外设需要的头文件 */
 12#include <rtdevice.h>
 13#include <board.h>
 14
 15
 16/* 获取RGB灯对应的引脚编号 */
 17#define PIN_LED_R        GET_PIN(E,  7)
 18#define PIN_LED_G        GET_PIN(E,  8)
 19#define PIN_LED_B        GET_PIN(E,  9)
 20
 21#define RES_BUFFER_SIZE     4096        //数据接收数组大小
 22#define HEADER_BUFFER_SIZE      2048    //最大支持的头部长度
 23
 24/* URL */
 25#define POST_FILE_URL  "http://vop.baidu.com/server_api?dev_pid=1536&cuid=lxzzzzzxl&token=25.9119f50a60602866be9288f1f14a1059.315360000.1884092937.282335-15525116"
 26/* 头部数据(必需) */
 27char *form_data = "audio/pcm;rate=16000";
 28
 29/* 预定义的指令 */
 30char *cmd1 = "打开红灯";
 31char *cmd2 = "关闭红灯";
 32char *cmd3 = "打开蓝灯";
 33char *cmd4 = "关闭蓝灯";
 34char *cmd5 = "打开绿灯";
 35char *cmd6 = "关闭绿灯";
 36
 37
 38
 39/************************************************
 40函数名称 :bd
 41功    能 : 将音频文件发送到百度语音服务器,并接收响应数据
 42参    数 : 音频文件名(注意在文件系统中的位置,默认根目录)
 43返 回 值 :void
 44作    者 :rtthread;霹雳大乌龙
 45*************************************************/
 46void bd(int argc, char **argv)
 47{    
 48    char *filename = NULL;
 49    unsigned char *buffer = RT_NULL;
 50    int content_length = -1, bytes_read = 0;
 51    int content_pos = 0;
 52    int ret = 0;
 53
 54    /* 判断命令是否合法 */
 55    if(argc != 2)
 56    {
 57        rt_kprintf("bd <filename>\r\n");
 58        return;
 59    }
 60
 61    /* 获取音频文件名 */
 62    filename = argv[1];
 63
 64    /* 以只读方式打开音频文件 */
 65    int fd = open(filename, O_RDONLY, 0);
 66    if(fd < 0)
 67    {
 68        rt_kprintf("open %d fail!\r\n", filename);
 69        goto __exit;
 70    }
 71
 72    /* 获取音频文件大小 */
 73    size_t length = lseek(fd, 0, SEEK_END);
 74    lseek(fd, 0, SEEK_SET);
 75
 76    /* 创建响应数据接收数据 */
 77    buffer = (unsigned char *) web_malloc(RES_BUFFER_SIZE);
 78    if(buffer == RT_NULL)
 79    {
 80        rt_kprintf("no memory for receive response buffer.\n");
 81        ret = -RT_ENOMEM;
 82        goto __exit;
 83    }
 84
 85    /* 创建会话 */
 86    struct webclient_session *session = webclient_session_create(HEADER_BUFFER_SIZE);
 87    if(session == RT_NULL)
 88    {
 89        ret = -RT_ENOMEM;
 90        goto __exit;
 91    }
 92
 93    /* 拼接头部数据 */
 94    webclient_header_fields_add(session, "Content-Length: %d\r\n", length);
 95    webclient_header_fields_add(session, "Content-Type: %s\r\n", form_data);
 96
 97    /* 发送POST请求 */
 98    int rc = webclient_post(session, POST_FILE_URL, NULL);
 99    if(rc < 0)
100    {
101        rt_kprintf("webclient post data error!\n");
102        goto __exit;
103    }else if (rc == 0)
104    {
105        rt_kprintf("webclient connected and send header msg!\n");
106    }else
107    {
108        rt_kprintf("rc code: %d!\n", rc);
109    }
110
111    while(1)
112    {
113        rt_memset(buffer, 0, RES_BUFFER_SIZE);
114        length = read(fd, buffer, RES_BUFFER_SIZE);
115        if(length <= 0)
116        {
117            break;
118        }
119        ret = webclient_write(session, buffer, length);
120        if(ret < 0)
121        {
122            rt_kprintf("webclient write error!\r\n");
123            break;
124        }   
125        rt_thread_mdelay(100);
126    }
127    close(fd);
128    rt_kprintf("Upload voice data successfully\r\n");
129
130    if(webclient_handle_response(session) != 200)
131    {
132        rt_kprintf("get handle resposne error!");
133        goto __exit;
134    }   
135
136    /* 获取接收的响应数据长度 */
137    content_length = webclient_content_length_get(session);
138    rt_thread_delay(100);
139
140    do
141    {
142        bytes_read = webclient_read(session, buffer, 1024);
143        if (bytes_read <= 0)
144        {
145            break;
146        }
147
148        for(int index = 0; index < bytes_read; index++)
149        {
150            rt_kprintf("%c", buffer[index]);
151        }
152
153        content_pos += bytes_read;
154        }while(content_pos < content_length);   
155
156    /* 解析json数据 */
157    bd_data_parse(buffer);
158
159    __exit:
160                if(fd >= 0)
161                    close(fd);
162                if(session != NULL)
163                    webclient_close(session);
164                if(buffer != NULL)
165                    web_free(buffer);
166
167                return;
168}
169
170/* 导出为命令形式 */
171MSH_CMD_EXPORT(bd, webclient post file);
 1/* bd_speech_rcg.c */
 2
 3/************************************************
 4函数名称 :bd_data_parse
 5功    能 : 解析json数据,并作出响应动作
 6参    数 :data ------ 百度语音服务返回的数据(json格式)
 7返 回 值 :void
 8作    者 :RT-Thread;霹雳大乌龙
 9*************************************************/
10void bd_data_parse(uint8_t *data)
11{
12    cJSON *root = RT_NULL, *object = RT_NULL, *item =RT_NULL;
13
14    root = cJSON_Parse((const char *)data);
15    if (!root)
16    {
17        rt_kprintf("No memory for cJSON root!\n");
18        return;
19    }
20
21    object = cJSON_GetObjectItem(root, "result");
22
23    item = object->child;
24
25    rt_kprintf("\nresult    :%s \r\n", item->valuestring);
26
27        rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
28        rt_pin_mode(PIN_LED_G, PIN_MODE_OUTPUT);
29        rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT);
30        rt_pin_write(PIN_LED_R,1);
31        rt_pin_write(PIN_LED_G,1);
32        rt_pin_write(PIN_LED_B,1);
33
34        if(strstr((char*)data, cmd1) != NULL)
35        {
36                /* 打开红灯 */
37            rt_pin_write(PIN_LED_R,0);
38        }       
39        if(strstr((char*)data, cmd2) != NULL)
40        {
41                /* 关闭红灯 */
42            rt_pin_write(PIN_LED_R,1);
43        }
44        if(strstr((char*)data, cmd3) != NULL)
45        {
46                /* 打开蓝灯 */
47            rt_pin_write(PIN_LED_B,0);
48        }
49        if(strstr((char*)data, cmd4) != NULL)
50        {
51                /* 关闭蓝灯 */
52            rt_pin_write(PIN_LED_B,1);
53        }
54        if(strstr((char*)data, cmd5) != NULL)
55        {
56                /* 打开绿灯 */
57            rt_pin_write(PIN_LED_G,0);
58        }
59        if(strstr((char*)data, cmd6) != NULL)
60        {
61                /* 关闭绿灯 */
62            rt_pin_write(PIN_LED_G,1);
63        }
64
65
66    if (root != RT_NULL)
67        cJSON_Delete(root);
68}

只需以上的代码(代码参考自论坛各位小伙伴),你就可以实现百度语音识别以及控制相应外设了。下面看看实际效果:我使用的潘多拉开发板板载了stlink,且其为我们提供了一个虚拟串口,用usb数据线将开发板和电脑连接起来,将代码烧写进开发板后,我们利用这个虚拟串口,使用Xshell一类的终端软件,就可以看到如下的开机画面:

这便是RT-Thread提供的Finsh控制台组件,使用这个组件,我们可以方便地观察程序的运行状态,以命令行的形式调试运行程序,从图中我们可以看到,我们需要的文件系统和网络功能都已经初始化成功。

使用ls命令看看:

欸~,这便是我事先准备好的音频文件。

在上面的代码中,我们可以看到有这样一句:

1MSH_CMD_EXPORT(bd, webclient post file);

通过这行代码,我们就可以在Finsh控制台里使用bd这个命令,这个命令就是将音频文件发送到百度语音服务器,试试看:

使用bd命令将greenon.pcm发送到百度语音服务器,正确识别出结果:“打开绿灯”;与此同时,rgb灯也亮起了绿色。

尝试其他音频文件,效果完美!

RT-Thread线上/下活动

1、RT-Thread开发者大会报名】上海站马上开始!2019年RT-Thread开发者大会将登入成都、上海、深圳与开发者们见面,还有RT-Thread在中高端智能领域的应用、一站式RTT开发工具、打造IoT极速开发模式等干货演讲,期待您的参与!


立即报名

#题外话# 喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread

你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群

RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

长按二维码,关注我们

点击“阅读原文”报名开发者大会

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐