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智能…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
