最近由于公司项目需要研究了一下讯飞的语音合成功能,其实挺简单的,这里写下来一是为了加深自己的印象,二是告诉大家我踩过的坑。
集成SDK之前,你得需要下载SDK,而下载SDK之前你需要在讯飞开放平台上创建一个你自己的应用,(这里注意一下创建一个应用时,会自动关联一个Appid,Appid和对应的SDK具有一致性,如:创建android平台的应用A,关联的Appid是12345678,即12345678和应用A对应的SDK是一一对应关系。)
接下来我说明一下集成的具体步骤:
1 首先成为开发者
点击讯飞开放平台页面右上角“注册”按钮,在注册页面根据提示信息填写基本资料,成功提交后,即可成为一名开发者。这里是讯飞开放平台的地址:http://www.xfyun.cn/
2 完善用户资料
为啥要完善用户资料呢,因为需要完善用户资料才能创建应用;
3 创建应用
登录讯飞开放平台后,有两种方式可以创建应用:
1)点击“我的语音云”->“创建新应用”,请根据提示信息,完成应用的创建;
2)点击“产品服务”,进入某个服务页面,如“语音基础能力”,选择右侧的“使用服务”,在弹出的窗口中点击“创建新应用”,请根据提示信息,完成应用的创建。
4 应用提交审核
如果你只是为了测试玩玩的话,这一步骤你也可以不操作,如果你是公司有这方面功能的需求,那就需要这步操作了,因为 SDK集成调试阶段,服务量会有500次/日的限制,通过审核后可无限次使用语音服务。5 下载SDK
在讯飞开放平台首页顶部菜单栏选中SDK下载,选择好你要集成的服务如:在线语音合成,选择好你的平台如:android,选择好你刚刚在平台创建好的应用;
前期工作做好了,现在就可以将SDK集成到你的应用中了
6 集成SD
Step 1 导入SDK
将在官网下载的Android SDK 压缩包中libs目录下所有子文件拷贝至Android工程的libs目录下。如下图所示:
注:
- Android SDK提供了各个平台libmsc.so文件,开发者可以根据工程需求选取适当平台库文件进行集成。
- 如果您需要将应用push到设备使用,请将设备cpu对应指令集的libmsc.so push到/system/lib中。
Step 2 添加用户权限
在工程 AndroidManifest.xml 文件中添加如下权限
<!--连接网络权限,用于执行云端语音能力 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--获取当前wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--允许程序改变网络连接状态 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<!--读取手机信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--读取联系人权限,上传联系人需要用到此权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!--外存储写权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--外存储读权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--配置权限,用来记录应用配置信息 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
<!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
<uses-permission android:name="android.permission.CAMERA" />
注意:如需在打包或者生成APK的时候进行混淆,请在proguard.cfg中添加如下代码:
-keep class com.iflytek.**{*;}
-keepattributes Signature
Step 3 初始化
初始化即创建语音配置对象,只有初始化后才可以使用MSC的各项服务。建议将初始化放在程序入口处(如Application、Activity的onCreate方法),初始化代码如下:
// 将“12345678”替换成您申请的APPID,申请地址:http://www.xfyun.cn
// 请勿在“=”与appid之间添加任何空字符或者转义符
SpeechUtility.createUtility(context, SpeechConstant.APPID +"=12345678");
createUtility方法的第二个参数为传入的初始化参数列表,可配置的参数如下:
参数 | 说明 | 必填 |
---|---|---|
appid | 8位16进制数字字符串,应用的唯一标识,与下载的SDK一一对应。 | 是 |
usr | 开发者在云平台上注册的账号。 | 否 |
pwd | 账号对应的密码,与账号同时存在。 | 否 |
engine_mode | 引擎模式,可选值为:msc:只使用MSC的能力;plus:只使用语记能力;auto:云端使用MSC,本地使用语记;默认取值为auto。注:使用MSC本地功能的请设置为msc。 | 否 |
force_login | 在createUtility时会对进程名称进行检查,如果名称与应用包名不一致则不进行login操作,返回null,用以规避在子进程反复进行调用的问题。此参数设置是否强制login。默认值:false (进行检查,不强制login)。 | 否 |
lib_name | 在createUtility时会加载动态库,此时可以传入动态库名称。例如:libmsc_xxx_1072.so(xxx为您的公司名,1072为科大讯飞sdk版本号), 默认值:msc。注:如您是预装软件,为了避免动态库冲突建议修改名称。 | 否 |
注意:参数需要以键值对的形式存储在字符串中传入createUtility方法,以逗号隔开,如“appid=12345678,usr=iflytekcloud,pwd=123456”。
作者:嗯上面集成SDK的几点都是我直接复制官网的,我这里直接贴出来也是为了方便大家不用再去官网找了,不过有一点我要强调一下,用Android studio开发的同学们注意了,那些so文件必须要放下D:\project\VehicleTerminal\app\src\main\jniLibs目录下(“D:\project\VehicleTerminal\app\”前面这里的路径当然是根据你的项目来定了),如果你放在这里的话,你是初始化不了这个服务的,你调用初始化的时候会报错误码为21002的错误,官方文档是解释这个错误叫“引擎不支持”;语音合成
与语音听写相反,语音合成(SpeechSynthesizer)是将文字信息转化为可听的声音信息,让机器像人一样开口说话。
语音合成中,主要参数包括合成的
- 语言(LANGUAGE,中文、英文等)
- 方言(ACCENT,中文的普通话,粤语等)
- 发音人特征(性别,年龄,语气)
- 语速(SPEED)
- 音量(VOLUME)
- 语调(PITCH)
- 音频采样率(SAMPLE_RATE)
在 MSC SDK 参数中的前三者(语言、方言和特征)基本由发音人决定——即不同的发音人,支持不一样的语言、方言和特征,参考附录中的发音人列表。
如果使用的是离线合成,MSC 模式下还必须设置合成资源的路径,需下载使用对应的离线合成SDK。
mTts.setParameter( SpeechConstant.ENGINE_TYPE, engineType );
mTts.setParameter( SpeechConstant.ENGINE_MODE, engineMode );
if( SpeechConstant.TYPE_LOCAL.equals(engineType)
&&SpeechConstant.MODE_MSC.equals(engineMode) ){
// 需下载使用对应的离线合成SDK
mTts.setParameter( ResourceUtil.TTS_RES_PATH, ttsResPath );
}
mTts.setParameter( SpeechConstant.VOICE_NAME, voiceName );
final String strTextToSpeech = "科大讯飞,让世界聆听我们的声音";
mTts.startSpeaking( strTextToSpeech, mSynListener );
作者:其实当时我看到这里是一脸懵逼的,那个mTs是啥,engineType又是啥,我先什么都没管把这段代码复制到了我的demo项目中,然后又仔细找啊找啊才找到了这么段文字引擎类型
MSC SDK 有几种引擎类型(ENGINE_TYPE),此文章中我们主要关注并介绍以下两种:
- 在线引擎(TYPE_CLOUD),又称为云端模式,需要使用网络,速度稍慢,并产生一定流量,但有更好的识别和合成的效果,如更高的识别匹配度,更多的发音人等。
- 离线引擎(TYPE_LOCAL),又称为本地模式,不需要使用网络,且识别和合成的速度更快,但同时要求购买并使用对应的离线资源(下载对应离线功能的SDK包),或安装语记(语记的更多介绍,见后面章节)。
需要说明的是,在线引擎下,结果返回速度基本决定于用户网络的带宽限制。如在合成或识别下,默认的音频格式为 16000 HZ 采样,16 bit 精度,单声道, raw pcm,Little-endian;在未压缩的情况下,为 16000 * 16 = 256000 bit/s(比特每秒)。此时,如果要听写上传,或下载合成到的音频,时长为 t 秒,用户网络带宽为 x mpbs(兆比特每秒),则需要时长为 256000 * t / ( x(2^10)(2^10) ),假设 t 为 10,x 为 1,则10秒的音频,在1M的带宽下的传输时间约为 2.44s。
特别是在识别时,主要网络数据交互在音频的上传过程,此时网络上行带宽决定了音频上传的快慢,影响语音云服务器收到音频的快慢,继而决定了结果返回的快慢。
针对网络带宽的影响结果问题,MSC SDK 在识别音频上传和合成音频下载时,都做了相应的优化:
- 会话模式( ssm ),默认开启。只要 SDK 获取到一部份音频数据,就会开始上传到语音云服务器,而不是等到整段识别音频数据都获取到再上传。在实时的录制音频并进行识别时,此优化效果尤其明显:音频的录制需要时间,而 SDK 会利用这些时间,每录制到一小段音频,就开始上传到语音云服务器,且在有部份小分句(如有停顿的地方)的结果时,就会把结果返回给客户端。待音频录制完时,音频也已即将完成上传,此时,结果返回就更快,几乎能达到说完即得到结果的情况。更特别的,VAD(关于VAD的更多说明,参考《MSC Reference Manual.html》)生效的情况下,在应用层还未告知 SDK 已完成音频录制时,结果可能已返回。
- 音频压缩(aue),并默认开启。发送端对上下行的音频进行压缩(在客户端由 MSC SDK 自动压缩),压缩比约为 10:1,并在接收端解压还原(在客户端由 MSC SDK 自动解压),大大减少带宽占用,并减少网络交互的时间占用。
引擎模式
在引擎类型设置为离线引擎(TYPE_LOCAL)时,MSC SDK 提供两种方式(ENGINE_MODE)来使用离线功能:MSC,语记。
- MSC 模式(MODE_MSC),使用 MSC SDK 本身提供的离线模块,需要在下载SDK时,选择离线功能,以购买对应的离线功能资源;
- 语记模式(MODE_PLUS),使用讯飞语记应用提供的离线模式,离线功能资源由语记应用自带,不需要另外购买,但需要安装语记应用——应用在集成时,可以通过代码检查设备中是否已安装语记,并自动下载安装语记,检查已安装语记的资源等。可以通过以下链接 下载语记,或扫描下面的二维码下载,了解语记:
应用、MSC SDK 与语记间的关系大致如下图:
在引擎模式中,还有自动模式(MODE_AUTO),由SDK自动决定使用 MSC 模式,还是语记模式。在自动模式下,引擎类型为在线时,SDK 自动选择 MSC 模式;引擎类型为离线时,SDK 自动选择语记模式。
需要注意的是,为了方便应用的集成,MSC SDK 针对下仅有在线功能的 SDK,和含离线功能(唤醒、合成、识别)的 SDK,提供的 SDK 包会不一样:仅有在线功能的 SDK,默认的引擎模式为自动模式,方便应用使用语记提供的离线服务;含有离线功能的 SDK ,默认引擎模式为 MSC 模式,方便应用使用 MSC SDK 自带提供的离线服务。同时,为了减少 SDK 包的大小,在线 SDK 可能没有离线 SDK 的部份类。如果要混用 SDK 包,应用应明确指定使用的引擎模式。
在使用离线功能时,语记模式和 MSC 模式的区别在于,语记模式下,资源不必另外购买即可使用,但同时,资源覆盖程度也受语记的限制;MSC 模式下,如果有更多的需求,可以联系我们进行商务合作定制开发,提供更丰富的资源。
获取语记参数
用户可以通过语记中的资源下载(包括:识别资源、发音人资源)来提升语记离线能力,开发者可以通过以下接口获取当前语记包含的离线资源列表,此接口从语记1.032(99)版本开始支持。(通过getServiceVersion()获取版本号) 注:后续版本将支持获取语记当前设置的发音人字段
//1.设置所需查询的资源类型
/**
*1.PLUS_LOCAL_ALL: 本地所有资源
*2.PLUS_LOCAL_ASR: 本地识别资源
*3.PLUS_LOCAL_TTS: 本地合成资源
*/
String type = SpeechConstant.PLUS_LOCAL_ASR;
//2.获取当前《语记》包含资源列表
String resource = SpeechUtility.getUtility().getParameter(type);
//3.解析json-请参见下面示例及Demo中解析方法
{
"ret": 0,
"result": {
"version": 11,
"tts": [
{
"sex": "woman",
"language": "zh_cn",
"accent": "mandarin",
"nickname": "邻家姐姐",
"age": "22",
"name": "xiaojing"
},
{
"sex": "woman",
"language": "zh_cn",
"accent": "mandarin",
"nickname": "王老师",
"age": "24",
"name": "xiaoyan"
}
],
"asr": [
{
"domain": "asr",
"samplerate": "16000",
"language": "zh_cn",
"accent": "mandarin",
"name": "common"
}
]
}
}
作者:看了上面这两段文字就知道那个type和mode是什么了,然后我看到文档上面说可以改变语音声音的性别还有方言什么的,然后我就又找啊找,还上网查了一下有没有其他人写过,发现没有什么收获,这文档不是骗人的吧,想想也不可能,然后我就又想到我下载SDK的那个文件夹,发现原来源码的一些注解,还有规范都在里面
String engineType = SpeechConstant.TYPE_CLOUD; // 设置云端
String engineMode = SpeechConstant.MODE_AUTO;
mTts.setParameter( SpeechConstant.ENGINE_TYPE, engineType );// 设置云端
mTts.setParameter( SpeechConstant.ENGINE_MODE, engineMode );
mTts.setParameter(SpeechConstant.SPEED, "50"); // 设置语速
mTts.setParameter(SpeechConstant.VOLUME, "80"); // 设置音量,范围 0~100
// 设置合成音频保存位置(可自定义保存位置),保存在 “./sdcard/iflytek.pcm”
// 保存在 SD 卡需要在 AndroidManifest.xml 添加写 SD 卡权限
// 仅支持保存为 pcm 和 wav 格式, 如果不需要保存合成音频,注释该行代码
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, "./sdcard/iflytek.pcm");
mTts.setParameter( SpeechConstant.VOICE_NAME, "vils" ); // 设置发音人 默认是xiao yan
final String strTextToSpeech = mEtContent.getText().toString();
mTts.startSpeaking(strTextToSpeech, new SynthesizerListener() {
@Override
public void onSpeakBegin() {//开始播放
}
@Override
public void onSpeakPaused() {//暂停播放
}
@Override
public void onSpeakResumed() {//继续播放
}
@Override
public void onBufferProgress(int i, int i1, int i2, String s) {//合成进度
}
@Override
public void onSpeakProgress(int i, int i1, int i2) {//播放进度
}
@Override
public void onCompleted(SpeechError speechError) {
if (speechError == null) {
Toast.makeText(MainActivity.this,"播放完成 ",Toast.LENGTH_SHORT).show();
} else if (speechError != null ) {
Toast.makeText(MainActivity.this,speechError.getPlainDescription( true),Toast.LENGTH_SHORT).show();
}
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
// 以下代码用于获取与云端的会话 id,当业务出错时将会话 id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话 id为null
//if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
//}
}
});