当前位置: 首页 > news >正文

【双MCU项目复盘与优化】04 - 使用ESP-SR 进行语音识别

摘要:本文复盘了 V3 智控面板中使用 ESP-SR 框架实现离线语音识别的方案,包括 AFE 声学前端、WakeNet 唤醒词、MultiNet 命令词的配置与双任务调度逻辑,并指出 V4 需要优化任务周期与优先级以避免数据过载和任务饥饿

  • 参考资料:
    • AFE 声学前端算法框架 - ESP32-S3 - — ESP-SR latest 文档
    • 正点原子ESP32S3+ES8388+ESP-SR实现离线语音唤醒 - 小镇青年达师傅 - 博客园

1. V3 复盘

1.1 ESP-SR 介绍

  • ESP-SR 是乐鑫官方推出的离线语音识别解决方案,一套专为乐鑫芯片(如 ESP32-S3)优化的智能语音框架,集成了从音频处理到命令识别所需的全套功能,无需连接云端即可在本地设备上实现语音控制
  • 我们需要使用 ESP-SR 来处理我们通过 I2S 接收到的音频数据,将其“翻译”成可执行的指令
  • 本项目使用它的典型运用之一“语音识别”,它的核心由以下三大支柱构成,协同完成离线语音识别的全过程

1.1.1 AFE (Audio Front End, 声学前端)

  • 角色:环境过滤器
  • 功能:处理原始音频信号,以提升下游任务的准确率。像回声消除(AEC)、噪声抑制(NS)、语音活动检测(VAD)等功能,都由它集中控制
  • 内置算法了以下算法,支持单独开启
算法名称 简介
AEC (Acoustic Echo Cancellation) 回声消除算法,最多支持双麦处理,能够有效的去除 mic 输入信号中的自身播放声音,从而可以在自身播放音乐的情况下很好的完成语音识别
NS (Noise Suppression) 噪声抑制算法,支持单通道处理,能够对单通道音频中的非人声噪声进行抑制,尤其针对稳态噪声,具有很好的抑制效果
BSS (Blind Source Separation) 盲信号分离算法,支持双通道处理,能够很好的将目标声源和其余干扰音进行盲源分离,从而提取出有用音频信号,保证了后级语音的质量
MISO (Multi Input Single Output) 多输入单输出算法,支持双通道输入,单通道输出。用于在双麦场景,没有唤醒使能的情况下,选择信噪比高的一路音频输出
VAD (Voice Activity Detection) 语音活动检测算法,支持实时输出当前帧的语音活动状态
AGC (Automatic Gain Control) 自动增益控制算法,可以动态调整输出音频的幅值,当弱信号输入时,放大输出幅度;当输入信号达到一定强度时,压缩输出幅度
  • 当然,这些算法都有各种版本可以选择,一般默认即可

1.1.2 WakeNet (唤醒词引擎)

  • 角色:触发开关的门卫
  • 功能:基于神经网络的唤醒词模型,专为低功耗嵌入式 MCU 设计,可以检测特定的唤醒词,例如“Hi, ESP”或“你好,小智”。只有当它被触发,后续的命令词识别才会开始,目的是降低功耗与减少误判
  • 内置了两种 WakeNet 模型可供选择(如果使用官方预制的唤醒词即可无需单独训练),如下:
名称 内容
WakeNet9s 只支持 4 种预制唤醒词:“Hi,乐鑫”“Hi,ESP”“你好小智”“Hi,Jason”
WakeNet9 比 9s 版本更丰富的预制唤醒词

1.1.3 MultiNet (命令词识别器)

  • 角色:理解指令的翻译官。
  • 功能:负责在唤醒后,识别具体的语音命令,如“开灯”、“关灯”等
  • 内置了两种命令识别模型,如下:
名称 内容
mn6_cn 通用中文识别模型
mn6_en 通用英语识别模型

1.1.4 数据在 ESP-SR 内的完整流动

博客园图片

1.2 配置 ESP-SR

1.2.1 配置 SDK 配置编辑器(menuconfig)

配置项 本项目配置 说明
Model data path Read model data from flash 模型从 Flash 分区“model”加载,而非从 SD 卡或网络
Afe interface v1 使用 AFE 接口版本 1(ESP-SR 2.x 默认)
Noise suppression model eep noise suppression v2 (nsnet2) 选择基于神经网络的 NS 降噪算法
Voice activity detection voice activity detection (WebRTC) 选择 WebRTC VAD 算法(而不是内置的 NN VAD)。
WakeNet model WakeNet9,"Hi.ESP" ( wn9_hiesp ) 唤醒词模型,决定可用的唤醒词短语。
Chinese Speech Commands Model general chinese recognition(mn6_cn) 命令词识别选择通用中文识别模型
English Speech Commands Model general english recognition(mn6_en) 命令词识别选择通用英文识别模型
  • 配置完成之后,系统自动将涉及模型打包进 model 分区

1.2.2 代码初始化配置

  • 代码如下:
/* ====================  内部宏 (以 _ 开头)  ==================== */
#define _SR_TAG "SRV_SR"
/* AFE 输入通道格式: "M" = 单麦克风 (INMP441), 无参考通道 */
#define _AFE_INPUT_FORMAT "M"
/* 唤醒后等待命令的超时时间 (毫秒) */
#define _CMD_MN_TIMEOUT_MS (8000)
/* 命令词总数 */
#define _CMD_NUM 6/* ====================  命令词拼音表  ==================== */static const char *_cmd_pinyin[_CMD_NUM] = {
    "kai deng",        /* 开灯       */
    "guan deng",          /* 关灯       */
    "kai ji dian qi yi",  /* 开继电器1   */
    "guan ji dian qi yi", /* 关继电器1   */
    "kai ji dian qi er",  /* 开继电器2   */
    "guan ji dian qi er", /* 关继电器2   */
};/* ====================  内部全局变量 (以 _ 开头)  ==================== */
/* ---- AFE (音频前端) ---- */
static const esp_afe_sr_iface_t *_afe_iface = NULL;
static esp_afe_sr_data_t *_afe_data = NULL;
/* ---- MultiNet (命令词识别) ---- */
static const esp_mn_iface_t *_mn_iface = NULL;
static model_iface_data_t *_mn_data = NULL;Ret_t Srv_SR_Init(uint32_t feedStack, UBaseType_t feedPrio,BaseType_t feedCore,
                  uint32_t detectStack, UBaseType_t detectPrio,BaseType_t detectCore)
{
    /* ----------------------------------------------------------
     *  步骤 1: 初始化 I2S (I2S 模式, INMP441)
     * ---------------------------------------------------------- */
    if (bsp_i2s_init() != RET_OK)
    {
        LOGE(_SR_TAG, "bsp_i2s_init failed");
        return RET_FAIL;
    }    /* ----------------------------------------------------------
     *  步骤 2: 从 flash 分区加载 SR 模型
     * ---------------------------------------------------------- */
    srmodel_list_t *models = esp_srmodel_init("model");
    if (models == NULL)
    {
        LOGE(_SR_TAG, "esp_srmodel_init(\"model\") failed, "
                      "check partition table and menuconfig");
        return RET_FAIL;
    }    /* ----------------------------------------------------------
     *  步骤 3: 查找 WakeNet 模型
     * ---------------------------------------------------------- */
    char *wn_name = esp_srmodel_filter(models, ESP_WN_PREFIX, NULL);
    if (wn_name == NULL)
    {
        LOGE(_SR_TAG, "No WakeNet model found in partition");
        return RET_FAIL;
    }
    LOGI(_SR_TAG, "Using WakeNet model: %s", wn_name);    /* ----------------------------------------------------------
     *  步骤 4: 查找 MultiNet 模型
     * ---------------------------------------------------------- */
    char *mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX, NULL);
    if (mn_name == NULL)
    {
        LOGE(_SR_TAG, "No MultiNet model found in partition");
        return RET_FAIL;
    }
    LOGI(_SR_TAG, "Using MultiNet model: %s", mn_name);    /* ----------------------------------------------------------
     *  步骤 5: 配置并创建 AFE 实例
     * ---------------------------------------------------------- */
    afe_config_t *afe_cfg = afe_config_init(
        _AFE_INPUT_FORMAT,
        models,
        AFE_TYPE_SR,
        AFE_MODE_HIGH_PERF);    if (afe_cfg == NULL)
    {
        return RET_FAIL;
    }    /* ---- 微调 AFE 配置 ---- */
    afe_cfg->aec_init = false;
    afe_cfg->vad_init = true;
    afe_cfg->vad_mode = VAD_MODE_2;
    afe_cfg->vad_min_speech_ms = 100;
    afe_cfg->wakenet_init = true;
    afe_cfg->wakenet_model_name = wn_name;
    afe_cfg->wakenet_mode = DET_MODE_95;
    afe_cfg->afe_linear_gain = 4.0f;
    afe_cfg->afe_ringbuf_size = 150; /* 加大缓冲区, 抗抖动 */    /* ---- AGC ---- */
    afe_cfg->agc_init = true;
    afe_cfg->agc_mode = AFE_AGC_MODE_WAKENET;
    afe_cfg->agc_compression_gain_db = 9;
    afe_cfg->agc_target_level_dbfs = -5;    /* ---- NS ---- */
    afe_cfg->ns_init = true;
    afe_cfg->afe_ns_mode = AFE_NS_MODE_NET;    /* ---- 获取 AFE 接口 ---- */
    _afe_iface = esp_afe_handle_from_config(afe_cfg);
    if (_afe_iface == NULL)
    {
        LOGE(_SR_TAG, "esp_afe_handle_from_config failed");
        afe_config_free(afe_cfg);
        return RET_FAIL;
    }    /* ---- 创建 AFE 实例 ---- */
    _afe_data = _afe_iface->create_from_config(afe_cfg);
    afe_config_free(afe_cfg);
    if (_afe_data == NULL)
    {
        LOGE(_SR_TAG, "afe_handle->create_from_config failed");
        return RET_FAIL;
    }    /* ----------------------------------------------------------
     *  步骤 6: 初始化 MultiNet 引擎
     * ---------------------------------------------------------- */
    /* ---- 获取 MultiNet 接口 ---- */
    _mn_iface = (const esp_mn_iface_t *)esp_mn_handle_from_name(mn_name);
    if (_mn_iface == NULL)
    {
        LOGE(_SR_TAG, "esp_mn_handle_from_name(%s) failed", mn_name);
        return RET_FAIL;
    }    /* ---- 创建 MultiNet 实例 ---- */
    _mn_data = _mn_iface->create(mn_name, _CMD_MN_TIMEOUT_MS);
    if (_mn_data == NULL)
    {
        LOGE(_SR_TAG, "multinet->create failed");
        return RET_FAIL;
    }    /* ---- 注册命令词 ---- */
    esp_mn_commands_alloc(_mn_iface, _mn_data);
    for (int i = 0; i < _CMD_NUM; i++)
    {
        esp_err_t ret = esp_mn_commands_add(i, _cmd_pinyin[i]);
        if (ret != ESP_OK)
        {
            LOGW(_SR_TAG, "Add cmd %d (%s) failed: %d",
                 i, _cmd_pinyin[i], ret);
        }
    }    /* ---- 更新语言模型 ---- */
    esp_mn_error_t *err = esp_mn_commands_update();
    if (err != NULL)
    {
        LOGW(_SR_TAG, "%d command(s) could not be parsed by multiNet",
             err->num);
    }    return RET_OK;
}
  • ESP-SR 开启了以下功能:
    • 语音活动检测(VAD):模式 2、最小语音时长 100ms
    • 唤醒词引擎(WakeNet):检测率 95% 模式
    • 自动增益控制(AGC):仅在唤醒后生效,目标电平 -5dBFS、最大增益 9dB
    • 噪声抑制(NS):采用神经网络模式
    • 命令词识别(MultiNet)
  • AEC(回声消除)则被关闭
  • 此外,通过 MultiNet 动态添加了 6 条拼音命令词

1.3 ESP-SR 的运行

博客园图片
  • 使用双任务进行分工
    • sr_feed_task:仅负责音频数据采集,1ms 周期循环
    • sr_detect_task:处理AFE结果和状态机“唤醒->识别->执行回调”,1ms 周期循环
  • 为什么不能用一个任务?
    • feed() 操作必须严格遵循采样率时序(例如每 feed_chunksize 个样本就需要调用一次)
    • 如果混在一起,一次 fetch() 可能会因为等待神经网络推理而耗时过长,导致错过下一次喂数据的时机,最终使 AFE 内部的环形缓冲区溢出

简单来说,feed() 是生产者,fetch() 是消费者,两者需要不同的节奏,所以必须分任务

  • 一个需要分清的点:

    • ESP-SR 本质上是一个在 CPU 上运行的算法库
    • 它不像一个线程那样自己就能运行,而是必须由你的代码通过 FreeRTOS 任务来调用它的 API,才能驱动它在 CPU 上进行数据处理
  • 相关代码如下:

static TaskHandle_t _feed_task_handle = NULL;
static TaskHandle_t _detect_task_handle = NULL;/* ====================  内部宏 (以 _ 开头)  ==================== */
/* feed 任务中每隔多少轮打印一次 chunksize (≈每1秒) */
#define _FEED_PRINT_INTERVAL (1000)/* ====================  内部函数声明  ==================== */static void _srv_sr_feed_task(void *pvParam);static void _srv_sr_detect_task(void *pvParam);static void _srv_sr_handle_cmd(int cmd_id);Ret_t Srv_SR_Init(uint32_t feedStack, UBaseType_t feedPrio,BaseType_t feedCore,                  uint32_t detectStack, UBaseType_t detectPrio,BaseType_t detectCore){// 初始化ESP-SR 逻辑..../* ----------------------------------------------------------
     *  步骤 7: 创建双任务 (参考正点原子成功案例)
     * ---------------------------------------------------------- */
    /* ---  创建 SR_Feed 任务 (core 1) --- */
    BaseType_t ret_val = xTaskCreatePinnedToCore(
        _srv_sr_feed_task,  /* 任务函数   */
        "SR_Feed",          /* 任务名称   */
        feedStack,          /* 栈深度     */
        _afe_data,          /* 参数: AFE 实例 */
        feedPrio,           /* 优先级     */
        &_feed_task_handle, /* 传出句柄   */
        1                   /* 固定 core 1 (APP CPU) */
    );    if (ret_val != pdPASS)
    {
        LOGE(_SR_TAG, "Failed to create SR_Feed task");
        _afe_iface->destroy(_afe_data);
        _mn_iface->destroy(_mn_data);
        return RET_FAIL;
    }    /* --- 创建 SR_Detect 任务 (core 0) --- */
    ret_val = xTaskCreatePinnedToCore(
        _srv_sr_detect_task,  /* 任务函数   */
        "SR_Detect",          /* 任务名称   */
        detectStack,          /* 栈深度     */
        _afe_data,            /* 参数: AFE 实例 */
        detectPrio,           /* 优先级     */
        &_detect_task_handle, /* 传出句柄   */
        0                     /* 固定 core 0 (PRO CPU) */
    );    if (ret_val != pdPASS)
    {
        LOGE(_SR_TAG, "Failed to create SR_Detect task");
        vTaskDelete(_feed_task_handle);
        _feed_task_handle = NULL;
        _afe_iface->destroy(_afe_data);
        _mn_iface->destroy(_mn_data);
        return RET_FAIL;
    }
    
    return RET_OK;
}/* ================================================================
 *  公共 API — 注册回调
 * ================================================================ */Ret_t Srv_SR_Register_Callback(Srv_SR_Callback_t cb, void *user_data)
{
    if (cb == NULL)
    {
        LOGE(_SR_TAG, "Register_Callback: cb is NULL");
        return RET_ERR;
    }
    _callback = cb;
    _callback_userdata = user_data;
    return RET_OK;
}/* ================================================================
 *  内部 — Feed 任务
 * ================================================================ */
static void _srv_sr_feed_task(void *pvParam)
{
    esp_afe_sr_data_t *afe_data = (esp_afe_sr_data_t *)pvParam;    /* ---- 获取 AFE 输入参数 ---- */
    int feed_chunksize = _afe_iface->get_feed_chunksize(afe_data);
    int feed_channels = _afe_iface->get_feed_channel_num(afe_data);
    size_t buf_len = (size_t)feed_chunksize * (size_t)feed_channels;    LOGI(_SR_TAG, "[feed] chunk=%d samples, channels=%d",
         feed_chunksize, feed_channels);    /* ---- 分配音频缓冲区 ---- */
    int16_t *audio_buf = heap_caps_malloc(
        buf_len * sizeof(int16_t),
        MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
    if (audio_buf == NULL)
    {
        LOGE(_SR_TAG, "Feed task: OOM for audio buffer");
        vTaskDelete(NULL);
    }    /* ---- 循环: 读 I2S → feed AFE ---- */
    int print_cnt = 0;    while (1)
    {
        /* 从 I2S 读取 feed_chunksize 个样本 (阻塞直到收满) */
        int n = bsp_i2s_read(audio_buf, (int)buf_len, portMAX_DELAY);
        if (n <= 0)
        {
            vTaskDelay(pdMS_TO_TICKS(1));
            continue;
        }        /* 喂给 AFE (入环形缓冲区, 非阻塞) */
        _afe_iface->feed(afe_data, audio_buf);        /* 每隔约1秒打印一次 chunksize 和前4个PCM样本(验证麦克风是否工作) */
        if (++print_cnt >= _FEED_PRINT_INTERVAL)
        {
            LOGI(_SR_TAG, "[feed] chunk=%d, ch=%d, pcm[0..3]=%d %d %d %d",
                 feed_chunksize, feed_channels,
                 audio_buf[0], audio_buf[1], audio_buf[2], audio_buf[3]);
            print_cnt = 0;
        }        /* 让出 CPU, 防止 IDLE 任务饿死 (与参考资料一致) */
        vTaskDelay(pdMS_TO_TICKS(1));
    }
}/* ================================================================
 *  内部 — Detect 任务
 * ================================================================ */
static void _srv_sr_detect_task(void *pvParam)
{
    esp_afe_sr_data_t *afe_data = (esp_afe_sr_data_t *)pvParam;    int mn_chunksize = _mn_iface->get_samp_chunksize(_mn_data);
    int16_t *mn_buf = heap_caps_malloc(mn_chunksize * sizeof(int16_t),
                                       MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
    if (mn_buf == NULL)
    {
        LOGE(_SR_TAG, "Detect task: OOM for multinet buffer");
        vTaskDelete(NULL);
    }    bool in_cmd = false;
    int mn_cnt = 0;
    TickType_t cmd_start_tick = 0;    // 能量阈值(可根据实际环境调整,当前是一帧内绝对值和 < 5000 视为静音)
    const int32_t MIN_ENERGY_PER_FRAME = 5000;    LOGI(_SR_TAG, "Listening for \"Hi, ESP\"...");    while (1)
    {
        afe_fetch_result_t *res = _afe_iface->fetch(afe_data);
        if (res == NULL || res->ret_value == ESP_FAIL)
        {
            vTaskDelay(pdMS_TO_TICKS(1));
            continue;
        }        // ---------- 阶段1:唤醒监听 ----------
        if (!in_cmd)
        {
            if (res->wakeup_state == WAKENET_DETECTED)
            {
                /* ---- 系统处于 IDLE 模式时忽略唤醒 ---- */
                if (Mdl_Flag_GetSystem() == 0)
                {
                    LOGI(_SR_TAG, "System in IDLE, wakeup ignored");
                    continue;
                }                LOGI(_SR_TAG, "\033[1;32m\"Hi, ESP\" detected!\033[0m");                Mdl_Flag_SetSRWakeup(1);                _afe_iface->disable_wakenet(afe_data);
                _mn_iface->clean(_mn_data);
                in_cmd = true;
                mn_cnt = 0;
                cmd_start_tick = xTaskGetTickCount(); // 记录起始时间
            }
        }        // ---------- 阶段2:命令识别 ----------
        else
        {
            // 整体超时检查(基于系统时钟)
            if ((xTaskGetTickCount() - cmd_start_tick) >= pdMS_TO_TICKS(_CMD_MN_TIMEOUT_MS))
            {
                LOGI(_SR_TAG, "[CMD] Overall timeout (%dms)", _CMD_MN_TIMEOUT_MS);
                Mdl_Flag_SetSRWakeup(0);
                _afe_iface->reset_buffer(afe_data);
                _afe_iface->enable_wakenet(afe_data);
                in_cmd = false;
                continue;
            }            if (res->data_size > 0)
            {
                int samples = (int)(res->data_size / sizeof(int16_t));
                int16_t *data = (int16_t *)res->data;                // ---- 能量过滤:丢弃低能量帧 ----
                int32_t energy = 0;
                for (int i = 0; i < samples; i++)
                {
                    energy += abs(data[i]);
                }                if (energy < MIN_ENERGY_PER_FRAME)
                {
                    // 静音帧直接跳过,不累积(避免噪声触发)
                    vTaskDelay(pdMS_TO_TICKS(1));
                    continue;
                }                int can_copy = (samples < mn_chunksize - mn_cnt) ? samples : (mn_chunksize - mn_cnt);
                memcpy(mn_buf + mn_cnt, data, can_copy * sizeof(int16_t));
                mn_cnt += can_copy;
            }
            
            // 凑满一帧,进行检测
            if (mn_cnt >= mn_chunksize)
            {
                esp_mn_state_t mn_state = _mn_iface->detect(_mn_data, mn_buf);                if (mn_state == ESP_MN_STATE_DETECTED)
                {
                    esp_mn_results_t *result = _mn_iface->get_results(_mn_data);
                    if (result && result->num > 0)
                    {
                        LOGI(_SR_TAG, "[CMD] Detected! id=%d, phrase=%s",
                             result->command_id[0], result->string);
                        _srv_sr_handle_cmd(result->command_id[0]);
                    }
                    // 检测到命令,退出命令模式
                    _afe_iface->reset_buffer(afe_data);
                    _afe_iface->enable_wakenet(afe_data);
                    in_cmd = false;
                    continue;
                }
                else if (mn_state == ESP_MN_STATE_TIMEOUT)
                {
                    LOGI(_SR_TAG, "[CMD] MultiNet timeout");
                    Mdl_Flag_SetSRWakeup(0);
                    _afe_iface->reset_buffer(afe_data);
                    _afe_iface->enable_wakenet(afe_data);
                    in_cmd = false;
                    continue;
                }                // 保留多余数据,防止帧错位
                int remaining = mn_cnt - mn_chunksize;
                if (remaining > 0)
                {
                    memmove(mn_buf, mn_buf + mn_chunksize, remaining * sizeof(int16_t));
                }
                mn_cnt = remaining;
            }
        }        vTaskDelay(pdMS_TO_TICKS(1));
    }
}/* ================================================================
 *  内部 — 命令词分发
 * ================================================================ */
static void _srv_sr_handle_cmd(int cmd_id)
{
    Srv_SR_Cmd_t cmd;    switch (cmd_id)
    {
    case 0:
        cmd = SR_CMD_LIGHT_ON;
        break;
    case 1:
        cmd = SR_CMD_LIGHT_OFF;
        break;
    case 2:
        cmd = SR_CMD_RELAY1_ON;
        break;
    case 3:
        cmd = SR_CMD_RELAY1_OFF;
        break;
    case 4:
        cmd = SR_CMD_RELAY2_ON;
        break;
    case 5:
        cmd = SR_CMD_RELAY2_OFF;
        break;
    default:
        cmd = SR_CMD_NONE;
        break;
    }    if (cmd == SR_CMD_NONE)
    {
        LOGW(_SR_TAG, "Unknown command ID: %d", cmd_id);
        return;
    }    if (_callback != NULL)
    {
        /* ---- 系统处于 IDLE 模式时不执行命令 ---- */
        if (Mdl_Flag_GetSystem() == 0)
        {
            LOGW(_SR_TAG, "System in IDLE, cmd=%d dropped", cmd_id);
            /* 清除唤醒标志,让下次能重新唤醒 */
            Mdl_Flag_SetSRWakeup(0);
            return;
        }        Mdl_Flag_SetSRCmd(1);
        _callback(cmd, _callback_userdata);
    }
    else
    {
        LOGW(_SR_TAG, "No callback registered, cmd=%d dropped", cmd_id);
    }
}

综上,V3 的语音识别虽然功能完整,但双任务的高频轮询对系统实时性造成了较大压力,V4 需对此优化


2. V4 优化方向

  • 目前的双 MCU 存在两个风险:
    • 数据过载与任务饥饿:如果 sr_detect_task 因优先级过低或处理延迟,fetch() 任务处理速度可能跟不上 feed() 任务的投喂速度,会导致AFE的环形缓冲区溢出引发唤醒成功率下降、命令识别失灵等严重问题
    • 任务执行过于频繁:在 FreeRTOS 中,1ms 周期意味着任务每 1ms 运行一次。对于语音识别来说,这个频率过于激进,而且这两个任务在整体项目中并非最低优先级,容易导致低优先级任务被饿死
  • V4 应该调整任务周期或者优先级,降低 sr_detect_task 和 sr_feed_task 对于其它任务运行的影响
  • 例如,将 sr_feed_task 和 sr_detect_task 的周期从 1ms 调整为 10ms 或更长(根据 feed_chunksize 计算),并适当降低这两个任务的优先级,避免阻塞其他任务
  • 同时考虑使用任务通知或队列来同步数据,减少轮询开销
http://www.jsqmd.com/news/1019866/

相关文章:

  • 3分钟搞定FF14国际服汉化:开源工具FFXIVChnTextPatch深度解析
  • 免费的文字转配音工具推荐?2026司马去水印永久免费AI配音全面实测 - 科技大爆炸
  • 05 逻辑斯蒂回归(Logistic Regression)
  • B站视频怎么无水印保存?2026司马去水印免费下载B站视频到手机相册教程 - 科技大爆炸
  • Next.js App Router 实践:从页面路由到服务端组件,现代 Web 应用的架构演进
  • 20252919 2025-2026-2 《网络攻防实践》第十一次作业
  • MSC8251多核DSP引导程序与系统配置实战指南
  • 如何零配置部署Kimi AI免费API:解锁长文本处理与多模态对话能力
  • LabVIEW文件读写报错8?别慌,这5个常见原因和修复方法帮你搞定
  • 2026年6月全国APP开发公司综合实力排名 - IT老炮老刘
  • PXD10 ADC中断与DMA配置详解:从寄存器到实战应用
  • 别再到处找破解版了!手把手教你用Docker在Kali Linux上部署AWVS 14(附官方试用版获取指南)
  • 2026年全国铝板带材核心供应商评测:五大源头工厂实力与采购适配指南 - 互联网科技品牌测评
  • 2026论文隐藏级降AIGC软件大曝光:一键改写直达人工原创!
  • 如何快速掌握UEFITool:3步完成BIOS固件深度解析
  • MPC866 PCMCIA接口详解:从硬件信号到驱动开发的嵌入式系统扩展实践
  • 2026 AI简历优化平台怎么选:5款工具实测 + ATS/JD匹配“算法逻辑”拆解(首推鹅来面)
  • 2026年6月超声波流量计品牌好评榜:国产头部品牌技术突围与市场口碑全景分析 - 水质仪表品牌排行榜
  • QT连接达梦数据库DM8,为什么我总卡在UnixODBC这一步?
  • 华为eNSP模拟器BGP排错实战:这10个display命令帮你快速定位网络邻居和路由问题
  • 小红书视频怎么无水印保存?2026司马去水印免费下载小红书视频到手机相册教程 - 科技大爆炸
  • VLC播放器终极美化指南:5款VeLoCity皮肤让你的影音体验飙升500%
  • 6/15
  • Label Studio开源数据标注工具完全指南:多模态AI训练数据标注解决方案
  • 不损坏原画质的视频去字幕方法有哪些?2026司马去水印高清去字幕方案 - 科技大爆炸
  • 2026年6月乐清黄金回收市场深度调查:三家诚信商家排名与避坑指南 - 钦扬网络
  • 蒙特卡洛离策略强化学习:工业级落地实战指南
  • WorkshopDL:跨平台Steam创意工坊模组下载的技术实现方案
  • SPI通信协议与DSPI高级特性:从基础原理到工程实践
  • 给烽火HG680-MC盒子‘瘦身’并解锁:刷入当贝桌面纯净版,告别运营商限制