当前位置: 首页 > news >正文

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播报破音解决方案汇总

导航app引导中经常遇到破音&#xff0c;这里也将之前经历过的方案收集以下&#xff0c;方便以后选择&#xff1a; 1 对于开始和结尾破音&#xff1a; 可以用升降音来处理 两种方式 一种是 直接对开始和结束的时间段进行音量直接渐进改变。这里配的是200ms的渐变。 VolumeSha…...

2024年新提出的算法:一种新的基于数学的优化算法——牛顿-拉夫森优化算法|Newton-Raphson-based optimizer,NRBO

1、简介 开发了一种新的元启发式算法——Newton-Raphson-Based优化器&#xff08;NRBO&#xff09;。NRBO受到Newton-Raphson方法的启发&#xff0c;它使用两个规则&#xff1a;Newton-Raphson搜索规则&#xff08;NRSR&#xff09;和Trap Avoidance算子&#xff08;TAO&#…...

笔记 | Clickhouse 命令行连接及查询

在 ClickHouse 中&#xff0c;可以使用命令行客户端执行查询。默认情况下&#xff0c;ClickHouse 的命令行客户端称为 clickhouse-client。下面是一些基本的步骤和示例&#xff0c;用于使用 clickhouse-client 进行查询。 首先&#xff0c;需要确保已经安装了 ClickHouse 服务…...

设计模式—行为型模式之责任链模式

设计模式—行为型模式之责任链模式 责任链&#xff08;Chain of Responsibility&#xff09;模式&#xff1a;为了避免请求发送者与多个请求处理者耦合在一起&#xff0c;于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链&#xff1b;当有请求发生时&am…...

如何使用Python+Flask搭建本地Web站点并结合内网穿透公网访问?

文章目录 前言1. 安装部署Flask并制作SayHello问答界面2. 安装Cpolar内网穿透3. 配置Flask的问答界面公网访问地址4. 公网远程访问Flask的问答界面 前言 Flask是一个Python编写的Web微框架&#xff0c;让我们可以使用Python语言快速实现一个网站或Web服务&#xff0c;本期教程…...

【C语言】【力扣】刷题小白的疑问

一、力扣做题时的答案&#xff0c;没有完整的框架 疑问&#xff1a; 在学习C语言的初始&#xff0c;就知道C语言程序离不开下面这个框架&#xff0c;为什么力扣题的解答往往没有这个框架&#xff1f; #include <stdio.h>int main() {return 0; } 解答&#xff1a; 力扣平…...

【Python】03快速上手爬虫案例三:搞定药师帮

文章目录 前言1、破解验证码2、获取数据 前言 提示&#xff1a;通过用户名、密码、搞定验证码&#xff0c;登录进药师帮网站&#xff0c;然后抓取想要的数据。 爬取数据&#xff0c;最终效果图&#xff1a; 1、破解验证码 使用药师帮测试系统&#xff1a;https://dianrc.ysb…...

C++异步编程

thread std::thread 类代表一个单独的执行线程。在创建与线程对象相关联时&#xff0c;线程会立即开始执行&#xff08;在等待操作系统调度的延迟之后&#xff09;&#xff0c;从构造函数参数中提供的顶层函数开始执行。顶层函数的返回值被忽略&#xff0c;如果它通过抛出异常…...

dfs专题(记忆化搜索)P1141 01迷宫——洛谷(题解)

题目描述 有一个仅由数字 00 与 11 组成的 &#xfffd;&#xfffd;nn 格迷宫。若你位于一格 00 上&#xff0c;那么你可以移动到相邻 44 格中的某一格 11 上&#xff0c;同样若你位于一格 11 上&#xff0c;那么你可以移动到相邻 44 格中的某一格 00 上。 你的任务是&#…...

pip 安装出现报错 SSLError(SSLError(“bad handshake

即使设置了清华源&#xff1a; pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplepip 安装包不能配置清华源&#xff0c;出现报错: Retrying (Retry(total2, connectNone, readNone, redirectNone, statusNone)) after connection broken by ‘SSLE…...

新概念英语第二册(46)

【New words and expressions】生词和短语&#xff08;12&#xff09; unload v. 卸&#xff08;货&#xff09; wooden adj. 木制的 extremely adv. 非常&#xff0c;极其 occur …...

动态规划入门题目

动态规划&#xff08;记忆化搜索&#xff09;&#xff1a; 将给定问题划分成若干子问题&#xff0c;直到子问题可以被直接解决。然后把子问题的答保存下来以免重复计算&#xff0c;然后根据子问题反推出原问题解的方法 动态规划也称为递推&#xff08;暴力深搜记忆中间状态结果…...

探索云性能测试的各项功能有哪些?

云性能测试作为现代软件开发和部署过程中不可或缺的一环&#xff0c;为确保系统在各种条件下的高效运行提供了关键支持。本文将介绍云性能测试的各项功能&#xff0c;帮助您更好地了解其在软件开发生命周期中的重要性。 1. 负载测试 云性能测试的首要功能之一是负载测试。通过模…...

(大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量

今天&#xff0c;面试了一家公司&#xff0c;什么也不说先来三道面试题做做&#xff0c;第一题。 那么&#xff0c;我们就开始做题吧&#xff0c;谁叫我们是打工人呢。 题目是这样的&#xff1a; 统计除豪车外&#xff0c;销售最差的车 车辆按批销售&#xff0c;每次销售若干…...

Git安装,Git镜像,Git已安装但无法使用解决经验

git下载地址&#xff1a; Git - 下载 (git-scm.com) <-git官方资源 Git for Windows (github.com) <-github资源 CNPM Binaries Mirror (npmmirror.com) <-阿里镜像&#xff08;推荐&#xff0c;镜…...

Python与CAD系列高级篇(二十五)分类提取坐标到excel(补充圆半径、线长度、圆弧)

目录 0 简述1 分类提取坐标到excel2 结果展示0 简述 上一篇中介绍了:对点、直线、多段线、圆、样条曲线分类读取坐标并提取到excel。考虑到进一步提取图形信息,此篇补充对圆半径、线长度以及圆弧几何信息的提取。 1 分类提取坐标到excel 代码实现: import math import nump…...

Linux安装Influxdb

Linux安装Influxdb 1、安装步骤1.1、安装Influxdb步骤1.2、Influxdb默认安装路径1.3、命令行操作Influxdb&#xff0c;建库&#xff0c;建用户1.3.1 进入influxdb命令行1.3.2 创建用户1.3.2 库查询和创建 1、安装步骤 1.1、安装Influxdb步骤 yum install -y wget #下载安装包…...

Flutter CustomPainter 属性介绍与使用

Flutter 中的 CustomPainter 是一个强大的工具&#xff0c;允许开发者通过自定义绘制来创建各种复杂的图形和动画。本文将介绍 CustomPainter 的一些重要属性以及如何使用它们来实现自定义绘制。 1. CustomPainter 简介 CustomPainter 是一个抽象类&#xff0c;用于自定义绘制…...

基于Javaweb开发的二手图书零售系统详细设计【附源码】

基于Javaweb开发的二手图书零售系统详细设计【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统…...

【JaveWeb教程】(35)SpringBootWeb案例之《智能学习辅助系统》登录功能的详细实现步骤与代码示例(8)

目录 案例-登录和认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 案例-登录和认证 在前面的课程中&#xff0c;我们已经实现了部门管理、员工管理的基本功能&#xff0c;但是大家会发现&#xff0c;我们并没有登录&#xff0c;就直接访问到了Tlias智能…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...