天天看點

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

上一章: 智能語音終端SDK快速上手說明 | 《無需從0開發 1天上手智能語音離線上方案》第三章>>> 下一章: 智能語音終端開發闆适配指南 | 《無需從0開發 1天上手智能語音離線上方案》第五章 >>>

1. 概述

本章介紹智能語音終端SDK的軟體開發方法。

名詞解釋

下面介紹本文中涉及的一些專有名詞:

• PCM:脈沖編碼調制,這裡專指未經過編碼的語音資料。直接從麥克風采集出來的語音即為PCM資料。

• KWS:關鍵詞識别,識别特定的幾個詞語。在我們方案中,該關鍵詞為“寶拉寶拉”,該過程在裝置端實作。

• ASR:語音識别,将聲音轉化為文字的過程。該過程在雲端完成。

• NLP:自然語言處理,将文字轉化成語義的過程。該過程在雲端完成。

• TTS:文本轉語音,将文字轉換成語音資料。該過程在雲端完成。

SDK目錄介紹

下面是智能語音SDK的目錄結構,表格中介紹了各個目錄的功能。

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

2. 核心元件介紹

本章介紹播放器元件、語音元件、雲服務元件等核心元件。

• 播放器提供了語音播放功能,支援PCM、WAV、MP3等編碼格式,可以實作記憶體音頻、SD卡音頻、線上音頻等多種音頻流播放。支援音頻的播放、停止、暫停、繼續、音量控制等多種音頻控制指令。通過适配對應的聲霸卡播放通路,可以靈活得實作多種播放場景。

• 語音元件提供了語音喚醒及麥克風音頻采集功能,可以将喚醒事件、VAD事件、采集到的音頻流等資料和事件實時傳送給調用方,調用方結合雲服務元件和播放器元件可以實作多種智能語音服務。

• 雲服務元件提供了雲端一體化的語音服務,封裝了端側和雲側的互動過程,開發者隻需簡單調用對應API、處理回調函數,即可友善的獲得雲端ASR/NLP識别結果和TTS合成服務。

• 配網服務元件提供了配網架構及多種配網子產品,封裝了統一的應用接口,開發者無需關心配網實作,即可通過統一接口擷取多種配網服務。

• 鬧鈴服務元件提供了鬧鈴服務,不論系統處于運作狀态、低功耗狀态還是待機休眠狀态,都可準時産生鬧鈴回調。

• 按鍵服務元件提供GPIO高低電平、上升/下降沿等多種類型的按鍵掃描功能。開發者設定對應的按鍵參數、回調函數,按鍵服務會自動掃描并觸發對應按鍵事件。

2.1 播放器服務

2.1.1 功能介紹

播放器服務元件支援播放、停止、音量等通用控制功能外,還支援常用的最小音量控制、音樂打斷恢複及音樂漸變切換功能。支援PCM、WAV、MP3等編碼格式。

應用示例中實作了如下播放器服務功能:

• 通知音播放。

• 線上音頻播放

• 雲端合成TTS流播放

• SD卡音頻播放

播放器服務元件的主要API如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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,使用者可根據需要在事件中增加應用功能。

播放器事件如下表:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

播放器類型如下表:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
#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如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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初始化時傳入了語音服務回調函數,之後使用語音與裝置互動時,所有的語音事件都會通過回調函數通知給使用者。

語音事件如下表:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

下例程介紹了事件回調函數的典型實作:

• 檢測到喚醒詞後會首先收到喚醒事件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如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

API調用流程圖

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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,支援多個配網方法同時啟動,如果幾個方法沒有互斥。退出配網狀态時,所有啟動的配網将一起退出。

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

配網流程

配網服務元件的主要API如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
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合成,生成的音頻通過播放器播放出來。

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章

3.1 方案介紹

智能語音應用包含網絡功能,音頻播放服務、語音服務(關鍵詞識别和資料互動)、雲服務等。整個語音互動流程如下圖。

智能語音應用開發指南 | 《無需從0開發 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函數進行網絡事件的處理。

網絡事件如下:

智能語音應用開發指南 | 《無需從0開發 1天上手智能語音離線上方案》第四章
#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;
    }
}           

繼續閱讀