使用OLAMI SDK和讯飞语音合成制作一个语音回复的短信小助手

现代人的生活越来越离不开手机,但我们总会遇到一些时候不方便用手去操作,比如开车,玩游戏的时候。智能语音时代这种情况有了新的解决方案。本文介绍了一个使用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系统可以编写语法。我们的应用准备支持重念和回复功能。所以添加对应的两句语法:

写完之后不要忘记发布:

至此,我们在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" />
  • 1

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

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

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

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

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

收到短信后,要播报短信内容,并且要存储短信以便重听的时候使用。所以这里在MainActivity中实现了一个接口方便在Receiver中使用:

SmsReceiver.java

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

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();
    }
}
  • 1

播报短信

前面我们看到在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");
    }
});
  • 1

然后就可以使用了:

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) {

        }
    });
}
  • 1

从代码里的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);
  • 1

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();

    }
}
  • 1

之后就是在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());
                }
            }
        }
    }
}
  • 1

这里提到一个小插曲,在刚开始做这个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("我不知道你想听的是哪条短信。");
    }
}
  • 1

小结

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

结束语

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340