小智:自定义唤醒词(基于multinet)

小智:自定义唤醒词(基于multinet)

前言:

此方法主要是使用乐鑫官方文档中提到的multinet模型进行离线命令词识别从而达到自定义唤醒词的效果 资源消耗会高很多。

乐鑫集成了WakeNet和MultiNet两个技术模块,目前开源代码使用的是WakeNet语音唤醒引擎,其好处是内存占比小(约20KB左右),计算速度快。 而本文中用到的是MultiNet轻量级模型,内存占用可能会WakeNet高好几倍,比其设计借鉴了卷积递归神经网络(CRNN)和连接主义时间分类(CTC),它可将音频的MFCC(Mel-Frequency Cepstral Coefficients)特征值处理为对应汉字或单词的组合音素,然后通过对比输出音素找到对应的语音命令。

本文不太适合小白,要有一点编程基础,其他基础配置小智官方文档里有

基于1.7.3版本源码修改(不同版本源码略有区别,但实现方式是一样的)

目前只支持汉语拼音,不支持汉语+英文(需要换其他库);

大佬们看个思路就行了

第一步:配置中加入汉语识别model

第二步:给model增加分区空间

根据自己芯片的flash大小修改对应的分区表(本文以16MB为例)

该model较大(2.3MB左右),原本默认的model分区不太够(具体多少够我也没试过,大佬们可以试一下)

通过减少ota_1的空间,分配给model,注意改偏移地址

第三步:在唤醒词初始化代码中加入multinet

找到这个函数 ,在其原本的模型加载和检测中加入一个新的模型加载和检测

void AfeWakeWord::Initialize(AudioCodec *codec)

{

codec_ = codec;

int ref_num = codec_->input_reference() ? 1 : 0;

srmodel_list_t *models = esp_srmodel_init("model"); // 初始化语音识别模型列表,从"model"目录加载

// 遍历所有加载的模型

for (int i = 0; i < models->num; i++)

{

ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]); // 打印每个模型的名称

if (strstr(models->model_name[i], ESP_MN_PREFIX) != NULL) //此处 ESP_MN_PREFIX 为 "mn" 也就是新加的模型库

{

// 获取 multinet 模型句柄

multinet = esp_mn_handle_from_name(models->model_name[i]);

if (!multinet)

{

ESP_LOGI("MULTINET", "failed to create multinet handle");

continue;

}

//加载模型数据

model_data = multinet->create(models->model_name[i], 6000);

if (!model_data)

{

ESP_LOGI("MULTINET", "failed to create model_data handle");

continue;

}

}

if (model_data)

{

//此处使用拼音+空格的方式添加想要自定义的唤醒词

esp_mn_commands_clear();

esp_mn_commands_add(1, "ni hao xiao nuo");

esp_mn_commands_add(2, "xiao nuo xiao nuo");

esp_mn_commands_add(3, "xiao nuo");

esp_mn_commands_update();

}

esp_mn_active_commands_print(); //打印添加的唤醒词

if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL)

{

wakenet_model_ = models->model_name[i];

auto words = esp_srmodel_get_wake_words(models, wakenet_model_);

// split by ";" to get all wake words

std::stringstream ss(words);

std::string word;

while (std::getline(ss, word, ';'))

{

wake_words_.push_back(word);

}

}

}

std::string input_format;

for (int i = 0; i < codec_->input_channels() - ref_num; i++)

{

input_format.push_back('M');

}

for (int i = 0; i < ref_num; i++)

{

input_format.push_back('R');

}

afe_config_t *afe_config = afe_config_init(input_format.c_str(), models, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);

afe_config->aec_init = codec_->input_reference();

afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;

afe_config->afe_perferred_core = 1;

afe_config->afe_perferred_priority = 1;

afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;

afe_iface_ = esp_afe_handle_from_config(afe_config);

afe_data_ = afe_iface_->create_from_config(afe_config);

xTaskCreate([](void *arg)

{

auto this_ = (AfeWakeWord*)arg;

this_->AudioDetectionTask();

vTaskDelete(NULL); }, "audio_detection", 4096, this, 3, nullptr);

//新增一个任务

xTaskCreate([](void *arg)

{

auto this_ = (AfeWakeWord*)arg;

this_->CommandDetectionTask();

vTaskDelete(NULL); }, "command_detection", 4096, this, 5, &command_detection_task_);

}

TaskHandle_t command_detection_task_ = nullptr; //新增任务中的定义 我定义为了全局变量

esp_mn_iface_t *multinet; //全部定义的全局变量

model_iface_data_t *model_data;

第四步:实现新增任务的函数

void AfeWakeWord::CommandDetectionTask()

{

while (true)

{

ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //阻塞 收到通知后再跑

esp_mn_state_t mn_state;

esp_mn_results_t *mn_result;

if (model_data && res->data) //此处res使用的是原本唤醒AudioDetectionTask任务中的res,将其改为全局变量,记得一定要共用一个, afe_fetch_result_t *res; !

{

mn_state = multinet->detect(model_data, res->data);

//判断是否检测到唤醒词

if (mn_state == ESP_MN_STATE_DETECTING)

{

continue;

}

else if (mn_state == ESP_MN_STATE_DETECTED)

{

mn_result = multinet->get_results(model_data);

//此处id就是你添加唤醒词的id 注意 第一个是0 了,在此处自己可以写个回调函数任意处理 也可以控制其他设备 也可以当作唤醒词处理

if (mn_result != nullptr && mn_result->num > 0)

{

int command_id = mn_result->phrase_id[0];

if (command_id == 0)

{

Application::GetInstance().WakeWordInvoke("你好小诺");

}

else if (command_id == 1)

{

Application::GetInstance().WakeWordInvoke("小诺小诺");

}

else if (command_id == 2)

{

Application::GetInstance().WakeWordInvoke("山东华尔诺");

}

}

}

}

}

}

第五步:增加检测到有效音频数据通知处理任务

找到该函数

除了第四步注释中提到的将该函数中的res改为全局变量外,新增一个通知功能

// 通知

else if (res->data)

{

if (model_data)

{

xTaskNotifyGive(command_detection_task_);

}

}

第六步:补充头文件

这是我用到的头文件

至此 结束 编译烧录就可以了

大佬们有优化和创新 记得让我学习学习