本文主要是介绍android tts播报破音解决方案汇总,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
导航app引导中经常遇到破音,这里也将之前经历过的方案收集以下,方便以后选择:
1 对于开始和结尾破音: 可以用升降音来处理
两种方式
一种是 直接对开始和结束的时间段进行音量直接渐进改变。这里配的是200ms的渐变。
VolumeShaper.Configuration cfg_out= null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
cfg_out = new VolumeShaper.Configuration.Builder()
.setCurve(new float[]{0f,1f},new float[]{1f,0f})
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
.setDuration(200)
.build();
VolumeShaper vShaper = mAudioTrack.createVolumeShaper(cfg_out);
vShaper.apply(VolumeShaper.Operation.PLAY);
}
一种是 开始的那帧数据进行音量从零渐进增加到当前音量,结束的那几帧数据进行音量从当前音量降到零
/**
* 对音频数据做 fade out
* @param byteBuffer byteBuffer
* @param channelCount channelCount
*/
private ByteBuffer shortFadeOut(ByteBuffer byteBuffer, int channelCount) {
int shortCount = byteBuffer.limit() / 2;
if(1 == channelCount) {
for(int i = 0; i < shortCount; i++) {
short data = (short) (byteBuffer.getShort(i * 2) * 1.0f * (shortCount - i) / (2*shortCount));
byteBuffer.putShort(i * 2, data);
}
} else {
for(int i = 0; i < shortCount; i += 2) {
short data = (short) (byteBuffer.getShort(i * 2) * 1.0f * (shortCount - i) / (2*shortCount));
byteBuffer.putShort(i * 2, data);
data = (short)(byteBuffer.getShort((i + 1) * 2) * 1.0f * (shortCount - i) / (2*shortCount));
byteBuffer.putShort((i + 1) * 2, data);
}
}
byteBuffer.rewind();
return byteBuffer;
}
2 适用于自己的tts引擎
tts放入app进程会受当前app的业务影响,导致tts 不稳定,尤其是导航app,大量的cpu,内存占用是常有的事,可单独放到一个独立进程里,并且启动个前台服务提高优先级。
怎么两个进程沟通呢,由于是低频的沟通,直接广播即可。
3 不固定位置的破音:直接控制tts解析出来的数据块
原理:破音由于系统处理的数据不足,或数据塞入间隔时间过长过短,我们这里直接控制每次写入的数据大小及间隔数据:
详细看下代码(系统不同,代码效果也不一样,要和系统tts端配合,而且要能拿到tts解析数据,我们是自己的tts引擎):
public class AudioTrackManager {
public static final String TAG = "AudioTrackManager";
private AudioTrack audioTrack;
private static AudioTrackManager mInstance;
private int bufferSize;
private byte[] simpleBytes = null;
private int writeRate = 180;
private int pushRate = 90;
//系统一次处理的数据块的最小值,小于的话,就会破音
private static int RateSize = 1900;
private SyncStack syncStack = new SyncStack();
private long oldTime = 0;
private ExecutorService pool = Executors.newSingleThreadExecutor();
//类似生产者,消费者的一个读写类(每写一次,都给一次取的机会,目的是不耽误取出播报的节奏)
class SyncStack {
LinkedBlockingQueue<byte[]> datas = new LinkedBlockingQueue<byte[]>();
long oldTime = 0;
public void clearData(){
datas.clear();
}
public synchronized void push(byte[] data) {
try {
datas.put(data);
long time = System.currentTimeMillis()-oldTime;
//空出机会给写入线程机会
if (time > pushRate) {
time = 5;
} else {
time = pushRate - time;
}
if(time>0) {
wait(time);
}
oldTime = System.currentTimeMillis();
} catch (InterruptedException e) {
e.printStackTrace();
}
// this.notify();
}
public synchronized byte[] pop() throws InterruptedException {
if (datas == null || datas.size() == 0) {
//50ms后不再等待数据,自动结束流程
if (datas == null || datas.size() == 0) {
wait(50);
}
if(datas==null||datas.size()==0) {
return null;
}
}
return datas.take();
}
}
public AudioTrackManager() {
bufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioPolicyManager.STREAM_NAVI, 8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
}
private void initTrack() {
if (audioTrack == null) {
bufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioPolicyManager.STREAM_NAVI, 8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
}
}
public static AudioTrackManager getInstance() {
if (mInstance == null) {
synchronized (AudioTrackManager.class) {
if (mInstance == null) {
mInstance = new AudioTrackManager();
}
}
}
return mInstance;
}
public void startReady() {
initTrack();
if(syncStack!=null) {
syncStack.clearData();
}else{
syncStack = new SyncStack();
}
}
//System.arraycopy()方法
public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
byte[] bt3 = new byte[bt1.length + bt2.length];
System.arraycopy(bt1, 0, bt3, 0, bt1.length);
System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
return bt3;
}
/**
* 停止播放
*/
public void stopPlay() {
try {
//destroyThread();
Log.v(TAG, "yangtest--stopTTS");
if(syncStack!=null){
syncStack.clearData();
}
if (audioTrack != null) {
if (audioTrack.getState() == AudioRecord.STATE_INITIALIZED) {
audioTrack.stop();
}
if (audioTrack != null) {
audioTrack.release();
}
audioTrack = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//tts 服务会不停的传过来解析出来的据
public void startPush(byte[] data) {
syncStack.push(data);
}
//启动播报线程
public void startPop() {
Log.e("yangtest","startpop-bufferSize-"+bufferSize);
pool.execute(
new Runnable(){
public void run() {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);
try {
//等待先写入数据一定的数据,防止进来就破音
Thread.sleep(getStartTime());
} catch (InterruptedException e) {
e.printStackTrace();
}
audioTrack.play();
try {
while ((simpleBytes = syncStack.pop()) != null) {
while (simpleBytes.length < RateSize) {
try {
//一次取的不够,先等待最小间隔时间再操作
Thread.sleep(writeRate);
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] temp = syncStack.pop();
if (temp != null) {
simpleBytes = byteMerger(simpleBytes, temp);
} else {
Log.e("yangtest", "no-data");
break;
}
}
startWrite();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (endPlay != null) {
endPlay.onEnd();
}
}
});
}
/**
* 启动播放线程
*/
private void startWrite() {
//需先等待最小的间隔时间,保持播报节奏
long timelen = System.currentTimeMillis() - oldTime;
if (timelen < writeRate) {
try {
Thread.sleep(writeRate - timelen);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
oldTime = System.currentTimeMillis();
audioTrack.write(simpleBytes, 0, simpleBytes.length);
simpleBytes = null;
}
public long getStartTime(){
int txtLen = BdTTSPlayer.speechs.length();
int len = 60 + txtLen * 10;
return len;
}
public void setEndPlay(EndPlay endPlay) {
this.endPlay = endPlay;
}
EndPlay endPlay;
interface EndPlay {
public void onEnd();
}
}
该方案需要自己调时间间隔值,没有一个固定的答案。
这篇关于android tts播报破音解决方案汇总的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!