智能语音应用开发指南 | 《无需从0开发 1天上手智能语音离在线方案》第四章

简介: 本章介绍 智能语音应用开发指南

上一章:智能语音终端SDK快速上手说明 | 《无需从0开发 1天上手智能语音离在线方案》第三章>>>
下一章:智能语音终端开发板适配指南 | 《无需从0开发 1天上手智能语音离在线方案》第五章 >>>

1. 概述

本章介绍智能语音终端SDK的软件开发方法。
名词解释
下面介绍本文中涉及的一些专有名词:
• PCM:脉冲编码调制,这里专指未经过编码的语音数据。直接从麦克风采集出来的语音即为PCM数据。
• KWS:关键词识别,识别特定的几个词语。在我们方案中,该关键词为“宝拉宝拉”,该过程在设备端实现。
• ASR:语音识别,将声音转化为文字的过程。该过程在云端完成。
• NLP:自然语言处理,将文字转化成语义的过程。该过程在云端完成。
• TTS:文本转语音,将文字转换成语音数据。该过程在云端完成。
SDK目录介绍
下面是智能语音SDK的目录结构,表格中介绍了各个目录的功能。

image.png

2. 核心组件介绍

本章介绍播放器组件、语音组件、云服务组件等核心组件。
• 播放器提供了语音播放功能,支持PCM、WAV、MP3等编码格式,可以实现内存音频、SD卡音频、在线音频等多种音频流播放。支持音频的播放、停止、暂停、继续、音量控制等多种音频控制命令。通过适配对应的声卡播放通路,可以灵活得实现多种播放场景。
• 语音组件提供了语音唤醒及麦克风音频采集功能,可以将唤醒事件、VAD事件、采集到的音频流等数据和事件实时传送给调用方,调用方结合云服务组件和播放器组件可以实现多种智能语音服务。
• 云服务组件提供了云端一体化的语音服务,封装了端侧和云侧的交互过程,开发者只需简单调用对应API、处理回调函数,即可方便的获得云端ASR/NLP识别结果和TTS合成服务。
• 配网服务组件提供了配网框架及多种配网模块,封装了统一的应用接口,开发者无需关心配网实现,即可通过统一接口获取多种配网服务。
• 闹铃服务组件提供了闹铃服务,不论系统处于运行状态、低功耗状态还是待机休眠状态,都可准时产生闹铃回调。
• 按键服务组件提供GPIO高低电平、上升/下降沿等多种类型的按键扫描功能。开发者设置对应的按键参数、回调函数,按键服务会自动扫描并触发对应按键事件。

2.1 播放器服务

2.1.1 功能介绍

播放器服务组件支持播放、停止、音量等通用控制功能外,还支持常用的最小音量控制、音乐打断恢复及音乐渐变切换功能。支持PCM、WAV、MP3等编码格式。

应用示例中实现了如下播放器服务功能:
• 通知音播放。
• 在线音频播放
• 云端合成TTS流播放
• SD卡音频播放

播放器服务组件的主要API如下:

image.png

2.1.2 代码示例

初始化

#include <aos/aos.h>
#include <media.h>

void app_player_init()
{
    /*创建任务*/
    utask_t *task_media = utask_new("task_media", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);
    /*初始化*/
    ret = aui_player_init(task_media, media_evt);
}

事件回调函数
在aui_player_init初始化时传入了播放器服务回调函数,之后所有的播放器事件都会通过回调函数通知给用户。
为方便应用开发,播放器中支持了两种播放类型,通知类型和音乐类型。通知可以打断音乐的播放,通知结束后,可以设置音乐是否自动恢复播放。回调函数中有播放类型和事件ID,用户可根据需要在事件中增加应用功能。
播放器事件如下表:

image.png

播放器类型如下表:

image.png

#include <media.h>

static void media_evt(int type, aui_player_evtid_t evt_id)
{
    /*播放音乐的事件处理*/
    switch (evt_id) {
        case AUI_PLAYER_EVENT_START:
            break;
        case AUI_PLAYER_EVENT_ERROR:
            break;
        case AUI_PLAYER_EVENT_FINISH:
            break;
        default:
            break;
    }
}

播放网络音乐

aui_player_play(MEDIA_MUSIC, "http://test_url/AudioTest1.mp3/", 1);

播放SD卡中的音频

aui_player_play(MEDIA_MUSIC, "file:///fatfs/1.mp3", 1);

播放FIFO中音频

aui_player_play(MEDIA_MUSIC, "fifo://test1", 1);

调节音量

/*音量降低10*/
aui_player_vol_adjust(MEDIA_ALL, -10)
/*音量增加10*/
aui_player_vol_adjust(MEDIA_ALL, 10)
/*音量设置到50*/
aui_player_vol_set(MEDIA_ALL, 50);

2.2 语音服务

2.2.1 功能介绍

语音服务组件提供关键词识别和语音数据的处理控制。输入麦克风的语音数据经过回音消除、降噪和关键词识别处理后再输出到应用层使用。
语音服务组件的主要API如下:

image.png

2.2.2 代码示例

初始化
语音服务在一个独立的任务中运行。需要先创建一个任务,然后把任务句柄和回调函数传入初始化函数。

static int app_mic_init(int wwwv_enable)
{
    int ret;
    static voice_adpator_param_t voice_param;
    static mic_param_t param;

    /* 注册麦克风驱动 */
    voice_mic_register();

    /* 创建语音服务线程 */
    utask_t *task_mic = utask_new("task_mic", 3 * 1024, 20, AOS_DEFAULT_APP_PRI);
    ret               = aui_mic_start(task_mic, mic_evt_cb);

    memset(&param, 0, sizeof(param));

    param.channels          = 5; /* 麦克风通道数 */
    param.sample_bits       = 16; /* 比特数 */
    param.rate              = 16000; /* 采样率 */
    param.sentence_time_ms = 600;
    param.noack_time_ms    = 5000;
    param.max_time_ms      = 10000;
    param.nsmode           = 0; /* 无非线性处理 */
    param.aecmode          = 0; /* 无非线性处理 */
    param.vadmode          = 0; /* 使能VAD */
    param.vadswitch        = 1;
    param.vadfilter        = 2;
    param.vadkws_strategy  = 0;
    param.vadthresh_kws.vad_thresh_sp = -0.6;
    param.vadthresh_kws.vad_thresh_ep = -0.6;
    param.vadthresh_asr.vad_thresh_sp = -0.6;
    param.vadthresh_asr.vad_thresh_ep = -0.6;
    param.wwv_enable        = wwwv_enable; /* 二次唤醒使能配置 */

    voice_param.pcm         = "pcmC0";
    voice_param.cts_ms      = 20;
    voice_param.ipc_mode    = 1;
    voice_param.ai_param    = &param;
    voice_param.ai_param_len = sizeof(mic_param_t);
    param.ext_param1        = &voice_param;

    /* 配置语音服务参数 */
    aui_mic_set_param(&param);

    if (wwwv_enable) {
        /* 二次唤醒使能初始化 */
        app_aui_wwv_init();
    }

    return ret;
}

事件回调函数
在app_mic_init初始化时传入了语音服务回调函数,之后使用语音与设备交互时,所有的语音事件都会通过回调函数通知给用户。
语音事件如下表:

image.png

下例程介绍了事件回调函数的典型实现:
• 检测到唤醒词后会首先收到唤醒事件MIC_EVENT_SESSION_START,调用函数aui_mic_control(MIC_CTRL_START_PCM)打开语音数据接收。
• 之后程序会持续接收到MIC_EVENT_PCM_DATA事件,获取到回音消除后的语音数据。将此音频通过云服务接口推送到云端,用于ASR/NLP识别。
• 当断句事件MIC_EVENT_SESSION_STOP发生时,调用函数aui_mic_control(MIC_CTRL_STOP_PCM)停止语音数据接收。

#include <yoc/mic.h>

static void mic_evt_cb(int source, mic_event_id_t evt_id, void *data, int size)
{
    switch (evt_id) {
        case MIC_EVENT_SESSION_START:
            /* 关键词唤醒,开始和云服务交互, 打开语音数据接收 */
            ret = app_aui_cloud_start(do_wwv);
            aui_mic_control(MIC_CTRL_START_PCM);
            break;
        case MIC_EVENT_PCM_DATA:
            /* 回音消除后的语音数据,推送到云端 */
            app_aui_cloud_push_audio(data, size);
            break;
        case MIC_EVENT_VAD:
            /* 检测到有效声音数据,可用于低功耗唤醒 */
            break;
        case MIC_EVENT_SESSION_STOP:
            /* 断句,停止和云服务交互,关闭语音数据接收 */
            app_aui_cloud_stop(1);
            aui_mic_control(MIC_CTRL_STOP_PCM);
            break;
        case MIC_EVENT_KWS_DATA:
            /* 接收到唤醒词数据 */
            break;
        default:;
    }
}

使能关键词检测
使用aui_mic_set_wake_enable开启和关闭关键词检测。例如使用按键开始语音交互时,就通过调用该函数关闭关键词检测。

if (1 == asr_en) {
    /* 使能关键词检测 */
    aui_mic_set_wake_enable(1);
} else {
    /* 关闭关键词检测 */
    aui_mic_set_wake_enable(0);
}

2.3 云服务

2.3.1 功能介绍

云服务组件提供应用与云端ASR/NLP/TTS服务交互的接口。调用对应服务API后,组件自动完成云端连接、鉴权、启动服务的过程,用户只需通过接口将需识别的音频或需合成的字符串传入,即可获得云端返回结果,设备端只需根据结果完成预定的应用行为。
云服务组件的主要API如下:

image.png

image.png

API调用流程图

image.png

2.3.2 代码示例

初始化
程序初始化时,需使用初始化函数aui_cloud_init初始化云服务,并设定对应参数。

#include <yoc/aui_cloud.h>

static aui_t        g_aui_handler;
int app_aui_nlp_init()
{
    /* 添加账号 */
    cJSON *js_account_info = NULL;
    cJSON_AddStringToObject(js_account_info, "device_uuid", device_uuid);
    cJSON_AddStringToObject(js_account_info, "asr_app_key", asr_app_key);
    cJSON_AddStringToObject(js_account_info, "asr_token", asr_token);
    cJSON_AddStringToObject(js_account_info, "asr_url", asr_url);
    cJSON_AddStringToObject(js_account_info, "tts_app_key", tts_app_key);
    cJSON_AddStringToObject(js_account_info, "tts_token", tts_token);
    cJSON_AddStringToObject(js_account_info, "tts_url", tts_url);
    
    aui_config_t cfg;
    cfg.per             = "aixia";
    cfg.vol             = 100;      /* 音量 0~100 */
    cfg.spd             = 0;        /* -500 ~ 500*/
    cfg.pit             = 0;        /* 音调*/
    cfg.fmt             = 2;        /* 编码格式,1:PCM 2:MP3 */
    cfg.srate           = 16000;    /* 采样率,16000 */
    cfg.tts_cache_path  = NULL;     /* TTS内部缓存路径,NULL:关闭缓存功能 */
    cfg.cloud_vad       = 1;        /* 云端VAD功能使能, 0:关闭;1:打开 */
    cfg.js_account      = s_account_info;
    cfg.nlp_cb          = aui_nlp_cb;
    g_aui_handler.config  = cfg;

    aui_asr_register_mit(&g_aui_handler);
    aui_tts_register_mit(&g_aui_handler);

    ret = aui_cloud_init(&g_aui_handler);
    aui_nlp_process_add(&g_aui_nlp_process, aui_nlp_proc_mit);
}

语音数据推送
在进行ASR识别时,需推送语音数据到云端。语音数据的推送流程主要在语音服务回调中处理。启动、推送、停止分别在语音服务事件MIC_EVENT_SESSION_START、MIC_EVENT_PCM_DATA、MIC_EVENT_SESSION_STOP中控制。具体代码已经在2.2.2 代码示例的“事件回调函数”一节有过介绍。

事件回调函数
云端下发的ASR和NLP结果通过aui_nlp_cb返回给用户。

#include <yoc/aui_coud.h>

/* 处理云端反馈的 ASR/NLP 数据,进行解析处理 */
static void aui_nlp_cb(const char *json_text)
{
    /* 处理的主入口, 具体处理见初始化注册的处理函数 */
    int ret = aui_nlp_process_run(&g_aui_nlp_process, json_text);
    switch (ret) {
        case AUI_CMD_PROC_ERROR:
            /* 没听清楚 */
            ...
            break;
        case AUI_CMD_PROC_NOMATCH:
            /* 不懂 */
            ...
            break;
        case AUI_CMD_PROC_MATCH_NOACTION:
            /* 不懂 */
            ...
            break;
        case AUI_CMD_PROC_NET_ABNORMAL:
            /* 网络问题 */
            ...
            break;
        default:;
    }
}

请求TTS服务
应用解析云端下发数据得到TTS文本之后,需要向云端请求TTS播放服务。代码段如下:

/* TTS回调函数 */
static void aui_tts_stat_cb(aui_tts_state_e stat)
{
    switch(stat) {
        case AUI_TTS_INIT:
            /* TTS初始化状态 */
            ...
            break;
        case AUI_TTS_PLAYING:
            /* TTS正在播放 */
            ...
            break;
        case AUI_TTS_FINISH:
            /* TTS播放完成 */
            ...
            break;
        case AUI_TTS_ERROR:
            /* TTS播放失败 */
            ...
            break;
    }
}

int app_aui_cloud_tts_run(const char *text, int wait_last)
{
    /* 注册回调函数 */
    aui_cloud_set_tts_status_listener(&g_aui_handler, aui_tts_stat_cb);
    /* 请求TTS播放服务,其中text是TTS文本 */
    return aui_cloud_req_tts(&g_aui_handler, text, NULL);
}

2.4 配网服务

配网服务由配网框架和配网模块组成,配网模块完成具体的配网功能,如一键配网、设备热点配网等,而配网框架封装了配网模块,为用户提供了统一的配网调用入口,简化了编程过程。

2.4.1 配网模块介绍

本服务提供设备的WiFi网络连接配置功能。支持两种配网方式,设备热点配网方式及阿里云生活物联网平台SDK配网方式。

设备热点网页配网
进入配网模式后,设备会辐射出一个WiFi热点。手机连接该热点后,会自动弹出配网页面,用户填写需连接的路由器SSID和密码信息。信息提交后,设备端会收到SSID和密码并连接路由器。如连接成功,设备会语音播报并广播配网成功信息,手机端监测到成功信息并弹出提示。如超时未成功,设备会语音播报配网超时。

生活物联网平台SDK配网
生活物联网平台SDK为阿里云生活物联网平台提供的设备端SDK,包含WiFi配网、云端连接及设备控制功能。配合生活物联网平台手机端SDK,可以形成多样化的网络解决方案。
生活物联网平台SDK的WiFi配网方式支持一键配网和设备热点配网两种方式,可互为补充,其操作特点如下:
• 一键配网:手机无需切换路由,用户在APP上输入所连路由器SSID和密码,手机进入一键配网模式后,会不停发送包含SSID和密码的802.11射频加密报文。设备截获报文并解密后,即可连接路由器。此配网过程十分方便迅速,但可能存在兼容性问题。
• 设备热点配网:与设备热点网页配网原理类似,不过手机端需使用APP发送SSID和密码。设备接收到SSID和密码后自动连接路由器。此配网方式兼容性好,适合作为备用方案。

2.4.2 配网框架介绍

配网框架提供了配网模块的注册、配网的启动、停止等接口,为底层不同的配网模块提供了统一的接口函数。
在配网流程启动前,我们需要先注册配网方法,该动作接口需要适配层实现。启动完成后应用层将通过调用配网框架提供接口wifi_prov_start来进入配网状态。配网状态退出有三种方式,超时自动退出,收到配网结果自动退出,调用接口 wifi_prov_stop 主动退出。框架接口wifi_prov_start,支持多个配网方法同时启动,如果几个方法没有互斥。退出配网状态时,所有启动的配网将一起退出。

image.png

配网流程
配网服务组件的主要API如下:

image.png

2.4.3 代码示例

初始化
程序初始化时,首先需要注册所需配网模块,可同时注册多种:

#include <softap_prov.h>
#include <wifi_provisioning.h>

/*注册设备热点网页配网服务,参数为SSID的前缀*/
wifi_prov_softap_register("YoC");

/*注册生活物联网平台配网服务*/
wifi_prov_sl_register();

启动配网
需要配网时,启动对应的配网模块,进入配网流程。

#include <softap_prov.h>
#include <wifi_provisioning.h>

/*启动配网服务,超时时间120秒*/
wifi_prov_start(wifi_prov_get_method_id("softap"), wifi_pair_callback, 120);

事件回调函数
配网成功或失败都会调用该函数,函数的参数中event变量可以确认结果,如果由多种配网同时启动可以从method_id确认哪种配网,配网参数从result返回,可使用该参数去连接网络。

#include <wifi_provisioning.h>

static void wifi_pair_callback(uint32_t method_id, wifi_prov_event_t event, wifi_prov_result_t *result)
{
    if (event == WIFI_PROV_EVENT_TIMEOUT) {
        /*配网超时*/
        LOGD(TAG, "wifi pair timeout...");
    } else if (event == WIFI_RPOV_EVENT_GOT_RESULT) {
        /*配网成功,获取配网参数*/
        LOGD(TAG, "wifi pair got passwd ssid=%s password=%s...", result->ssid, result->password);
    }
}

停止配网
调用该函数停止配网流程,若启动多种配网也会全部停止,退出配网状态。

#include <wifi_provisioning.h>

/*停止配网*/
wifi_prov_stop();

2.5 闹铃

2.5.1 功能介绍

闹铃服务组件提供设备闹铃提醒的功能。用户设置对应闹铃模式及回调函数后,闹铃到时,通过回调函数向用户抛出对应闹铃事件。

**闹铃服务具有如下特点:
**• 可设置“仅响一次”、“每天”、“每周”或“工作日”模式
• 支持最多5个闹铃
• 支持待机状态下唤醒
• 秒级精度
闹铃服务组件的主要API如下:

image.png

2.5.2 代码示例

初始化
系统启动时,调用函数clock_alarm_init初始化闹铃服务,初始化函数会从系统Flash中获取数据,更新闹铃信息和状态。

#include <clock_alarm.h>
#include <rtc_alarm.h>

void main()
{
    ...
    clock_alarm_init(app_clock_alarm_cb);
    ...
}

事件回调函数
闹铃到时,系统会调用app_clock_alarm_cb函数,用户可在该函数中处理对应闹铃事件。若设置了多个闹铃,参数clock_id来指示哪个闹铃。

#include <cloc_alarm.h>

/* 处理闹铃到时提醒 */
static void app_clock_alarm_cb(uint8_t clock_id)
{
    LOGI(TAG, "clock_id %d alarm cb handle", clock_id);

    char url[] = "http://www.test.com/test.mp3"; //url示例
    /* 播放闹铃音乐 */
    mplayer_start(SOURCE_CLOUD, MEDIA_SYSTEM, url, 0, 1);
}

闹铃设置
用户新增一个闹铃,需要设置闹铃的模式(即一次、工作日、每周、每天),输入具体的闹铃时分秒信息。

clock_alarm_config_t cli_time;

cli_time.period = CLOCK_ALARM_PERIOD_WORKDAY;
cli_time.hour = 7;
cli_time.min = 30;
cli_time.sec = 0;

/* 新增闹钟 */
id = clock_alarm_set(0, &cli_time);

/* 修改闹钟,clock_id是被修改闹钟id号 */
id = clock_alarm_set(clock_id, &cli_time);

/* 删除闹钟, clock_id是被删除闹钟id号 */
clock_alarm_set(clock_id, NULL);

2.6 按键服务

2.6.1 功能介绍

按键服务组件提供多种类型的按键扫描功能。用户可添加对应的按键类型、引脚号、触发阈值,启动后会周期性扫描按键引脚,当键值满足触发条件后,通过回调函数向用户抛出对应按键事件。

按键服务具有如下特点:
• 最多支持10个按键
• 支持GPIO高低电平按键
• 支持GPIO电平变化检测
• 支持单一按键和组合按键(2个键组合),支持短按、长按

按键服务组件的主要API如下:

image.png

2.6.2 代码示例

初始化
按键服务的初始化和配置需要四个步骤:
• 定义单一按键表和组合按键表
• 创建task和一个消息队列,用于接收按键消息,并将用户处理函数注册到task中
• 初始化按键服务和按键表
• 根据需要配置按键参数(例如超时时间等)

按键表定义

/* 单一按键表 */
const static button_config_t button_table[] = {
    {APP_KEY_MUTE,    (PRESS_UP_FLAG | PRESS_LONG_DOWN_FLAG), button_evt, NULL, BUTTON_TYPE_GPIO,  "mute"},
    {APP_KEY_VOL_INC, (PRESS_UP_FLAG | PRESS_LONG_DOWN_FLAG), button_evt, NULL, BUTTON_TYPE_GPIO,  "inc"},
    {APP_KEY_VOL_DEC, (PRESS_UP_FLAG | PRESS_LONG_DOWN_FLAG), button_evt, NULL, BUTTON_TYPE_GPIO,  "dec"},
    {APP_KEY_STANDBY, (PRESS_UP_FLAG | PRESS_LONG_DOWN_FLAG), button_evt, NULL, BUTTON_TYPE_GPIO,  "standby"},
    {0, 0, NULL, NULL},
};

/* 组合按键表 */
const static button_combinations_t bc_table[] = {
    {
        .pin_name[0] = "mute",
        .pin_name[1] = "inc",
        .evt_flag = PRESS_LONG_DOWN_FLAG,
        .pin_sum = 2,
        .tmout = 500,
        .cb = bc_evt,
        .priv = NULL,
        .name = "mute&inc_long"
    },
    {
        .pin_name[0] = "mute",
        .pin_name[1] = "dec",
        .evt_flag = PRESS_LONG_DOWN_FLAG,
        .pin_sum = 2,
        .tmout = 500,
        .cb = bc_evt,
        .priv = NULL,
        .name = "mute&dec_long"
    },
    ...
}

创建任务和消息队列

aos_task_t task;
static aos_queue_t s_queue;
static uint8_t s_q_buffer[sizeof(evt_data_t) * MESSAGE_NUM];

aos_queue_new(&s_queue, s_q_buffer, MESSAGE_NUM * sizeof(evt_data_t),sizeof(evt_data_t));
aos_task_new_ext(&task, "b-press", button_task_thread, NULL, 4096, AOS_DEFAULT_APP_PRI + 4);

初始化按键服务,并配置按键参数

button_task();
button_srv_init();
button_init(button_table);
button_param_t pb;
button_param_cur("mute", &pb);
pb.ld_tmout = 2000;
button_param_set("mute", &pb);
button_param_set("inc", &pb);
button_param_set("dec", &pb);
button_combination_init(bc_table);

事件回调函数

#include <yoc/adc_key_srv.h>

static void button_task_thread(void *arg)
{
    evt_data_t data;
    unsigned int len;

    while (1) {
        aos_queue_recv(&s_queue, AOS_WAIT_FOREVER, &data, &len);

        if (strcmp(data.name, "mute") == 0) {
            if (data.event_id == BUTTON_PRESS_LONG_DOWN) {
                /* mute长按 */
                ...
            } else if (data.event_id == BUTTON_PRESS_UP) {
                /* mute短按 */
                ...
            }
        } else if (strcmp(data.name, "inc") == 0) {
            /* inc按键 */
            ...
        } else if (strcmp(data.name, "dec") == 0) {
            /* dec按键 */
            ...
        } else if (strcmp(data.name, "standby") == 0) {
            /* standby按键 */
            ...
        } else if (data.event_id == BUTTON_COMBINATION) {
            /* 组合按键 */
            ...
            if (strcmp(data.name, "mute&inc_long") == 0) {
                /* mute和inc组合长按 */
                ...
            } else if (strcmp(data.name, "mute&dec_long") == 0) {
                /* mute和dec组合长按 */
                ...
        }
    }
}

3. 应用示例讲解

上文中已经介绍了播放器、语音、云服务三个核心组件的功能和使用方法。三者间关系如下图,应用通过语音服务获取到音频输入,通过云服务推送给云端进行ASR/NLP识别,再将结果进行TTS合成,生成的音频通过播放器播放出来。

image.png

3.1 方案介绍

智能语音应用包含网络功能,音频播放服务、语音服务(关键词识别和数据交互)、云服务等。整个语音交互流程如下图。

image.png
语音交互流程图

通用交互流程
• 使用者说出“宝拉宝拉,今天天气怎么样”时,语音服务识别出“宝拉宝拉”这个关键词,产生唤醒事件。
• 语音唤醒事件中控制开始录音。
• 使用者继续说“今天天气怎么样”,语音数据回调中推送录音音频给云端。
• 使用者说完,产生语音断句事件,事件中停止录音和云端推送。
• 云端依次执行 ARS->NLP->TTS 三个服务:先将语音数据转化成文字“今天天气怎么样”,然后通过NLP算法,理解语义,得知是天气查询,最后通过云端技能接口获得有关的天气信息,再将天气信息的文字转成语音音频,推送给设备端。
• 设备端收到语音音频后调用播放器播放。

流程差异
当然并不是所有的语音交互都遵循这个的流程,例如调整音量等命令类的语音交互,当使用者说出“声音小一点”时,设备端只需要获取到云端下发的NLP结果,判断为设备控制命令 ,就可以直接控制设备行为,而无需后续TTS流程。
同时还需认识到,使用不同的云端服务,调用的过程也会有区别。例如:
• 有些云服务器在进行ASR/NLP识别过程中不会单独下发ASR结果,而是直接下发最终的NLP结果。
• 不同云端NLP结果的封装方式差异很大。
• 有些云端不需要设备端参与TTS过程,会直接下发语音数据。

3.2 入口函数

应用的入口函数文件如下:

app/src/app_main.c

入口函数主要有,板级、系统级、音频驱动、网络、播放器、语音服务、云服务等模块的初始化。初始化完成之后,不同的服务和任务就在各自的线程中独立运行。

void main()
{
    board_base_init();
    yoc_base_init();

    LOGI(TAG, "Build:%s,%s",__DATE__, __TIME__);

    /* 系统事件处理 */
    sys_event_init();
    app_sys_init();

    /* 初始化LED灯 */
    app_status_init();

    /* 音频数据采集 */
    board_audio_init();

    /* 初始化PWM LED灯 */
#if defined(APP_PWM_EN) && APP_PWM_EN
    app_pwm_led_init();
#endif

    /* 低功耗初始化 */
    app_lpm_init();

    /* 启动播放器 */
    mplayer_init(4 * 1024, media_evt, 20);

    /* 配置EQ */
    aui_player_sona_config(sona_aef_config, sona_aef_config_len);

    /* 启动麦克风服务 */
    app_mic_init(1);

    /* 开启功放 */
    app_speaker_mute(0);

    /* 网络初始化 */
    wifi_mode_e mode = app_network_init();

    if (mode != MODE_WIFI_TEST) {
        if (mode != MODE_WIFI_PAIRING &&
            app_sys_get_boot_reason() != BOOT_REASON_WIFI_CONFIG &&
            app_sys_get_boot_reason() != BOOT_REASON_WAKE_STANDBY) {
                local_audio_play(LOCAL_AUDIO_STARTING);
            }
#if defined(APP_FOTA_EN) && APP_FOTA_EN
        /* FOTA升级初始化 */
        app_fota_init();
#endif

        if (g_fct_mode) {
            /* 产测初始化 */
            fct_case_init();
        }

        /* 交互系统初始化 */
        app_aui_nlp_init();
        app_text_cmd_init();
    }

    /* 按键初始化 */
    app_button_init();

    /* LED状态初始化 */
    app_set_led_state(LED_TURN_OFF);

    /* 命令行测试命令 */
    cli_reg_cmds();

    return;
}

3.3 事件处理机制

系统中驻留一独立任务来处理事件,用户可通过订阅接口来获取系统事件。下面通过网络模块的实现代码来讲解该机制的使用方法。
代码路径

app/src/app_net.c

事件回调函数

static void user_local_event_cb(uint32_t event_id, const void *param, void *context)
{
    if ((wifi_is_pairing() == 0) && wifi_network_inited()) {
        network_normal_handle(event_id, param);
        network_reset_handle(event_id);
    } else {
        LOGE(TAG, "Critical network status callback %d", event_id);
    }
}

订阅系统事件
通过函数app_net_init进行网络的初始化并订阅网络事件,注册用户回调函数user_local_event_cb。在程序中,当网络事件发生时,事件处理机制会调用user_local_event_cb函数进行网络事件的处理。
网络事件如下:

image.png

#include <aos/aos.h>
#include <yoc/netmgr.h>
#include <yoc/eventid.h>
#include <devices/wifi.h>

static wifi_mode_e app_net_init(void)
{
    char ssid[32 + 1] = {0};
    int ssid_len = sizeof(ssid);
    char psk[64 + 1] = {0};
    int psk_len = sizeof(psk);

    /* 系统事件订阅 */
    event_subscribe(EVENT_NETMGR_GOT_IP, user_local_event_cb, NULL);
    event_subscribe(EVENT_NETMGR_NET_DISCON, user_local_event_cb, NULL);

    /* 使用系统事件的定时器 */
    event_subscribe(EVENT_NTP_RETRY_TIMER, user_local_event_cb, NULL);
    event_subscribe(EVENT_NET_CHECK_TIMER, user_local_event_cb, NULL);
    event_subscribe(EVENT_NET_LPM_RECONNECT, user_local_event_cb, NULL);

    aos_kv_get("wifi_ssid", ssid, &ssid_len);
    aos_kv_get("wifi_psk", psk, &psk_len);
    if (strlen(ssid) == 0) {
        wifi_pair_start();
        return MODE_WIFI_PAIRING;
    } else {
        wifi_network_init(ssid, psk);
    }
    return MODE_WIFI_NORMAL;
}

自定义事件
除了系统事件以外,事件机制也允许用户自定义事件,我们以EVENT_NTP_RETRY_TIMER为例。
app_main.h中统一管理所有用户自定义事件

#define EVENT_NTP_RETRY_TIMER       (EVENT_USER + 1)
#define EVENT_NET_CHECK_TIMER       (EVENT_USER + 2)
#define EVENT_NET_NTP_SUCCESS       (EVENT_USER + 3)
#define EVENT_NET_LPM_RECONNECT     (EVENT_USER + 4)

下面的代码是网络事件处理函数,回调中获取到EVENT_NETMGR_GOT_IP事件后,发送EVENT_NTP_RETRY_TIMER消息启动NTP对时,然后在回调的EVENT_NTP_RETRY_TIMER事件分支中调用ntp_sync_time进行对时,如果对时失败,调用event_publish_delay发送EVENT_NTP_RETRY_TIMER延时消息,一定时间后重新对时。

#include <yoc/netmgr.h>
#include <yoc/eventid.h>

static void network_normal_handle(uint32_t event_id, const void *param)
{
    switch (event_id) {
    case EVENT_NETMGR_GOT_IP: {
        /* 启动NTP对时 */
        event_publish(EVENT_NTP_RETRY_TIMER, NULL);
    } break;

    case EVENT_NETMGR_NET_DISCON: {
        LOGD(TAG, "Net down");
        /* 不主动语音提示异常,等有交互再提示 */
        internet_set_connected(0);
    } break;

    case EVENT_NTP_RETRY_TIMER:
        if (ntp_sync_time(NULL) == 0) {
#if CONFIG_RTC_EN
            /* 网络对时成功,同步到RTC中 */
            rtc_from_system();
#endif
            if (wifi_internet_is_connected() == 0){
                /* 同步到时间,确认网络成功,提示音和升级只在第一次启动 */
                internet_set_connected(1);

                app_status_update();
                local_audio_play(LOCAL_AUDIO_NET_SUCC);
                event_publish(EVENT_NET_NTP_SUCCESS, NULL);
            }
        } else {
            /* 同步时间失败重试 */
            event_publish_delay(EVENT_NTP_RETRY_TIMER, NULL, 6000);
        }
        break;
    default:
        break;
    }
}
相关文章
|
16天前
|
机器学习/深度学习 存储 人工智能
智能语音识别技术的深度剖析与应用前景####
本文深入探讨了智能语音识别技术的技术原理、关键技术突破及广泛应用场景,通过具体实例展现了该技术如何深刻改变我们的日常生活和工作方式。文章还分析了当前面临的挑战与未来发展趋势,为读者提供了一幅全面而深入的智能语音识别技术图景。 ####
|
19天前
|
机器学习/深度学习 搜索推荐 语音技术
智能语音识别技术在智能家居中的应用与挑战####
本文深入探讨了智能语音识别技术的基本原理、关键技术环节,以及其在智能家居领域的广泛应用现状。通过分析当前面临的主要挑战,如环境噪音干扰、方言及口音识别难题等,文章进一步展望了未来发展趋势,包括技术融合创新、个性化服务定制及安全隐私保护的加强。本文旨在为读者提供一个关于智能语音识别技术在智能家居中应用的全面视角,同时激发对该领域未来发展方向的思考。 ####
58 6
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
智能语音识别技术在多语言环境中的应用与挑战####
随着全球化的不断推进,跨语言交流的需求日益增长,智能语音识别技术成为连接不同语言文化的桥梁。本文旨在探索该技术在多语言环境中的应用现状、面临的挑战及未来发展趋势,通过深入分析技术瓶颈与创新策略,为促进全球无障碍沟通提供新视角。 ####
|
1月前
|
存储 自然语言处理 搜索推荐
智能语音识别技术在医疗健康领域的深度应用与前景####
本文深入探讨了智能语音识别技术在医疗健康领域的多维度应用,从电子病历的高效录入到远程诊疗的无缝对接,再到患者教育与健康管理的个性化服务,展现了该技术如何显著提升医疗服务效率与质量。通过分析典型应用场景、挑战及解决方案,本文揭示了智能语音识别技术在推动医疗行业智能化转型中的关键作用,并展望了其未来发展趋势与广阔前景。 ####
|
1月前
|
机器学习/深度学习 算法 语音技术
智能语音识别技术在医疗健康领域的应用与挑战####
本文深入探讨了智能语音识别技术(Intelligent Speech Recognition, ISR)在医疗健康领域的现状、应用实例及面临的主要挑战。通过分析ISR技术的基本原理,结合其在电子病历记录、远程医疗咨询、患者监护及健康管理等方面的实际应用案例,揭示了该技术如何提升医疗服务效率、改善医患沟通并促进个性化医疗的发展。同时,文章也指出了数据隐私保护、方言与口音识别难题、技术准确性及用户接受度等关键挑战,为未来研究和技术优化提供了方向。 ####
|
1月前
|
人工智能 算法 语音技术
智能语音识别技术:原理、应用与挑战####
本文深入浅出地探讨了智能语音识别技术的基本原理,从声学模型到语言模型的构建过程,揭示了其背后的复杂算法。同时,文章详细阐述了该技术在智能家居、客户服务、无障碍技术等领域的广泛应用,并指出了当前面临的主要挑战,包括噪声干扰、方言差异及数据隐私等问题,为读者提供了对这一前沿技术领域的全面了解。 ####
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
智能语音交互:技术原理与应用前景####
【10月更文挑战第25天】 一句话概括本文主旨,并引发读者兴趣。 智能语音交互技术,作为人工智能领域的重要分支,正以前所未有的速度融入我们的生活,从简单的语音助手到复杂的多轮对话系统,它不仅重塑了人机交互的方式,还为多个行业带来了革命性的变化。本文将深入浅出地探讨智能语音交互的技术原理、当前主流技术路线、面临的挑战及未来发展趋势,为读者揭开这一高科技领域的神秘面纱。 ####
|
5月前
|
机器学习/深度学习 自然语言处理 机器人
基于深度学习的智能语音机器人交互系统设计方案
**摘要** 本项目旨在设计和实现一套基于深度学习的智能语音机器人交互系统,该系统能够准确识别和理解用户的语音指令,提供快速响应,并注重安全性和用户友好性。系统采用分层架构,包括用户层、应用层、服务层和数据层,涉及语音识别、自然语言处理和语音合成等关键技术。深度学习模型,如RNN和LSTM,用于提升识别准确率,微服务架构和云计算技术确保系统的高效性和可扩展性。系统流程涵盖用户注册、语音数据采集、识别、处理和反馈。预期效果是高识别准确率、高效处理和良好的用户体验。未来计划包括系统性能优化和更多应用场景的探索,目标是打造一个适用于智能家居、医疗健康、教育培训等多个领域的智能语音交互解决方案。
|
API 语音技术 开发者
构建智能语音助手应用:语音识别和语音合成的实践
智能语音助手应用正在成为现代应用程序的热门趋势。语音识别技术使应用能够理解和解释用户的语音输入,而语音合成技术则将计算机生成的语音转化为可听的声音。本文将介绍构建智能语音助手应用的实践方法,并展示如何使用开源工具和API进行语音识别和语音合成。
535 0
|
API 语音技术
构建智能语音助手应用:语音识别和语音合成的实践
智能语音助手应用正变得越来越流行,它们能够通过语音与用户进行交互,为用户提供便捷的服务。在本文中,我们将介绍如何构建一个智能语音助手应用,包括语音识别和语音合成的实践。我们将使用现代化的语音处理技术和开源工具来实现这个应用。
401 0
下一篇
DataWorks