前言
本篇是整个百度语音识别连载的第六篇,也是最后一篇了。这一次我们要把前面实现的各部分功能用线程串接起来,形成一个完整的项目,效果是这样的:
按下按键,开始录音,录音结束后自动将音频发送到百度服务器端,返回识别结果,进行数据解析,显示结果(控制外设)。
那么我们大致可以将以上功能划分为三个线程,分别是:按键线程,录音线程以及识别线程。下面开始对这三个线程进行分析:
实现详解
首先我把各部分功能拆分成了多个源文件,这样使整个工程看起来更加简洁:
main.c
语音识别 bd_speech_rcg.c
录音 wav_record.c
按键只是简单的读IO状态,就放在main.c就好了
bd_speech_rcg.c和wav_record.c里都是前面已经实现的功能函数,这里我就不做讲解,主要来看看main.c:
/* main.c */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <dfs_posix.h>
#include <string.h>
#include <fal.h>
#include <drv_lcd.h>
#include <cn_font.h>
/* 函数声明 */
extern int wavrecord_sample();
extern void bd();
/* 线程参数 */
#define THREAD_PRIORITY 25 //优先级
#define THREAD_STACK_SIZE 1024 //线程栈大小
#define THREAD_TIMESLICE 10 //时间片
/* 线程句柄 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool[128];
/* 录音线程 tid1 入口函数 */
static void thread1_entry(void *parameter)
{
static rt_err_t result;
while(1)
{
result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
if (result != RT_EOK)
{
rt_kprintf("take a dynamic semaphore, failed.\n");
rt_sem_delete(dynamic_sem);
return;
}
else
{
rt_kprintf("take a dynamic semaphore, success.\n");
wavrecord_sample(); //获取到信号量,开始录音
rt_mb_send(&mb, NULL); //录音结束,发送邮件
}
rt_thread_mdelay(100);
}
}
/* 语音识别线程 tid2 入口函数 */
static void thread2_entry(void *parameter)
{
while (1)
{
rt_kprintf("try to recv a mail\n");
/* 从邮箱中收取邮件 */
if (rt_mb_recv(&mb, NULL, RT_WAITING_FOREVER) == RT_EOK)
{
show_str(20, 40, 200, 32, (rt_uint8_t *)"百度语音识别", 32);
show_str(20, 100, 200, 32, (rt_uint8_t *)"识别结果:", 32);
rt_kprintf("get a mail from mailbox!");
bd(); //收到邮件,进行语音识别
rt_thread_mdelay(100);
}
}
/* 执行邮箱对象脱离 */
rt_mb_detach(&mb);
}
/* 按键线程 tid3 入口函数 */
static void thread3_entry(void *parameter)
{
unsigned int count = 1;
while(count > 0)
{
if(rt_pin_read(KEY0) == 0)
{
rt_kprintf("release a dynamic semaphore.\n");
rt_sem_release(dynamic_sem); //当按键被按下,释放一个信号量
}
rt_thread_mdelay(100);
}
}
int main(void)
{
fal_init();
rt_pin_mode(KEY0, PIN_MODE_INPUT);
rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
rt_pin_mode(PIN_LED_G, PIN_MODE_OUTPUT);
rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT);
rt_pin_write(PIN_LED_R,1);
rt_pin_write(PIN_LED_G,1);
rt_pin_write(PIN_LED_B,1);
/* 清屏 */
lcd_clear(WHITE);
/* 设置背景色和前景色 */
lcd_set_color(WHITE,BLACK);
/* 在LCD 上显示字符 */
lcd_show_string(55, 5, 24, "RT-Thread");
show_str(120, 220, 200, 16, (rt_uint8_t *)"By 霹雳大乌龙", 16);
/* 创建一个动态信号量,初始值是 0 */
dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
if (dynamic_sem == RT_NULL)
{
rt_kprintf("create dynamic semaphore failed.\n");
return -1;
}
else
{
rt_kprintf("create done. dynamic semaphore value = 0.\n");
}
rt_err_t result;
/* 初始化一个 mailbox */
result = rt_mb_init(&mb,
"mbt", /* 名称是 mbt */
&mb_pool[0], /* 邮箱用到的内存池是 mb_pool */
sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */
RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */
if (result != RT_EOK)
{
rt_kprintf("init mailbox failed.\n");
return -1;
}
/* 创建线程 */
tid1 = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
tid2 = rt_thread_create("thread2",
thread2_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
tid3 = rt_thread_create("thread3",
thread3_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid3 != RT_NULL)
rt_thread_startup(tid3);
return 0;
}
通过上面的源码可以看到,我采用了信号量+邮箱的通讯机制;按键线程不断读取IO状态,当按键被按下时,释放一个信号量;录音线程处于永久等待信号量的状态,当接收到一个信号量时开始录音,录音结束后发送一封邮件到邮箱中;识别线程不停尝试获取邮件,当接收到邮件时,进行语音识别,后续的解析,显示。。。都是我们之前讲过的了。
但是上面的功能实现并不是完美的,可能存在着一些问题,但大致流程是没问题的(效果亲测OK),因为整个工程也是第一次完整实现,我也还没进行优化啥的,所以有不足之处,请多多包涵。
总结
那么本系列的分享到此也就告一段落了,因为我学习RT-Thread的时间也并不算长,所以可能有些理解并不到位,再加上此次项目是第一次实现,并不完美,但是我会把完整工程放到我的GitHub上,后续我有一些优化,功能拓展啥的,大家可以在GitHub上看到,
项目地址:https://github.com/lxzzzzzxl/Baidu_Speech_base_on_RT-Thread
谢谢搭嘎~