使用OLAMI SDK和訊飛語音合成制作一個(gè)語音回復(fù)的短信小助手

(本文源地址:http://blog.csdn.net/speeds3/article/details/75131125

現(xiàn)代人的生活越來越離不開手機(jī),但我們總會(huì)遇到一些時(shí)候不方便用手去操作,比如開車,玩游戲的時(shí)候。智能語音時(shí)代這種情況有了新的解決方案。本文介紹了一個(gè)使用OLAMI Android SDK進(jìn)行語音識(shí)別和理解,訊飛在線語音合成sdk進(jìn)行語音合成實(shí)現(xiàn)在收到短信時(shí)直接進(jìn)行語音回復(fù)的demo開發(fā)過程。在此基礎(chǔ)上我們也可以很方便的增加其他的功能,比如查新聞,百科等,完成一個(gè)DIY的語音助手。

簡介

OLAMI

OLAMI是由威盛電子(上海)有限公司人工智能軟件研發(fā)團(tuán)隊(duì)推出的一個(gè)人工智能軟件開發(fā)平臺(tái),提供包括自然語音交互技術(shù)在內(nèi)的全方位人機(jī)交互解決方案,覆蓋了眾多垂直領(lǐng)域的語義通用場景。

訊飛語音合成

科大訊飛提供的語音合成解決方案,解決的主要問題是如何將文字信息轉(zhuǎn)化為可聽的聲音信息,也即“讓機(jī)器像人一樣開口說話”。

開始

整個(gè)demo很簡單,主要流程就是:收到短信->播報(bào)短信->用戶語音回復(fù)->發(fā)送短信。其中的關(guān)鍵就是用戶語音回復(fù)的內(nèi)容如何轉(zhuǎn)換成發(fā)送短信的命令。

獲取OLAMI SDK的密鑰

要使用OLAMI服務(wù),需要先注冊(cè),地址是https://cn.olami.ai/open/website/register/register_data。注冊(cè)過程很簡單,之后綁定手機(jī)就可以使用了。

現(xiàn)在可以回到應(yīng)用管理,點(diǎn)擊查看Key,記錄下App Key和App Secret,留待后面編寫代碼的時(shí)候使用。順便提一下,點(diǎn)擊配置模塊,里面有一些預(yù)定義好的內(nèi)置功能,默認(rèn)勾選的有nonsense(聊天)、date(日歷)和cyclopaedia(百科)這幾個(gè)被勾上,作用我們后面再說。

新建OLAMI應(yīng)用

注冊(cè)完之后進(jìn)入應(yīng)用管理,就可以創(chuàng)建自己的應(yīng)用了。創(chuàng)建完應(yīng)用之后進(jìn)入NLI系統(tǒng)可以編寫語法。我們的應(yīng)用準(zhǔn)備支持重念和回復(fù)功能。所以添加對(duì)應(yīng)的兩句語法:

重說
回復(fù)

寫完之后不要忘記發(fā)布:

發(fā)布

至此,我們?cè)贠LAMI平臺(tái)的準(zhǔn)備工作就已經(jīng)完成,馬上就將進(jìn)入Android Studio開始編寫代碼。

新建Android工程

在Android Studio中新建一個(gè)工程,Target Devices選Phone and Tablet。Android SDK從API 19開始提供了新的獲取新收到短信的方法,所以Minimum SDK選擇API 19。然后一路Next到Finish。

接收收到短信事件

在manifest中添加接收和發(fā)送短信的權(quán)限:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />

由于這個(gè)應(yīng)用中收到短信廣播后,要回到Activity中進(jìn)行后續(xù)的工作,我們選擇了在Activity動(dòng)態(tài)創(chuàng)建一個(gè)Broadcast receiver,并通過setSmsHandler將二者關(guān)聯(lián)起來:

// 注冊(cè)短信廣播接收器
receiver = new SmsReceiver();
receiver.setSmsHandler(this);
IntentFilter filter = new IntentFilter();
filter.addAction(SMS_RECEIVED_ACTION);
registerReceiver(receiver, filter);

在receiver的onReceive方法中獲得剛收到的短信內(nèi)容:

SmsMessage[] msgs = Telephony.Sms.Intents.getMessagesFromIntent(intent);

然后就可以解析短信的數(shù)據(jù)了。需要注意的是,號(hào)碼如果直接送到tts去讀,有可能會(huì)讀成數(shù)量的形式(例如10086會(huì)讀成一萬零八十六),所以demo中在每個(gè)數(shù)字之間加入了空格。

收到短信后,要播報(bào)短信內(nèi)容,并且要存儲(chǔ)短信以便重聽的時(shí)候使用。所以這里在MainActivity中實(shí)現(xiàn)了一個(gè)接口方便在Receiver中使用:

SmsReceiver.java

public interface SmsHandler {
    void processNewMsg(String phoneNumber, String content);
}

MainActivity.java

@Override
public void processNewMsg(String phoneNumber, String content) {
    StringBuilder finalAddress = new StringBuilder();

    // 播報(bào)號(hào)碼中加入空格,以免讀成數(shù)量
    for (int i = 0; i < phoneNumber.length(); i ++) {
        finalAddress.append(phoneNumber.charAt(i));
        finalAddress.append(' ');
    }
    String tts = String.format("收到來自%s的短信,內(nèi)容是%s,你想回復(fù)什么?", finalAddress, content);

    speak(tts);

    // 記錄短信內(nèi)容
    number = phoneNumber;
    lastMsg = content;

    // 打開錄音
    try {
        recognizer.start();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

播報(bào)短信

前面我們看到在processNewMsg中調(diào)用了speak方法來念出短信。這個(gè)例子中選擇使用訊飛的在線語音合成api實(shí)現(xiàn)。訊飛語音合成的sdk同樣需要注冊(cè),創(chuàng)建應(yīng)用,完成后即可下載sdk。訊飛的知名度比較高,這里就不詳細(xì)介紹過程了。

訊飛api的使用也很簡單,按照sdk中的文檔添加權(quán)限,把包放到響應(yīng)的位置就可以開始編寫代碼了。首先在onCreate中初始化:

// 初始化訊飛服務(wù)。APPID注冊(cè)訊飛平臺(tái),創(chuàng)建應(yīng)用即可獲得
SpeechUtility uti = SpeechUtility.createUtility(getApplicationContext(), SpeechConstant.APPID + "=595da10d");

if (uti == null) {
    System.out.println("create Utility failed. ");
}

tts = SpeechSynthesizer.createSynthesizer(getApplicationContext(), new InitListener() {
    @Override
    public void onInit(int i) {
        System.out.println("tts初始化完成");
        tts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
        tts.setParameter(SpeechConstant.ENGINE_MODE, SpeechConstant.MODE_AUTO);
        tts.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
        tts.setParameter(SpeechConstant.SPEED, "40");
    }
});

然后就可以使用了:

public void speak(String content) {
    tts.startSpeaking(content, new SynthesizerListener() {
        @Override
        public void onSpeakBegin() {

        }

        @Override
        public void onBufferProgress(int i, int i1, int i2, String s) {

        }

        @Override
        public void onSpeakPaused() {

        }

        @Override
        public void onSpeakResumed() {

        }

        @Override
        public void onSpeakProgress(int i, int i1, int i2) {

        }

        @Override
        public void onCompleted(SpeechError speechError) {

        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    });
}

從代碼里的linstener中我們可以看到訊飛提供了豐富的回調(diào)函數(shù)便于處理整個(gè)語音合成的過程。

至此我們就實(shí)現(xiàn)了收到短信播報(bào)的功能。

語音轉(zhuǎn)語義

OLAMI SDK中提供了方便的接口(參考文檔:OLAMI Android Client SDK & 示例代碼),配置對(duì)應(yīng)權(quán)限后,從打開錄音到得到語義的整個(gè)過程都只需調(diào)用一個(gè)簡單的start方法,之后在對(duì)應(yīng)的回調(diào)函數(shù)中可以拿到結(jié)果。

與訊飛接口類似,首先在onCreate中初始化:

...

// MainActivity的成員變量
RecorderSpeechRecognizer recognizer;
TextRecognizer textRecognizer;

...

// onCreate方法中

// 初始化olami服務(wù)
// 創(chuàng)建 APIConfiguration 對(duì)象
APIConfiguration config = new APIConfiguration(KEY, SECRET, APIConfiguration.LOCALIZE_OPTION_SIMPLIFIED_CHINESE);
recognizer = RecorderSpeechRecognizer.create(this, config);
textRecognizer = new TextRecognizer(config);
// 下面為可選的設(shè)置
recognizer.setLengthOfVADEnd(2000);
textRecognizer.setEndUserIdentifier("Someone");
textRecognizer.setTimeout(1000);

RecorderSpeechRecognizer是語音識(shí)別引擎,TextRecognizer是文本識(shí)別引擎。TextRecognizer在這里用于模擬器測試,在不方便錄音的情況下也能夠做到短信收發(fā)。RecorderSpeechRecognizer.create方法的第一個(gè)參數(shù)是IRecorderSpeechRecognizerListener,這里直接在MainActivity中實(shí)現(xiàn)。這個(gè)Listener中也提供了很豐富的回調(diào)接口,涵蓋了整個(gè)錄音、識(shí)別、音量調(diào)節(jié)過程,很方便使用。

然后在Activity中放一個(gè)按鈕(我這里叫Button2),點(diǎn)擊事件中啟動(dòng)錄音:

public void onButton2Click(View view) {
    RecorderSpeechRecognizer.RecordState recordState = recognizer.getRecordState();

    // Check to see if we should start recording or stop manually.
    if (recordState == RecorderSpeechRecognizer.RecordState.STOPPED) {
        try {

            // * Request to start voice recording and recognition.
            recognizer.start();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    } else if (recordState == RecorderSpeechRecognizer.RecordState.RECORDING) {

        // * Request to stop voice recording when manually stop,
        //   and then wait for the final recognition result.
        recognizer.stop();

    }
}

之后就是在onRecognizeResultChange中處理結(jié)果:

@Override
public void onRecognizeResultChange(APIResponse response) {
    if (response.ok() && response.hasData()) {
        // 提取語音轉(zhuǎn)文字識(shí)別結(jié)果
        //SpeechResult sttResult = response.getData().getSpeechResult();
        // 提取 NLI 語義或 IDS 數(shù)據(jù)
        if (response.getData().hasNLIResults()) {
            NLIResult[] nliResults = response.getData().getNLIResults();
            for (NLIResult result : nliResults) {
                if (result.getSemantics() != null && result.getSemantics().length > 0) {
                    for (Semantic semantic : result.getSemantics()) {
                        if (semantic.getAppModule().equals("sms")) {
                            switch (semantic.getGlobalModifiers()[0]) {
                                case "reply": {
                                    for (Slot slot : semantic.getSlots()) {
                                        if (slot.getName().equals("content")) {
                                            replyMsg(slot.getValue());
                                            return;
                                        }
                                    }
                                }
                                break;
                                case "repeat": {
                                    repeatMsg();
                                }
                                default:
                                    break;
                            }
                        }
                    }
                } else {
                    speak(result.getDescObject().getReplyAnswer());
                }
            }
        }
    }
}

這里提到一個(gè)小插曲,在剛開始做這個(gè)demo的時(shí)候下載的OLAMI SDK還是1.0版jar包的方式,前兩天發(fā)現(xiàn)SDK升級(jí)到了2.0版。2.0版的使用比舊版方便很多,只需在build.gradle中加上配置,就可以直接從gradle中央倉庫獲得??梢奜LAMI官方代碼的維護(hù)還是很勤快的。

回復(fù)短信

接下來只要實(shí)現(xiàn)replyMsg和repeatMsg就大功告成了:

public void replyMsg(String content) {
    if (number != null) {
        SmsManager.getDefault().sendTextMessage(number, null, content, null, null);
        speak("好的");
    } else {
        speak("我還不知道要發(fā)給誰。");
    }
}

private void repeatMsg() {
    if (lastMsg != null) {
        speak(lastMsg);
    } else {
        speak("我不知道你想聽的是哪條短信。");
    }
}

小結(jié)

總的來說OLAMI和訊飛兩部分的SDK使用起來都很輕松方便,只要把任務(wù)交出去就能拿到結(jié)果。整個(gè)demo結(jié)構(gòu)非常簡單,實(shí)現(xiàn)了主要功能,但正式使用的話還需要很多改進(jìn),比如加上權(quán)限的判斷,以免啟動(dòng)時(shí)報(bào)錯(cuò)退出;豐富OLAMI平臺(tái)上的語法,以適應(yīng)不同用戶的不同說法等。

結(jié)束語

本文主要介紹了如何用OLAMI SDK和訊飛語音合成制作一個(gè)Android平臺(tái)短信小助手的過程。通過對(duì)這些開放平臺(tái)的使用,普通的開發(fā)者能夠很快捷的實(shí)現(xiàn)語音、語義相關(guān)的功能。前面提到的OLAMI平臺(tái)勾選的預(yù)置功能在最后也帶來了很有意思的效果:本來預(yù)想一個(gè)簡單的語音回復(fù)短信工具居然自帶了聊天機(jī)器人的功能。如果我們對(duì)功能進(jìn)行擴(kuò)展,就能很快的為自己量身打造一個(gè)語音助手,這在幾年前是很難想象的事情。

附錄

** 本示例源代碼 **

github:https://github.com/stdioh-cn/MessageDemo2

csdn下載頻道:http://download.csdn.net/detail/speeds3/9899273

** 優(yōu)秀自然語言理解博客文章推薦:**

根據(jù)OLAMI平臺(tái)開發(fā)的日歷Demo

用olami開放語義平臺(tái)做匯率換算應(yīng)用

自然語言處理-實(shí)際開發(fā):用語義開放平臺(tái)olami寫一個(gè)翻譯的應(yīng)用

自定義java.awt.Canvas—趣味聊天

微信小程序+OLAMI自然語言API接口制作智能查詢工具--快遞、聊天、日歷等

熱門自然語言理解和語音API開發(fā)平臺(tái)對(duì)比


** 自然語言理解開發(fā)愛好者博客匯總:**

http://blog.csdn.net/huangmeimao

http://blog.csdn.net/u011211290

http://blog.csdn.net/u011827504

http://blog.csdn.net/xinfinityx

http://blog.csdn.net/happycxz

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 現(xiàn)代人的生活越來越離不開手機(jī),但我們總會(huì)遇到一些時(shí)候不方便用手去操作,比如開車,玩游戲的時(shí)候。智能語音時(shí)代這種情況...
    yml7822閱讀 403評(píng)論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,368評(píng)論 25 708
  • 本文寫于兩年前,一直是未公開狀態(tài),屬內(nèi)部交流使用,現(xiàn)在也換公司了,公開發(fā)布下。 一、相關(guān)公司介紹 1.科大訊飛 網(wǎng)...
    kevin282閱讀 7,948評(píng)論 1 23
  • 在今年這個(gè)炎炎的夏日里忙于高三的上課,可能是事先有了心理準(zhǔn)備,加上課程安排的比較合理,再加上一家人的聚...
    何必曾相逢閱讀 564評(píng)論 0 0
  • 8:00起床 工作到中午11:30,回家吃飯。 回家工作到下午7:00,下午3:00露露跟我打電話說她被下課了,哭...
    書恒被從名了閱讀 222評(píng)論 0 0