在Android开发中,语音播报经常使用,但是,在使用过程中会发现,如果语音来源过于块,频率过高,在前一条没有播放完的情况下会执行第二条播放,本篇文章用来解决这个问题。
一、科大讯飞语音集成:
1、在科大讯飞的开发者平台(科大讯飞开放平台)注册。我这里使用的是在线语音合成,下载SDK包。
2、将SDK包里面的libs里面的.so文件和jar包复制到你的项目中,并且引用.so文件和jar包,这里要注意的是,个人建议将所有CPU类型的.so文件都放到你的项目中,这样保证在所有设备中都能播放,虽然这样会导致项目变大。
3、在MyApplication中初始化:
SpeechUtility.createUtility(this, SpeechConstant.APPID + "=******");
注意这里的APPID,不要丢了这个“=”号。
4、在使用到语音播放的地方,初始化语音引擎:
private SpeechSynthesizer mTts = SpeechSynthesizer.createSynthesizer(context, mTtsInitListener);
5、初始化中有两个参数,第一个是上下文,第二个是初始化回调:
private InitListener mTtsInitListener = new InitListener() {
@Override
public void onInit(int code) {
if (code != ErrorCode.SUCCESS) {
Toast.showToast(context, "语音初始化失败,错误码:" + code);
} else {
AppLog.i("语音初始化成功");
// 初始化成功,之后可以调用startSpeaking方法
// 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
// 正确的做法是将onCreate中的startSpeaking调用移至这里
}
}
};
6、播放:
public void playVoice(String msg) {
mTts.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
mTts.setParameter(SpeechConstant.SPEED, "60"); //播放速度
mTts.setParameter(SpeechConstant.VOLUME, "80"); //播放音量
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD); //引擎类型
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, "./sdcard/iflyket.pcm"); //声音文件地址
mTts.startSpeaking(msg, mTtsListener);
}
这里面可以配置播放人声(SpeechConstant.VOICE_NAME)、声音大小(peechConstant.VOLUME)、语速(SpeechConstant.SPEED),如果你发现你选择的人声无法通过手机系统声音控制声音大小时,将人声换成系统默认的“xiaoyan”,在mTts.startSpeaking(msg, mTtsListener);
配置中,第一个参数就是要播放的内容,第二个是播放回调:
private SynthesizerListener mTtsListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() {
// AppLog.i("onSpeakBegin");
}
@Override
public void onSpeakPaused() {
// AppLog.i("onSpeakPaused");
}
@Override
public void onSpeakResumed() {
// AppLog.i("onSpeakResumed");
}
@Override
public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
}
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
}
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
if (!queue.isEmpty()) {
presenter.playVoice(queue.poll());
} else {
isFirst = true;
}
} else if (error != null) {
AppLog.i(error.getPlainDescription(true));
}
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代码用于获取与云端的会话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);
// }
}
};
播放回调看法不言而喻了。
二、重点来了 解决播放跳跃问题:
1、首先来看下这个场景:比如有一个收银系统消息接受终端,每收一笔钱都播放一条“收款成功xxx元”,如果收银速度过款人客流大,当还没播放完第一条语音的时候,第二次收银已经结束了,此时一般情况就会每次播放都直接调用playVioce(String msg)这个这个方法,此时第一条没完,知己播放第二条了。还有一个情景就是,集成高德地图,语音是要自己合成的,同样用科大讯飞来播放路况,当路况复杂的情况下会出现一样的问题。
2、解决问题:解决这个问题的方法就是使用队列:
Queue<String> queue = new LinkedList<String>();
在我的项目的场景就是,每次收款一次,都会受到一次推送一串String,我要播放的就是这条String
//EvenBus接受推送过来的消息广播
@Subscribe(threadMode = ThreadMode.MAIN)
public void getMessage(PushEvent pushEvent) {
Log.i("tag","收款:" + pushEvent.getMessage());
String msg = pushEvent.getMessage();
queue.offer(msg);
//此处需要将语音添加到队列,保证一条播放完再播放下一条
//必须第一次收到消息才能在这播放,以后每次收到消息将消息添加到队列,每次播放完后去队列里面取;
if (isFirst) {
playVoice(queue.poll());
//保证播放方法这里只执行第一次
isFirst = !isFirst;
}
}
明白的童鞋知道我这里使用的EvenBus来接受推送的消息,可以忽略。将推送过来的消息,放到队列queue中,注意,这里使用的是queue.offer(msg);
offer()这个方法,队列添加还有add()这个方法,这两个方法的不同之处就是offer在添加失败的时候不抛异常,所以不建议使用add()方法。使用
queue.poll()来出去队列中的数据,队列是先进先出的,每次poll都会将该条消息从队列中清除。
这里使用的一个变量isFirst来控制播放,只有初次播放的时候才在getMessage()中使用playVoice()方法,这里解决了每次收到消息后都会触发getMessage()这个方法里面的playVoice()。如果每次收到推送消息都调用playVoice(queue.poll());方法会导致少播放一条消息。
在播放回调的onCompleted()方法中继续取出队列里的消息播放,
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
if (!queue.isEmpty()) {
presenter.playVoice(queue.poll());
} else {
isFirst = true;
}
} else if (error != null) {
AppLog.i(error.getPlainDescription(true));
}
}
看代码,当队列不为空的时候就继续取出队列里的消息播放,知道为空的时候将isFirst 置为true,否则在全部播放完后不能再次播放了。
三、这样就很完美的解决了播放跳跃问题,这样,无论有多少条消息,多频繁,每次播放都从队列里面取出,这样就达到了播放完一条再播放下一条的问题了。
四、由于这个例子是从自己项目中抽取出来的,没办法上源码,抱歉,写的不好的话请多指正!