展示型網(wǎng)站制作公司中國體育新聞
導(dǎo)航app引導(dǎo)中經(jīng)常遇到破音,這里也將之前經(jīng)歷過的方案收集以下,方便以后選擇:
1 對于開始和結(jié)尾破音: 可以用升降音來處理
? 兩種方式
? 一種是 直接對開始和結(jié)束的時(shí)間段進(jìn)行音量直接漸進(jìn)改變。這里配的是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);
? ? ? ? }
? 一種是 開始的那幀數(shù)據(jù)進(jìn)行音量從零漸進(jìn)增加到當(dāng)前音量,結(jié)束的那幾幀數(shù)據(jù)進(jìn)行音量從當(dāng)前音量降到零
? ? ? /**
? ? ?* 對音頻數(shù)據(jù)做 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進(jìn)程會(huì)受當(dāng)前app的業(yè)務(wù)影響,導(dǎo)致tts 不穩(wěn)定,尤其是導(dǎo)航app,大量的cpu,內(nèi)存占用是常有的事,可單獨(dú)放到一個(gè)獨(dú)立進(jìn)程里,并且啟動(dòng)個(gè)前臺(tái)服務(wù)提高優(yōu)先級。
? 怎么兩個(gè)進(jìn)程溝通呢,由于是低頻的溝通,直接廣播即可。
3 不固定位置的破音:直接控制tts解析出來的數(shù)據(jù)塊
? ?原理:破音由于系統(tǒng)處理的數(shù)據(jù)不足,或數(shù)據(jù)塞入間隔時(shí)間過長過短,我們這里直接控制每次寫入的數(shù)據(jù)大小及間隔數(shù)據(jù):
? ?詳細(xì)看下代碼(系統(tǒng)不同,代碼效果也不一樣,要和系統(tǒng)tts端配合,而且要能拿到tts解析數(shù)據(jù),我們是自己的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;
? ? //系統(tǒng)一次處理的數(shù)據(jù)塊的最小值,小于的話,就會(huì)破音
? ? private static int RateSize = 1900;
? ? private SyncStack syncStack = new SyncStack();
? ? private long oldTime = 0;
? ? private ExecutorService pool = Executors.newSingleThreadExecutor();
? ? //類似生產(chǎn)者,消費(fèi)者的一個(gè)讀寫類(每寫一次,都給一次取的機(jī)會(huì),目的是不耽誤取出播報(bào)的節(jié)奏)
? ? 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;
? ? ? ? ? ? ? ? //空出機(jī)會(huì)給寫入線程機(jī)會(huì)
? ? ? ? ? ? ? ? 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后不再等待數(shù)據(jù),自動(dòng)結(jié)束流程
? ? ? ? ? ? ? ? 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 服務(wù)會(huì)不停的傳過來解析出來的據(jù)
? ? public void startPush(byte[] data) {
? ? ? ? syncStack.push(data);
? ? }
? ? //啟動(dòng)播報(bào)線程
? ? 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 {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //等待先寫入數(shù)據(jù)一定的數(shù)據(jù),防止進(jìn)來就破音
? ? ? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(getStartTime());
? ? ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? audioTrack.play();
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? while ((simpleBytes = syncStack.pop()) != null) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? while (simpleBytes.length < RateSize) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //一次取的不夠,先等待最小間隔時(shí)間再操作
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 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();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? });
? ? }
? ? /**
? ? ?* 啟動(dòng)播放線程
? ? ?*/
? ? private void startWrite() {
? ? ? ? //需先等待最小的間隔時(shí)間,保持播報(bào)節(jié)奏
? ? ? ? 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();
? ? }
}
該方案需要自己調(diào)時(shí)間間隔值,沒有一個(gè)固定的答案。