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智能…...
数据分析师课程
数据分析是什么定义:运用统计分析方法对收集的数据进行汇总、理解和消化,最大化开发数据功能数据形式:观测值通过实验/测量获得,常以图表或表格呈现分类体系:描述性分析(初级):占日常…...
Chord视频时空定位惊艳效果:边界框动态跟踪+毫秒级时间戳可视化呈现
Chord视频时空定位惊艳效果:边界框动态跟踪毫秒级时间戳可视化呈现 1. 工具核心能力展示 Chord视频时空理解工具基于先进的Qwen2.5-VL架构开发,专门解决视频内容分析的复杂需求。与传统图像分析工具不同,Chord能够理解视频中的时序信息&…...
NoSleep防休眠工具:让系统持续运行的轻量级解决方案
NoSleep防休眠工具:让系统持续运行的轻量级解决方案 【免费下载链接】NoSleep Lightweight Windows utility to prevent screen locking 项目地址: https://gitcode.com/gh_mirrors/nos/NoSleep 在现代工作环境中,电脑意外休眠常常成为工作流程的…...
全栈实战:在快马平台从零到一开发一个可部署的极客日报应用
今天想和大家分享一个最近在InsCode(快马)平台上完成的实战项目——极客日报全栈应用开发。这个项目从零开始,完整实现了前后端分离的Web应用开发流程,特别适合想系统性学习全栈开发的同学参考。 项目架构设计 整个应用采用经典的三层架构:前…...
5分钟Mac本地跑通32B Qwen!免费GPT-4o替代,还能5分钟造个会开浏览器+执行Shell的AI Agent
1. 硬件与模型选择 配置:Apple M2 Pro(19 核 GPU)、32GB 统一内存。 推荐模型:mlx-community/Qwen2.5-Coder-32B-Instruct-4bit 4bit 量化后只占 18-22GB 内存专为代码和 Agent 优化,Tool Calling 能力强MLX 原生支持…...
Qwen3.5-9B应用场景:高校AI教学——图像题自动批改+实验报告生成
Qwen3.5-9B应用场景:高校AI教学——图像题自动批改实验报告生成 1. 高校教学场景的AI解决方案 在高校计算机和人工智能相关课程的教学中,教师常常面临两大挑战:一是需要批改大量学生提交的图像识别作业,二是需要指导学生完成规范…...
【Scratch×AI 系列 05】工程化实战:先统一目录(init),再拆分流水线(plan / exec-plan / build)
摘要 Scratch 项目最容易“做着做着就乱”:素材散落、版本混杂、产物找不到,AI 更是无从下手xw-scratch-init 不是“创建文件夹”,而是把协作与自动化的前提一次性铺好把流程拆成 plan → exec-plan → build,是为了把 AI 从“胡写…...
重构游戏串流体验:Sunshine如何突破设备与场景限制
重构游戏串流体验:Sunshine如何突破设备与场景限制 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 当你想在平板上玩3A游戏时,最大的障碍是什么?…...
如何高效处理asar文件?WinAsar让Electron资源管理变得简单
如何高效处理asar文件?WinAsar让Electron资源管理变得简单 【免费下载链接】WinAsar Portable and lightweight GUI utility to pack and extract asar( Electron archive ) files, Only 551 KB! 项目地址: https://gitcode.com/gh_mirrors/wi/WinAsar 还在为…...
拯救者BIOS终极解锁:告别隐藏设置,完全掌控你的笔记本电脑
拯救者BIOS终极解锁:告别隐藏设置,完全掌控你的笔记本电脑 【免费下载链接】LEGION_Y7000Series_Insyde_Advanced_Settings_Tools 支持一键修改 Insyde BIOS 隐藏选项的小工具,例如关闭CFG LOCK、修改DVMT等等 项目地址: https://gitcode.c…...
