内置语音唤醒

更新时间:2025-06-30 11:02:48下载pdf

概述

Wukong AI 硬件开发框架在 T5 芯片/模组上,支持内置语音唤醒算法,用户可以通过特定的唤醒词来唤醒设备。目前默认支持三种唤醒词:“你好涂鸦”、“小智同学”、“Hey, Tuya”(英文)。

如果想定义个性化的唤醒词,目前需要通过涂鸦来定制。您可以联系您的涂鸦商务,提交需求,并沟通具体的项目细节和计划。

硬件设计要求

内置语音唤醒算法需要硬件的支持。设计音频回采电路,将喇叭播放的声音通过回采电路输入到回声消除算法(AEC),以保障唤醒词在喇叭播放声音的时候正常工作。

关于音频回采电路,您可以参考以下硬件方案:

软件设计要求

内置语音唤醒算法目前工作在涂鸦语音子系统中,尚未对外开放。您只需要设置 TY_AI_TOY_CFG_DEFAULTtrigger_modeTY_AI_TRIGGER_MODE_WAKEUP 或者 TY_AI_TRIGGER_MODE_FREE 即自动支持,无需关心其原理以及具体如何使用。

// 交互类型定义
typedef enum {
    TY_AI_TRIGGER_MODE_HOLD,        // 长按触发模式
    TY_AI_TRIGGER_MODE_ONE_SHOT,    // 单次按键,回合制对话模式
    TY_AI_TRIGGER_MODE_WAKEUP,      // 关键词唤醒模式
    TY_AI_TRIGGER_MODE_FREE,        // 关键词唤醒和自由对话模式
} TY_AI_TRIGGER_MODE_E;

// 设置工作模式为关键词唤醒模式
#define TY_AI_TOY_CFG_DEFAULT { \
    .audio_trigger_pin = TUYA_GPIO_NUM_12, \
    .spk_en_pin = TUYA_GPIO_NUM_28, \
    .led_pin = TUYA_GPIO_NUM_1, \
    .trigger_mode = TY_AI_TRIGGER_MODE_WAKEUP, \
    .audio_cfg = TY_AI_AUDIO_CFG_DEF \
}

替换唤醒算法

唤醒算法目前在 CPU1 上运行,接收 VAD 算法判断有人声的数据之后进行唤醒词识别,并将识别成功的通过 IPC(核间通信) 发送给 CPU0,用于唤醒设备进行交互(未来功能会修改,修改时会再更新此文档)。

仅替换唤醒算法

如果您有语音处理的经验,想要使用自己的唤醒算法,可以按照以下步骤:

  1. 确认是否支持自定义 KWS,查看 vendor/T5/t5_os/projects/tuya_app/main/app_main.c 文件中 main 函数;如果当前 SDK 版本不支持,可以自行修改这段代码。

    int main(void)
    {        
        ...
    
    extern OPERATE_RET tuya_ipc_init(void);
        tuya_ipc_init();
    #if (CONFIG_SYS_CPU1)
        if (tuya_asr_enable()) {
            bk_printf("tuya_asr_init\r\n");
            extern VOID_T tkl_system_psram_malloc_force_set(BOOL_T enable);
            extern BOOL_T tuya_asr_enable(VOID_T);
            extern VOID _tuya_asr_int(VOID_T);
            tkl_system_psram_malloc_force_set(TRUE);
            // FIXME: set cpu freq to 480M only for asr
            bk_pm_module_vote_cpu_freq(PM_DEV_ID_DECODER, PM_CPU_FRQ_480M);
            _tuya_asr_init();
    
        } else {
            bk_printf("user_asr_init\r\n");
            extern VOID_T tkl_system_psram_malloc_force_set(BOOL_T enable);
            extern VOID user_asr_int(VOID_T);
            tkl_system_psram_malloc_force_set(TRUE);
            // FIXME: set cpu freq to 480M only for asr
            bk_pm_module_vote_cpu_freq(PM_DEV_ID_DECODER, PM_CPU_FRQ_480M);
            user_asr_init();        
        }
    extern void tkl_fs_init(void);
        tkl_fs_init();
        // xTaskCreate(__fs_test, "fs_test", 4096, NULL, 6, (TaskHandle_t * const )&__fs_test_handle);
    #endif
    
        ...
    
    }
    
  2. 修改 tuya_asr_enable 函数,关闭涂鸦唤醒词算法,自行在语音处理流程中添加唤醒算法。

    BOOL_T tuya_asr_enable(VOID_T)
    {
        return FALSE;
    }
    
  3. 添加唤醒库。如果您自定的唤醒词是以 lib 的形式集成,需要修改 vendor/T5/tuyaos/tuyaos_adapter/CMakelist.txt,在文件末尾新增两行,将 lib 链接到程序中。注意,需要按照您的库的实际存放位置来修改名称。

    add_prebuilt_library(user_asr.a "$ENV{ARMINO_PATH}/../../tuyaos/tuyaos_adapter/include/asr/user/user_asr.a")
    target_link_libraries(${COMPONENT_LIB} INTERFACE user_asr.a)
    
  4. 实现自己的 user_asr_init 入口,以下代码供参考,实际实现需要按照自己的 KWS 算法实际情况进行修改。

    typedef struct {
        uint8_t type;
        int len;
        char *data;
    } user_asr_msg_data_t;
    
    // ASR 消息队列句柄
    static TKL_QUEUE_HANDLE user_asr_msg_queue;
    // 目前语音数据需要从 0 核发送到 1 核,此函数接收 0 核数据,并发送给 ASR 消息队列间
    VOID_T user_asr_cpu1_event(VOID *buf, UINT_T len, VOID *args)
    {
        OPERATE_RET  rt;
        struct ipc_msg_s *send_msg = (struct ipc_msg_s *)buf;
        user_asr_msg_data_tmsg_data;
        memcpy(&msg_data, &send_msg->buf[0], sizeof(user_asr_msg_data_t));
    
        if (msg_data.type == 1) {
            void *p_buff = (void *)tkl_system_psram_malloc(msg_data.len);
            if (p_buff != NULL) {
                memset(p_buff, 0, msg_data.len);
                *(void **)msg_data.data = p_buff;
            }
        } else {
            rt = tkl_queue_post(user_asr_msg_queue, &msg_data, 0);
            if (rt != OPRT_OK) {
                bk_printf("tkl_queue_post failed %d\n", rt);
            }
        }
    }
    
    // 唤醒状态要从 1 核发送到 0 核
    OPERATE_RET user_asr_event(uint8_t event)
    {
        struct ipc_msg_s send_msg = {0};
    
        send_msg.type   = TKL_IPC_TYPE_ASR;
        send_msg.buf[0] = event; 
    
        return tkl_asr_send((UINT8_T *)&send_msg, sizeof(send_msg));
    }
    
    // KWS 处理线程,这里提供参考,实际需要自行实现
    int user_asr_main(void)
    {
        // 此段代码需要自行、按需实现 -- begin
        int ret = user_kws_init(user_parameters);
        if (ret!= OPRT_OK) {
            bk_printf("Fail to initial user asr%d.\n", ret);
            goto __err_exit;
        }
    
        UINT_T timeout = TKL_QUEUE_WAIT_FROEVER;
        while (1)
        {
            int rt = tkl_queue_fetch(user_asr_msg_queue, &msg_data, timeout);
            if (rt != OPRT_OK) {
                bk_printf("")
                continue;
            }
        
            // 组织数据,输入到 KWS 库,这里提供一个参考,实际需要自行实现
            user_ks_OneFrame(user_parameters, //accepting 20ms pcm stream
                    (W16*)(mic_data + i * FRAME_SZ_BYTE),
                    &user_result);
    
        // 判断检测结果,按需实现
        if (user_result!= 0) {     //check wakeup 
            // 判断是否命中了自定义唤醒词
            // 自行实现
    
            // 发送 ASR 事件
            mic_data_len = 0;
            timeout = TKL_QUEUE_WAIT_FROEVER;
            user_asr_event(1);
            
            // 重置算法状态,按需实现
            user_kws_init(user_parameters);
                break;
         }
       // 此段代码需要自行、按需实现 -- end
      }
    }
    
    // 初始化,如果算法线程主函数名修改,则自行替换
    VOID user_asr_init(VOID_T)
    {
        tkl_asr_init(user_asr_cpu1_event, NULL);
        tkl_queue_create_init(&user_asr_msg_queue, sizeof(asr_msg_data_t), 500);
        tkl_thread_create_in_psram(&sg_hrd_hdl, "user_asr", 1024*4, 4, user_asr_main, NULL);
    }
    

替换语音前端算法和唤醒算法

如果您语音处理经验丰富,能够完全使用自己的语音前端处理的算法,可以自行进行 AEC、VAD 算法替换

支持与帮助

在开发过程遇到问题,可以到 TuyaOS 开发者论坛 联网单品开发版块 发帖咨询。