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引导中经常遇到破音,这里也将之前经历过的方案收集以下,方便以后选择: 1 对于开始和结尾破音: 可以用升降音来处理 两种方式 一种是 直接对开始和结束的时间段进行音量直接渐进改变。这里配的是200ms的渐变。 VolumeSha…...
2024年新提出的算法:一种新的基于数学的优化算法——牛顿-拉夫森优化算法|Newton-Raphson-based optimizer,NRBO
1、简介 开发了一种新的元启发式算法——Newton-Raphson-Based优化器(NRBO)。NRBO受到Newton-Raphson方法的启发,它使用两个规则:Newton-Raphson搜索规则(NRSR)和Trap Avoidance算子(TAO&#…...
笔记 | Clickhouse 命令行连接及查询
在 ClickHouse 中,可以使用命令行客户端执行查询。默认情况下,ClickHouse 的命令行客户端称为 clickhouse-client。下面是一些基本的步骤和示例,用于使用 clickhouse-client 进行查询。 首先,需要确保已经安装了 ClickHouse 服务…...
设计模式—行为型模式之责任链模式
设计模式—行为型模式之责任链模式 责任链(Chain of Responsibility)模式:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时&am…...
如何使用Python+Flask搭建本地Web站点并结合内网穿透公网访问?
文章目录 前言1. 安装部署Flask并制作SayHello问答界面2. 安装Cpolar内网穿透3. 配置Flask的问答界面公网访问地址4. 公网远程访问Flask的问答界面 前言 Flask是一个Python编写的Web微框架,让我们可以使用Python语言快速实现一个网站或Web服务,本期教程…...
【C语言】【力扣】刷题小白的疑问
一、力扣做题时的答案,没有完整的框架 疑问: 在学习C语言的初始,就知道C语言程序离不开下面这个框架,为什么力扣题的解答往往没有这个框架? #include <stdio.h>int main() {return 0; } 解答: 力扣平…...
【Python】03快速上手爬虫案例三:搞定药师帮
文章目录 前言1、破解验证码2、获取数据 前言 提示:通过用户名、密码、搞定验证码,登录进药师帮网站,然后抓取想要的数据。 爬取数据,最终效果图: 1、破解验证码 使用药师帮测试系统:https://dianrc.ysb…...
C++异步编程
thread std::thread 类代表一个单独的执行线程。在创建与线程对象相关联时,线程会立即开始执行(在等待操作系统调度的延迟之后),从构造函数参数中提供的顶层函数开始执行。顶层函数的返回值被忽略,如果它通过抛出异常…...
dfs专题(记忆化搜索)P1141 01迷宫——洛谷(题解)
题目描述 有一个仅由数字 00 与 11 组成的 ��nn 格迷宫。若你位于一格 00 上,那么你可以移动到相邻 44 格中的某一格 11 上,同样若你位于一格 11 上,那么你可以移动到相邻 44 格中的某一格 00 上。 你的任务是&#…...
pip 安装出现报错 SSLError(SSLError(“bad handshake
即使设置了清华源: pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplepip 安装包不能配置清华源,出现报错: Retrying (Retry(total2, connectNone, readNone, redirectNone, statusNone)) after connection broken by ‘SSLE…...
新概念英语第二册(46)
【New words and expressions】生词和短语(12) unload v. 卸(货) wooden adj. 木制的 extremely adv. 非常,极其 occur …...
动态规划入门题目
动态规划(记忆化搜索): 将给定问题划分成若干子问题,直到子问题可以被直接解决。然后把子问题的答保存下来以免重复计算,然后根据子问题反推出原问题解的方法 动态规划也称为递推(暴力深搜记忆中间状态结果…...
探索云性能测试的各项功能有哪些?
云性能测试作为现代软件开发和部署过程中不可或缺的一环,为确保系统在各种条件下的高效运行提供了关键支持。本文将介绍云性能测试的各项功能,帮助您更好地了解其在软件开发生命周期中的重要性。 1. 负载测试 云性能测试的首要功能之一是负载测试。通过模…...
(大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量
今天,面试了一家公司,什么也不说先来三道面试题做做,第一题。 那么,我们就开始做题吧,谁叫我们是打工人呢。 题目是这样的: 统计除豪车外,销售最差的车 车辆按批销售,每次销售若干…...
Git安装,Git镜像,Git已安装但无法使用解决经验
git下载地址: Git - 下载 (git-scm.com) <-git官方资源 Git for Windows (github.com) <-github资源 CNPM Binaries Mirror (npmmirror.com) <-阿里镜像(推荐,镜…...
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,建库,建用户1.3.1 进入influxdb命令行1.3.2 创建用户1.3.2 库查询和创建 1、安装步骤 1.1、安装Influxdb步骤 yum install -y wget #下载安装包…...
Flutter CustomPainter 属性介绍与使用
Flutter 中的 CustomPainter 是一个强大的工具,允许开发者通过自定义绘制来创建各种复杂的图形和动画。本文将介绍 CustomPainter 的一些重要属性以及如何使用它们来实现自定义绘制。 1. CustomPainter 简介 CustomPainter 是一个抽象类,用于自定义绘制…...
基于Javaweb开发的二手图书零售系统详细设计【附源码】
基于Javaweb开发的二手图书零售系统详细设计【附源码】 🍅 作者主页 央顺技术团队 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 文末获取源码联系方式 📝 🍅 查看下方微信号获取联系方式 承接各种定制系统…...
【JaveWeb教程】(35)SpringBootWeb案例之《智能学习辅助系统》登录功能的详细实现步骤与代码示例(8)
目录 案例-登录和认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 案例-登录和认证 在前面的课程中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登录,就直接访问到了Tlias智能…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
