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

相关文章

Python按照24个实用大方向精选的上千种工具库汇总整理

《Python按照24个实用大方向精选的上千种工具库汇总整理》本文整理了Python生态中近千个库,涵盖数据处理、图像处理、网络开发、Web框架、人工智能、科学计算、GUI工具、测试框架、环境管理等多... 目录1、数据处理文本处理特殊文本处理html/XML 解析文件处理配置文件处理文档相关日志管理日期和

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

kkFileView在线预览office的常见问题以及解决方案

《kkFileView在线预览office的常见问题以及解决方案》kkFileView在线预览Office常见问题包括base64编码配置、Office组件安装、乱码处理及水印添加,解决方案涉及版本适... 目录kkFileView在线预览office的常见问题1.base642.提示找不到OFFICE组件

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

WinForm跨线程访问UI及UI卡死的解决方案

《WinForm跨线程访问UI及UI卡死的解决方案》在WinForm开发过程中,跨线程访问UI控件和界面卡死是常见的技术难题,由于Windows窗体应用程序的UI控件默认只能在主线程(UI线程)上操作... 目录前言正文案例1:直接线程操作(无UI访问)案例2:BeginInvoke访问UI(错误用法)案例

Spring Security常见问题及解决方案

《SpringSecurity常见问题及解决方案》SpringSecurity是Spring生态的安全框架,提供认证、授权及攻击防护,支持JWT、OAuth2集成,适用于保护Spring应用,需配置... 目录Spring Security 简介Spring Security 核心概念1. ​Securit

MySQL逻辑删除与唯一索引冲突解决方案

《MySQL逻辑删除与唯一索引冲突解决方案》本文探讨MySQL逻辑删除与唯一索引冲突问题,提出四种解决方案:复合索引+时间戳、修改唯一字段、历史表、业务层校验,推荐方案1和方案3,适用于不同场景,感兴... 目录问题背景问题复现解决方案解决方案1.复合唯一索引 + 时间戳删除字段解决方案2:删除后修改唯一字