动态调频APP(lvp_app_kws_state_demo)
一般的语音控制类产品都是唤醒词+命令词的形式进行语音控制,也就是先唤醒词唤醒后,再进行二级指令词的识别。由于我们的解码器对多个指令词打分所需要的 MCU 算力较高,为了进一步节省功耗,SDK 引入了两个状态(VAD状态 和 唤醒状态)进行动态调频:
- VAD状态:只能激活主唤醒词,mcu 为低频率,一般设为4M,主唤醒词激活进入 唤醒状态
- 唤醒状态:所有词都能激活,mcu 为高频率,超时 进入 VAD状态
提醒
SDK提供了一份参考示例:lvp_tws/app/lvp_app_kws_state_demo/lvp_app_kws_state_demo.
方便开发者进行参考
1. 代码解读
lvp_tws/app/lvp_app_kws_state_demo/lvp_app_kws_state_demo.c |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 | // #define DO_NOT_SLEEP_WHEN_WAKING_UP // 定义后唤醒词唤醒后mcu不会进入休眠,超时之后才会进入休眠,有些客户会有这个需求
#ifdef DO_NOT_SLEEP_WHEN_WAKING_UP
#include <lvp_pmu.h>
#endif
#define __LOW_FREQUENCE CPU_FREQUENCE_8M // 进入VAD状态的时候的MCU频率
#define __TIME_OUT_S 10 // 激活后无操作超时进如VAD状态时间 单位s
#define __WAKE_UP_KWS_1 100 // 主唤醒词event_id
#define __WAKE_UP_KWS_2 100 // 有些项目有多个主唤醒词
typedef struct TIMEOUT_STANDBY{
unsigned long first_time; // 记录第一次时间
unsigned long next_time; // 记录第二次时间
unsigned char timeout_flag; // 两次时间差大于超时时间,则激活超时标志位
}TIMEOUT_STANDBY; // 用于超时检测
static TIMEOUT_STANDBY kws_state_time = {0, 0, 1}; // 初始状态设为超时状态
static int kws_state_init_flag = 0; // 部分初始化只需要执行一次,初始化标志位
#ifdef DO_NOT_SLEEP_WHEN_WAKING_UP
static int lock = 0; // 用于保存锁的句柄
#endif
//===================================================================================
static int KwsStateDemoAppSuspend(void *priv) // App进入待机回调接口
{
printf(LOG_TAG" ---- %s ----\n", __func__);
BoardSetPowerSavePinMux(); // 将GPIO口全部设为输入,降低待机功耗
return 0;
}
static int KwsStateDemoAppResume(void *priv) // App恢复工作回调接口
{
BoardSetUserPinMux(); // 恢复用户的GPIO配置
printf(LOG_TAG" ---- %s ----\n", __func__);
if (kws_state_time.timeout_flag) // 判断工作后是否超时,因为active状态也能进入待机
LvpDynamiciallyAdjustCpuFrequency(__LOW_FREQUENCE); // 超时设置低频
printf("*************** cpu frequency is %dHz *************\n",gx_clock_get_module_frequence(CLOCK_MODULE_SCPU)); // 获取MCU时钟频率
return 0;
}
static int KwsStateDemoAppInit(void) // App初始化接口
{
if (!kws_state_init_flag) // 这个判断里的程序只会执行一次
{
#ifdef DO_NOT_SLEEP_WHEN_WAKING_UP
LvpPmuSuspendLockCreate(&lock); // 创建一个pmu锁,返回一个空闲锁的句柄
#endif
LvpSetVuiKwsStates(VUI_KWS_VAD_STATE); // 框架默认为Active状态,需要在App里设置为VAD状态
kws_state_init_flag = 1; // 标志位置1
gx_rtc_get_tick(&kws_state_time.first_time); // 获取时间
}
return 0;
}
// App Event Process
static int KwsStateDemoAppEventResponse(APP_EVENT *app_event) // App事件响应
{
if (!kws_state_time.timeout_flag) { // 判断超时标志位,已超时就不再执行
gx_rtc_get_tick(&kws_state_time.next_time); //未超时状态下一直更新next_time
if (kws_state_time.next_time - kws_state_time.first_time > __TIME_OUT_S) {
// 判断是否超时
kws_state_time.timeout_flag = 1; // 超时标志位置1
LvpSetVuiKwsStates(VUI_KWS_VAD_STATE); // 设置为VAD状态
LvpDynamiciallyAdjustCpuFrequency(__LOW_FREQUENCE); // 超时,设置mcu低频
printf("*****************cpu frequency is %dHz ************\n",gx_clock_get_module_frequence(CLOCK_MODULE_SCPU));
// 获取mcu频率,查看是否设置成功
printf("timeout! ---> next_time%ds - first_time%ds = %ds \n", kws_state_time.next_time,\
kws_state_time.first_time, kws_state_time.next_time - kws_state_time.first_time);
// 打印超时信息
#ifdef DO_NOT_SLEEP_WHEN_WAKING_UP
LvpPmuSuspendUnlock(lock); // 超时后解锁
#endif
}
}
if (app_event->event_id < 100) // 100以下的是系统事件,100及以上是用户事件
return 0;
LVP_CONTEXT *context;
unsigned int ctx_size;
LvpGetContext(app_event->ctx_index, &context, &ctx_size);
if (app_event->event_id == __WAKE_UP_KWS_1 || app_event->event_id == __WAKE_UP_KWS_2) {
// 判断是否是主唤醒词激活
if(kws_state_time.timeout_flag){ // 未超时的状态下唤醒不再切换频率
LvpSetVuiKwsStates(VUI_KWS_ACTIVE_STATE); // 设置为Active状态
LvpDynamiciallyAdjustCpuFrequency(CPU_FREQUENCE_DEFAULT);
// 设置mcu频率为用户默认配置的频率
#ifdef DO_NOT_SLEEP_WHEN_WAKING_UP
LvpPmuSuspendLock(lock); // 唤醒后上锁
#endif
}
printf("*************** cpu frequency is %dHz *************\n",gx_clock_get_module_frequence(CLOCK_MODULE_SCPU));
// 获取mcu频率,查看是否设置成功
}
gx_rtc_get_tick(&kws_state_time.first_time);// 激活后更新第一次记录的时间
kws_state_time.timeout_flag = 0; // 将超时标志位置0,激活后,超时重新计算
printf(LOG_TAG"event_id %d\n", app_event->event_id);// 打印时间id
return 0;
}
// APP Main Loop
static int KwsStateDemoAppTaskLoop(void) // App循环接口
{
return 0;
}
|
2. 开发说明
2.1 验证模型
- 在App开发之前先验证模型已经没问题,通过
kws_state_demo
的APP来测试是否能正常切换频率,再去开发自己的App
- 在编译配置中的
LVP Application Settings
中选择 Kws State Demo App
应用。

- 在编译配置中的
VUI Settings
中配置Enable FFT Vad
和 Enable Standby
。

- 验证能正常激活,打印log,切换频率,则表示模型配置没有问题,则可以进行后续开发,不能正常工作见 2.3。
2.2 代码填充
- 验证没问题之后,开发用户自己的App
- 复制这个.c文件代码到要开发的app文件里,只需要配置以下宏
#define __LOW_FREQUENCE CPU_FREQUENCE_8M // 进入VAD状态的时候的MCU频率
#define __TIME_OUT_S 10 // 激活后无操作超时进如VAD状态时间 单位s
#define __WAKE_UP_KWS_1 100 // 主唤醒词event_id
#define __WAKE_UP_KWS_2 100 // 有些项目有多个主唤醒词
- 接口里原有的代码不需要修改,只需要将处理的代码填充到对应的接口里
2.3 常见问题
- 唤醒词无法激活:这个问题基本都是模型中的kws_list.h文件的主唤醒词major没有配置,导致程序无法识别哪个是主唤醒词。
- 例如在
lvp/vui/kws/models/xxxx/kws_list.h
中需要配置主唤醒词的major,找到主唤醒词对应的所在代码,将最后一个参数配置为1。
{"xiao ai xiao ai", {14, 38, 55, 31, 14, 38, 55, 31}, 8, CONFIG_KEYWORD_XIAO_AI_XIAO_AI_THRESHOLD, CONFIG_KEYWORD_XIAO_AI_XIAO_AI_VALUE, 1},
- 唤醒率较低,打印速率较慢,不正常
- NPU频率较低,算法跑不动,需要适当提升NPU频率。
- 在编译配置
Frequency settings
中提高NPU频率

- 指令词太多,没有分组,算法跑不动,需要将指令词分组,分组时注意将较容易互相串词的分在一个组。
- 编译配置
VUI Settings
中选择 Model Param Setting
中进行分组
