现代人的生活越来越离不开手机,但我们总会遇到一些时候不方便用手去操作,比如开车,玩游戏的时候。智能语音时代这种情况有了新的解决方案。本文介绍了一个使用OLAMI Android SDK进行语音识别和理解,讯飞在线语音合成sdk进行语音合成实现在收到短信时直接进行语音回复的demo开发过程。在此基础上我们也可以很方便的增加其他的功能,比如查新闻,百科等,完成一个DIY的语音助手。

简介

OLAMI

OLAMI是由威盛电子(上海)有限公司人工智能软件研发团队推出的一个人工智能软件开发平台,提供包括自然语音交互技术在内的全方位人机交互解决方案,覆盖了众多垂直领域的语义通用场景。

讯飞语音合成

科大讯飞提供的语音合成解决方案,解决的主要问题是如何将文字信息转化为可听的声音信息,也即“让机器像人一样开口说话”。

开始

整个demo很简单,主要流程就是:收到短信->播报短信->用户语音回复->发送短信。其中的关键就是用户语音回复的内容如何转换成发送短信的命令。

开发工具:Android Studio 2.3.3 
OLAMI SDK版本: 2.0.0

获取OLAMI SDK的密钥

要使用OLAMI服务,需要先注册,地址是https://cn.olami.ai/open/website/register/register_data。注册过程很简单,之后绑定手机就可以使用了。

现在可以回到应用管理,点击查看Key,记录下App Key和App Secret,留待后面编写代码的时候使用。顺便提一下,点击配置模块,里面有一些预定义好的内置功能,默认勾选的有nonsense(聊天)、date(日历)和cyclopaedia(百科)这几个被勾上,作用我们后面再说。

新建OLAMI应用

注册完之后进入应用管理,就可以创建自己的应用了。创建完应用之后进入NLI系统可以编写语法。我们的应用准备支持重念和回复功能。所以添加对应的两句语法:

android 讯飞星火语音合成无bugdemo 讯飞合成语音助手_讯飞

android 讯飞星火语音合成无bugdemo 讯飞合成语音助手_讯飞_02

写完之后不要忘记发布:

android 讯飞星火语音合成无bugdemo 讯飞合成语音助手_ide_03

至此,我们在OLAMI平台的准备工作就已经完成,马上就将进入Android Studio开始编写代码。

新建Android工程

在Android Studio中新建一个工程,Target Devices选Phone and Tablet。Android SDK从API 19开始提供了新的获取新收到短信的方法,所以Minimum SDK选择API 19。然后一路Next到Finish。

接收收到短信事件

在manifest中添加接收和发送短信的权限:

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

由于这个应用中收到短信广播后,要回到Activity中进行后续的工作,我们选择了在Activity动态创建一个Broadcast receiver,并通过setSmsHandler将二者关联起来:

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

在receiver的onReceive方法中获得刚收到的短信内容:

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

然后就可以解析短信的数据了。需要注意的是,号码如果直接送到tts去读,有可能会读成数量的形式(例如10086会读成一万零八十六),所以demo中在每个数字之间加入了空格。

收到短信后,要播报短信内容,并且要存储短信以便重听的时候使用。所以这里在MainActivity中实现了一个接口方便在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();

    // 播报号码中加入空格,以免读成数量
    for (int i = 0; i < phoneNumber.length(); i ++) {
        finalAddress.append(phoneNumber.charAt(i));
        finalAddress.append(' ');
    }
    String tts = String.format("收到来自%s的短信,内容是%s,你想回复什么?", finalAddress, content);

    speak(tts);

    // 记录短信内容
    number = phoneNumber;
    lastMsg = content;

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

播报短信

前面我们看到在processNewMsg中调用了speak方法来念出短信。这个例子中选择使用讯飞的在线语音合成api实现。讯飞语音合成的sdk同样需要注册,创建应用,完成后即可下载sdk。讯飞的知名度比较高,这里就不详细介绍过程了。

讯飞api的使用也很简单,按照sdk中的文档添加权限,把包放到响应的位置就可以开始编写代码了。首先在onCreate中初始化:

// 初始化讯飞服务。APPID注册讯飞平台,创建应用即可获得
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中我们可以看到讯飞提供了丰富的回调函数便于处理整个语音合成的过程。

至此我们就实现了收到短信播报的功能。

语音转语义

OLAMI SDK中提供了方便的接口(参考文档:OLAMI Android Client SDK & 示例代码),配置对应权限后,从打开录音到得到语义的整个过程都只需调用一个简单的start方法,之后在对应的回调函数中可以拿到结果。

与讯飞接口类似,首先在onCreate中初始化:

...

// MainActivity的成员变量
RecorderSpeechRecognizer recognizer;
TextRecognizer textRecognizer;

...

// onCreate方法中

// 初始化olami服务
// 创建 APIConfiguration 对象
APIConfiguration config = new APIConfiguration(KEY, SECRET, APIConfiguration.LOCALIZE_OPTION_SIMPLIFIED_CHINESE);
recognizer = RecorderSpeechRecognizer.create(this, config);
textRecognizer = new TextRecognizer(config);
// 下面为可选的设置
recognizer.setLengthOfVADEnd(2000);
textRecognizer.setEndUserIdentifier("Someone");
textRecognizer.setTimeout(1000);

RecorderSpeechRecognizer是语音识别引擎,TextRecognizer是文本识别引擎。TextRecognizer在这里用于模拟器测试,在不方便录音的情况下也能够做到短信收发。RecorderSpeechRecognizer.create方法的第一个参数是IRecorderSpeechRecognizerListener,这里直接在MainActivity中实现。这个Listener中也提供了很丰富的回调接口,涵盖了整个录音、识别、音量调节过程,很方便使用。

然后在Activity中放一个按钮(我这里叫Button2),点击事件中启动录音:

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中处理结果:

@Override
public void onRecognizeResultChange(APIResponse response) {
    if (response.ok() && response.hasData()) {
        // 提取语音转文字识别结果
        //SpeechResult sttResult = response.getData().getSpeechResult();
        // 提取 NLI 语义或 IDS 数据
        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());
                }
            }
        }
    }
}

这里提到一个小插曲,在刚开始做这个demo的时候下载的OLAMI SDK还是1.0版jar包的方式,前两天发现SDK升级到了2.0版。2.0版的使用比旧版方便很多,只需在build.gradle中加上配置,就可以直接从gradle中央仓库获得。可见OLAMI官方代码的维护还是很勤快的。

回复短信

接下来只要实现replyMsg和repeatMsg就大功告成了:

public void replyMsg(String content) {
    if (number != null) {
        SmsManager.getDefault().sendTextMessage(number, null, content, null, null);
        speak("好的");
    } else {
        speak("我还不知道要发给谁。");
    }
}

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

小结

总的来说OLAMI和讯飞两部分的SDK使用起来都很轻松方便,只要把任务交出去就能拿到结果。整个demo结构非常简单,实现了主要功能,但正式使用的话还需要很多改进,比如加上权限的判断,以免启动时报错退出;丰富OLAMI平台上的语法,以适应不同用户的不同说法等。

结束语

本文主要介绍了如何用OLAMI SDK和讯飞语音合成制作一个Android平台短信小助手的过程。通过对这些开放平台的使用,普通的开发者能够很快捷的实现语音、语义相关的功能。前面提到的OLAMI平台勾选的预置功能在最后也带来了很有意思的效果:本来预想一个简单的语音回复短信工具居然自带了聊天机器人的功能。如果我们对功能进行扩展,就能很快的为自己量身打造一个语音助手,这在几年前是很难想象的事情。

附录

* 本示例源代码 *

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

csdn下载频道:

* 优秀自然语言理解博客文章推荐:*

根据OLAMI平台开发的日历Demo

用olami开放语义平台做汇率换算应用

自然语言处理-实际开发:用语义开放平台olami写一个翻译的应用

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

微信小程序+OLAMI自然语言API接口制作智能查询工具–快递、聊天、日历等

热门自然语言理解和语音API开发平台对比


* 自然语言理解开发爱好者博客汇总:*