上一章: 智能語音終端SDK快速上手說明 | 《無需從0開發 1天上手智能語音離線上方案》第三章>>> 下一章: 智能語音終端開發闆适配指南 | 《無需從0開發 1天上手智能語音離線上方案》第五章 >>>
1. 概述
本章介紹智能語音終端SDK的軟體開發方法。
名詞解釋
下面介紹本文中涉及的一些專有名詞:
• PCM:脈沖編碼調制,這裡專指未經過編碼的語音資料。直接從麥克風采集出來的語音即為PCM資料。
• KWS:關鍵詞識别,識别特定的幾個詞語。在我們方案中,該關鍵詞為“寶拉寶拉”,該過程在裝置端實作。
• ASR:語音識别,将聲音轉化為文字的過程。該過程在雲端完成。
• NLP:自然語言處理,将文字轉化成語義的過程。該過程在雲端完成。
• TTS:文本轉語音,将文字轉換成語音資料。該過程在雲端完成。
SDK目錄介紹
下面是智能語音SDK的目錄結構,表格中介紹了各個目錄的功能。

2. 核心元件介紹
本章介紹播放器元件、語音元件、雲服務元件等核心元件。
• 播放器提供了語音播放功能,支援PCM、WAV、MP3等編碼格式,可以實作記憶體音頻、SD卡音頻、線上音頻等多種音頻流播放。支援音頻的播放、停止、暫停、繼續、音量控制等多種音頻控制指令。通過适配對應的聲霸卡播放通路,可以靈活得實作多種播放場景。
• 語音元件提供了語音喚醒及麥克風音頻采集功能,可以将喚醒事件、VAD事件、采集到的音頻流等資料和事件實時傳送給調用方,調用方結合雲服務元件和播放器元件可以實作多種智能語音服務。
• 雲服務元件提供了雲端一體化的語音服務,封裝了端側和雲側的互動過程,開發者隻需簡單調用對應API、處理回調函數,即可友善的獲得雲端ASR/NLP識别結果和TTS合成服務。
• 配網服務元件提供了配網架構及多種配網子產品,封裝了統一的應用接口,開發者無需關心配網實作,即可通過統一接口擷取多種配網服務。
• 鬧鈴服務元件提供了鬧鈴服務,不論系統處于運作狀态、低功耗狀态還是待機休眠狀态,都可準時産生鬧鈴回調。
• 按鍵服務元件提供GPIO高低電平、上升/下降沿等多種類型的按鍵掃描功能。開發者設定對應的按鍵參數、回調函數,按鍵服務會自動掃描并觸發對應按鍵事件。
2.1 播放器服務
2.1.1 功能介紹
播放器服務元件支援播放、停止、音量等通用控制功能外,還支援常用的最小音量控制、音樂打斷恢複及音樂漸變切換功能。支援PCM、WAV、MP3等編碼格式。
應用示例中實作了如下播放器服務功能:
• 通知音播放。
• 線上音頻播放
• 雲端合成TTS流播放
• SD卡音頻播放
播放器服務元件的主要API如下:
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,使用者可根據需要在事件中增加應用功能。
播放器事件如下表:
播放器類型如下表:
#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如下:
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(¶m, 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 = ¶m;
voice_param.ai_param_len = sizeof(mic_param_t);
param.ext_param1 = &voice_param;
/* 配置語音服務參數 */
aui_mic_set_param(¶m);
if (wwwv_enable) {
/* 二次喚醒使能初始化 */
app_aui_wwv_init();
}
return ret;
}
在app_mic_init初始化時傳入了語音服務回調函數,之後使用語音與裝置互動時,所有的語音事件都會通過回調函數通知給使用者。
語音事件如下表:
下例程介紹了事件回調函數的典型實作:
• 檢測到喚醒詞後會首先收到喚醒事件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如下:
API調用流程圖
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,支援多個配網方法同時啟動,如果幾個方法沒有互斥。退出配網狀态時,所有啟動的配網将一起退出。
配網流程
配網服務元件的主要API如下:
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如下:
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如下:
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合成,生成的音頻通過播放器播放出來。
3.1 方案介紹
智能語音應用包含網絡功能,音頻播放服務、語音服務(關鍵詞識别和資料互動)、雲服務等。整個語音互動流程如下圖。
語音互動流程圖
通用互動流程
• 使用者說出“寶拉寶拉,今天天氣怎麼樣”時,語音服務識别出“寶拉寶拉”這個關鍵詞,産生喚醒事件。
• 語音喚醒事件中控制開始錄音。
• 使用者繼續說“今天天氣怎麼樣”,語音資料回調中推送錄音音頻給雲端。
• 使用者說完,産生語音斷句事件,事件中停止錄音和雲端推送。
• 雲端依次執行 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函數進行網絡事件的處理。
網絡事件如下:
#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;
}
}