android tts播报破音解决方案汇总

2024-01-30 19:44

本文主要是介绍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播报破音解决方案汇总的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/661335

相关文章

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

SpringSecurity显示用户账号已被锁定的原因及解决方案

《SpringSecurity显示用户账号已被锁定的原因及解决方案》SpringSecurity中用户账号被锁定问题源于UserDetails接口方法返回值错误,解决方案是修正isAccountNon... 目录SpringSecurity显示用户账号已被锁定的解决方案1.问题出现前的工作2.问题出现原因各

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Java死锁问题解决方案及示例详解

《Java死锁问题解决方案及示例详解》死锁是指两个或多个线程因争夺资源而相互等待,导致所有线程都无法继续执行的一种状态,本文给大家详细介绍了Java死锁问题解决方案详解及实践样例,需要的朋友可以参考下... 目录1、简述死锁的四个必要条件:2、死锁示例代码3、如何检测死锁?3.1 使用 jstack3.2