跳转至

动态调频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 VadEnable 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中进行分组