基于AI的语音合成软件

基于AI的语音合成软件

wxh Lv2

前言

之前有想过开发一款基于在线基于大模型的文本转语音(TTS)的安卓应用,记得当时还是搞测试的哥们儿喜欢看小说我答应人家去写这么个应用来着.当时尝试开发着试了试虽然能发出声音但是线程控制,与Android系统交互这些细节做的是一塌糊涂,最终也没好意思把打包好的应用交出手.

后来是家里我妈想要听书,实在是不想让我妈用什么七猫阅读软件(全是广告),当然是用Legado / 开源阅读阅读软件搭配我自己写的TTS软件VolcengineTTS来达到最完美的听书体验啦XD

说干就干

开发

相关概念

Gradle

Gradle是一个现代化的 构建自动化工具 ,主要用于Java、Kotlin和Android项目的构建管理。

核心作用
  • 构建项目 - 把源代码编译成可运行的应用程序

  • 管理依赖 - 自动下载和管理第三方库 打包发布 - 生成APK、JAR等发布文件

Gradle和Maven的关系
共享仓库标准
  • Maven仓库是行业标准 :Maven Central、JCenter、Google Maven仓库等已经成为Java/Kotlin生态的标准依赖仓库
  • Gradle兼容Maven仓库 :Gradle可以直接使用Maven仓库,无需额外配置
  • 统一的坐标系统 :两者都使用 groupId:artifactId:version 的坐标格式
Gradle相比Maven的优势
  1. 构建性能
  • 增量构建 :Gradle只重新编译变化的部分
  • 构建缓存 :可以缓存构建结果
  • 并行执行 :支持并行任务执行
  1. 灵活性
  • 基于Groovy/Kotlin DSL :比Maven的XML更灵活
  • 插件系统 :更强大的插件机制
  • 多项目构建 :更好的多模块支持
  1. Android开发首选
  • 官方支持 :Google官方推荐用于Android开发
  • Android插件 :专门的Android Gradle插件
  • Compose支持 :更好的Jetpack Compose集成

AndroidX

AndroidX 是Android官方推出的新一代Android支持库,它取代了旧的Android Support Library。简单来说,AndroidX是Google为了统一和现代化Android开发而创建的官方库集合。

核心组件
  • androidx.core.ktx - 提供Kotlin扩展函数,简化Android API使用
  • androidx.activity.compose - 支持Compose的Activity组件
生命周期管理
  • androidx.lifecycle.runtime.ktx - 生命周期管理
  • androidx.lifecycle.viewmodel.ktx - ViewModel支持
  • androidx.lifecycle.viewmodel.compose - Compose中的ViewModel集成
Jetpack Compose(现代UI框架)
  • androidx.compose.bom - Compose物料清单,统一版本管理
  • androidx.compose.ui - Compose UI核心
  • androidx.compose.material3 - Material Design 3组件
  • androidx.compose.material.icons.extended - 图标库

应用清单文件 (Application Manifest)

应用清单文件也被称为Manifest文件,是Android应用的核心配置文件。它就像是应用的”身份证”和”说明书”,告诉Android系统关于你应用的所有重要信息。

AndroidManifest.xml的作用

简单说,这个文件 定义了应用的基本信息和组件结构 ,让Android系统知道:

  1. 应用是什么 (包名、版本、图标等)
  2. 应用能做什么 (需要的权限)
  3. 应用包含什么 (Activity、Service等组件)
  4. 应用如何启动 (主入口点)
为什么这个文件如此重要?
  1. 系统识别 :没有它,Android系统不知道你的应用存在
  2. 权限控制 :定义应用需要访问的系统资源
  3. 组件注册 :声明所有的Activity、Service等
  4. Intent过滤 :定义应用能响应哪些系统事件

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
VolcengineTTS/
├── .gitignore
├── LICENSE
├── README.md
├── app/ # 应用模块
│ ├── .gitignore
│ ├── build.gradle.kts # 应用构建配置
│ ├── proguard-rules.pro # 代码混淆规则
│ └── src/
│ ├── androidTest/ # Android测试代码
│ │ └── java/
│ ├── main/ # 主代码目录
│ │ ├── AndroidManifest.xml # 应用清单文件
│ │ ├── ic_launcher-playstore.png
│ │ ├── java/ # Java/Kotlin源代码
│ │ │ └── com/github/lonepheasantwarrior/volcenginetts/
│ │ │ ├── MainActivity.kt # 主界面
│ │ │ ├── TTSApplication.kt # 应用类(主上下文)
│ │ │ ├── common/ # 通用工具类
│ │ │ │ ├── Constants.java
│ │ │ │ ├── LogTag.kt
│ │ │ │ └── SettingsData.kt
│ │ │ ├── engine/ # TTS引擎相关
│ │ │ │ ├── SynthesisEngine.kt
│ │ │ │ └── SynthesisEngineListener.kt
│ │ │ ├── function/ # 功能模块
│ │ │ │ ├── SettingsFunction.kt
│ │ │ │ └── UpdateFunction.kt
│ │ │ ├── tts/ # TTS服务相关
│ │ │ │ ├── CheckVoiceData.java
│ │ │ │ ├── DownloadVoiceData.java
│ │ │ │ ├── GetSampleText.java
│ │ │ │ ├── TTSContext.java
│ │ │ │ ├── TTSService.java
│ │ │ │ └── TtsVoiceSample.java
│ │ │ └── ui/ # 用户界面
│ │ │ ├── UpdateDialog.kt
│ │ │ ├── WelcomeDialog.kt
│ │ │ └── theme/ # 主题相关
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── res/ # 资源文件
│ │ ├── drawable/ # 可绘制资源
│ │ │ ├── ic_launcher_background.xml
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── google-play/ # Google Play资源
│ │ │ ├── feature-graphic.png
│ │ │ └── icon.png
│ │ ├── mipmap-anydpi/ # 任意DPI图标
│ │ ├── mipmap-anydpi-v26/ # API 26+图标
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi/ # 高DPI图标
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_foreground.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-ldpi/ # 低DPI图标
│ │ ├── mipmap-mdpi/ # 中DPI图标
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_foreground.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi/ # 超高DPI图标
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_foreground.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi/ # 超超高DPI图标
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_foreground.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi/ # 超超超高DPI图标
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_foreground.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values/ # 值资源
│ │ │ ├── colors.xml # 颜色定义
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── integers.xml # 整数值
│ │ │ ├── speaker_type.xml # 扬声器类型
│ │ │ ├── strings.xml # 字符串资源
│ │ │ └── themes.xml # 主题定义
│ │ └── xml/ # XML配置文件
│ │ ├── backup_rules.xml # 备份规则
│ │ ├── data_extraction_rules.xml
│ │ └── tts_engine.xml # TTS引擎配置
│ └── test/ # 单元测试
│ └── java/
├── build.gradle.kts # 项目构建配置
├── gradle.properties # Gradle属性配置
├── gradle/ # Gradle配置目录
│ ├── libs.versions.toml # 依赖版本管理
│ └── wrapper/ # Gradle包装器
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew # Linux/macOS Gradle脚本
├── gradlew.bat # Windows Gradle脚本
└── settings.gradle.kts # 项目设置配置

工程依赖/构建配置

全局构建配置

位于项目根目录/build.gradle.kts.是项目级别的构建配置

主要功能:
  • 插件声明 :声明项目中使用的Gradle插件
  • 全局配置 :定义所有模块共享的配置
  • 插件版本管理 :统一管理插件版本
关键点:
  • apply false :表示插件在这里声明但不立即应用
  • 这些插件会在各个模块的build.gradle中具体应用
应用构建配置

位于/app/build.gradle.kts.是应用模块的具体配置

插件配置
1
2
3
4
5
plugins {
alias(libs.plugins.android.application) // Android应用插件
alias(libs.plugins.kotlin.android) // Kotlin Android支持
alias(libs.plugins.kotlin.compose) // Kotlin Compose支持
}
Android配置块 - 核心配置
1
2
3
4
5
6
7
8
9
10
11
12
android {
namespace = "com.github.lonepheasantwarrior.volcenginetts"
compileSdk = 36 // 编译SDK版本

defaultConfig {
applicationId = "com.github.lonepheasantwarrior.volcenginetts"
minSdk = 31 // 最低支持Android版本
targetSdk = 36 // 目标SDK版本
versionCode = 10007 // 内部版本号
versionName = "1.0.7" // 用户可见版本号
}
}
构建类型配置
1
2
3
4
5
6
buildTypes {
release {
isMinifyEnabled = false // 发布版本不启用代码混淆
proguardFiles(...) // 混淆规则文件
}
}
编译选项
1
2
3
4
5
6
7
8
9
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
}
}
构建特性
1
2
3
buildFeatures {
compose = true // 启用Jetpack Compose
}
依赖管理 - 最重要的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dependencies {
// AndroidX核心库
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)

// Jetpack Compose相关
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)

// 火山引擎TTS
implementation(libs.speechengine.tob)

// 网络请求
implementation(libs.okhttp)

// 测试依赖
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
}
全局构建配置和应用构建配置的关系
  • 根目录build.gradle.kts :管插件(用什么工具)
  • 应用模块build.gradle.kts :管配置(怎么用工具)
版本目录(Version Catalog)

Gradle 7.0+引入的重要特性

版本目录配置位于/gradle/libs.versions.toml.是现代Gradle项目中 必不可少的核心配置文件

libs.versions.toml 的作用

简单说,这个文件是 依赖和插件的集中管理中心 ,就像项目的”依赖字典”

[versions] 部分 - 版本定义

这里定义了所有依赖的 版本号 ,统一管理,避免版本冲突

1
2
3
4
5
[versions]
agp = "8.13.0" # Android Gradle Plugin版本
kotlin = "2.2.20" # Kotlin版本
coreKtx = "1.17.0" # AndroidX Core KTX版本
speechengine_tob = "5.4.8" # 火山引擎TTS版本
[libraries] 部分 - 库依赖定义

这里定义了具体的 库依赖 ,通过 version.ref 引用[versions]中定义的版本

1
2
3
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
speechengine_tob = { module = "com.bytedance.speechengine:speechengine_tts_tob", version.ref = "speechengine_tob"}
[plugins] 部分 - 插件定义

这里定义了 Gradle插件 及其版本

1
2
3
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

功能实现

主界面 MainActivity.kt

用户与TTS引擎交互的主要界面,职能如下:

  1. 配置火山引擎TTS:让用户输入必要的认证信息

  2. 声音参数设置:选择场景、声音类型、情感模式

  3. 实时演示:提供声音预览功能

  4. 设置持久化:保存用户偏好设置

文件概述

这个文件采用了现代Android开发架构,结合了:

  • Jetpack Compose - 声明式UI框架
  • MVVM模式 - 模型-视图-视图模型架构
  • 响应式编程 - 基于状态驱动的UI更新
主要组件
1. MainActivity类 - 主活动控制器
1
2
3
4
5
6
7
class MainActivity : ComponentActivity() {
private val synthesisEngine: SynthesisEngine get() = (applicationContext as TTSApplication).
synthesisEngine
internal val settingsFunction: SettingsFunction get() = (applicationContext as
TTSApplication).settingsFunction
internal val updateFunction: UpdateFunction by lazy { UpdateFunction(this) }
}

主要职责:

  • 管理应用生命周期(onCreate, onDestroy)
  • 持有核心业务组件(TTS引擎、设置功能、更新功能)
  • 处理应用更新检查
2. VolcengineTTSViewModel类 - 视图模型

这是业务逻辑的核心,负责:

  • 状态管理:管理所有UI状态(App ID、Token、声音选择等)
  • 数据持久化:保存和加载用户设置
  • 业务逻辑:验证设置、播放演示声音等
1
2
3
4
5
6
7
8
9
10
class VolcengineTTSViewModel(application: Application) : AndroidViewModel(application) {
// 应用配置状态
var appId by mutableStateOf("")
var token by mutableStateOf("")
var serviceCluster by mutableStateOf(Constants.DEFAULT_SERVICE_CLUSTER)

// 业务方法
fun saveSettings() { ... }
fun playSampleVoice() { ... }
}
3. UI组件 - Compose函数

文件包含多个Composable函数,构建用户界面:

主界面函数

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun VolcengineTTSUI(modifier: Modifier = Modifier) {
// 使用ViewModel管理状态
val viewModel: VolcengineTTSViewModel = viewModel(...)

// 构建UI布局
LazyColumn {
item { TTSBasicConfigurationInputs(...) } // 基础配置输入
item { TTSVoiceConfigurationInputs(...) } // 声音配置输入
item { TTSSaveSettingsButton(...) } // 保存按钮
}
}

配置输入组件

  • TTSBasicConfigurationInputs:处理App ID、Token、Service Cluster输入
  • TTSVoiceConfigurationInputs:处理场景选择、声音选择、情感开关
核心功能实现
1. 配置管理
  • 实时验证:输入时即时验证数据有效性
  • 错误提示:通过颜色变化和提示文本显示错误
  • 自动保存:用户修改后自动保存到持久化存储
2. 声音选择系统
1
2
3
4
5
6
fun filterSpeakersByScene(scene: String): List<SpeakerInfo> {
return getSpeakerList()
.map { it.split("|") }
.filter { it.size >= 3 && it[0] == scene }
.map { SpeakerInfo(name = it[1], id = it[2]) }
}

工作流程:

  1. 用户选择场景(如”通用”、”客服”等)

  2. 动态过滤可用的声音列表

  3. 用户选择具体声音后自动播放演示

3. 演示播放功能
1
2
3
4
5
fun playSampleVoice() {
val sampleText = TtsVoiceSample.getByLocate(getApplication(), Locale.getDefault())
synthesisEngine.create(appId, token, selectedSpeakerId, serviceCluster, isEmotional)
synthesisEngine.startEngine(sampleText, null, null, null)
}

应用类 TTSApplication.kt

整个TTS应用的全局上下文和初始化入口,职能如下:

  1. 全局初始化:负责应用启动时的核心组件初始化
  2. 生命周期管理:管理应用级别的生命周期事件
  3. 组件持有:作为全局容器持有核心业务组件
  4. 环境准备:初始化火山引擎TTS SDK运行环境
文件概述

这个文件是Android应用的Application类,继承自android.app.Application,是应用的单例全局上下文,在整个应用生命周期中只存在一个实例。

主要组件
1. TTSApplication类 - 应用全局控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TTSApplication: Application() {
lateinit var synthesisEngine: SynthesisEngine private set
lateinit var synthesisEngineListener: SynthesisEngineListener private set
lateinit var settingsFunction: SettingsFunction private set
lateinit var ttsContext: TTSContext private set

override fun onCreate() {
super.onCreate()
// 初始化火山引擎语音合成环境
// 在Application创建时调用,确保整个应用生命周期内只执行一次
SpeechEngineGenerator.PrepareEnvironment(applicationContext, this)
Log.d(LogTag.SDK_INFO, "火山引擎语音合成环境初始化完成")

synthesisEngine = SynthesisEngine(this)
synthesisEngineListener = SynthesisEngineListener(this)
settingsFunction = SettingsFunction(this)
ttsContext = TTSContext()
}
}

主要职责:

  • 环境初始化:调用SpeechEngineGenerator.PrepareEnvironment()准备火山引擎TTS SDK运行环境
  • 组件创建:创建并持有四个核心业务组件
  • 生命周期管理:在onCreate()中执行一次性初始化操作
核心功能实现
1. 火山引擎TTS环境初始化
1
2
SpeechEngineGenerator.PrepareEnvironment(applicationContext, this)
Log.d(LogTag.SDK_INFO, "火山引擎语音合成环境初始化完成")

关键特性:

  • 一次性执行:确保在整个应用生命周期内只执行一次
  • 环境准备:为火山引擎TTS SDK提供必要的运行环境
  • 日志记录:通过LogTag.SDK_INFO记录初始化完成状态
2. 核心业务组件初始化

TTSApplication持有四个核心业务组件,通过lateinit关键字延迟初始化:

  • synthesisEngine:语音合成引擎,负责TTS核心功能
  • synthesisEngineListener:合成引擎监听器,处理合成事件
  • settingsFunction:设置功能模块,管理用户配置
  • ttsContext:TTS上下文,提供运行时数据
3. 全局访问机制

其他组件可以通过ApplicationContext访问这些核心组件:

1
2
3
// 在Activity中访问
val synthesisEngine = (applicationContext as TTSApplication).synthesisEngine
val settingsFunction = (applicationContext as TTSApplication).settingsFunction
设计模式与架构
1. 单例模式
  • Application类在Android系统中是单例的
  • 确保全局状态的一致性
  • 避免重复初始化和资源浪费
2. 依赖注入容器
  • 作为全局的依赖注入容器
  • 提供统一的组件访问接口
  • 简化组件间的依赖关系管理
3. 生命周期感知
  • 在正确的时机执行初始化操作
  • 管理应用级别的资源生命周期
  • 确保组件在需要时可用
在AndroidManifest.xml中的配置

TTSApplication需要在AndroidManifest.xml中声明:

1
2
3
4
5
<application
android:name=".TTSApplication"
android:theme="@style/Theme.VolcengineTTS"
...>
</application>

通过android:name属性指定自定义的Application类,确保系统在应用启动时创建TTSApplication实例。

设置功能类 SettingsFunction.kt

用户配置数据的管理器,负责TTS应用设置信息的持久化存储和读取。

文件概述

这个类封装了Android的SharedPreferences功能,提供简洁的API来管理应用的配置数据。

主要功能
1. 配置保存
1
fun saveSettings(appId: String, token: String, selectedSpeakerId: String, serviceCluster: String, isEmotional: Boolean = false)

保存火山引擎TTS的核心配置参数:

  • App ID和Token(认证信息)
  • 选中的声音ID
  • 服务集群地址
  • 情感朗读开关状态
2. 配置读取
1
fun getSettings(): SettingsData

读取已保存的配置,返回包含所有设置数据的SettingsData对象。

3. 欢迎弹窗控制
1
2
fun shouldShowWelcomeDialog(): Boolean
fun setShowWelcomeDialog(show: Boolean)

管理首次启动时的欢迎弹窗显示逻辑。

技术实现
  • SharedPreferences存储:使用Android原生数据持久化方案
  • 主线程安全:通过Handler确保UI操作在主线程执行
  • 日志记录:关键操作记录日志便于调试
  • 用户反馈:保存成功后显示Toast提示
使用示例
1
2
3
4
5
6
// 保存设置
settingsFunction.saveSettings("your_app_id", "your_token", "speaker_001", "cluster_name")

// 读取设置
val settings = settingsFunction.getSettings()
val appId = settings.appId

合成引擎类 SynthesisEngine.kt

火山引擎语音合成SDK的核心封装类,负责语音合成引擎的创建、配置、启动和销毁。

文件概述

这个类封装了火山引擎TTS SDK的复杂API,提供简洁的接口来管理语音合成引擎的生命周期。

核心功能
1. 引擎生命周期管理
  • 创建引擎:通过create()方法创建语音合成引擎实例
  • 初始化配置:设置火山引擎TTS的各项参数
  • 启动引擎:启动合成引擎并设置监听器
  • 销毁引擎:清理资源并重置状态
2. 参数配置系统
  • 认证参数:appId、token、speakerId、serviceCluster
  • 合成参数:语速、音量、音高、情感预测
  • 技术参数:采样率、工作模式、音频流类型
  • 调试参数:User ID、Device ID用于问题定位
3. 合成控制
  • 文本合成:支持最多80个字符的文本合成
  • 参数调整:实时调整语速、音量、音高参数
  • 状态管理:维护引擎创建状态和参数设置状态
关键技术实现
1. 引擎创建与配置
1
2
3
4
5
6
7
8
9
fun create(appId: String, token: String, speakerId: String, serviceCluster: String, isEmotional: Boolean): SpeechEngine {
if (mSpeechEngine != null) {
destroy()
}
mSpeechEngine = SpeechEngineGenerator.getInstance()
mSpeechEngine!!.createEngine()
setEngineParams(appId, token, speakerId, serviceCluster, isEmotional)
return mSpeechEngine!!
}
2. 核心参数设置
  • 工作模式:设置为在线合成模式(TTS_WORK_MODE_ONLINE)
  • 音频采样率:从资源文件中读取配置(默认24000Hz)
  • 服务地址:配置火山引擎TTS服务端点和API路径
  • 音色选择:设置在线合成使用的音色代号
  • 情感预测:可选启用情感预测功能
3. 合成启动流程
1
2
3
4
5
6
7
8
9
10
11
12
fun startEngine(text: CharSequence?, speedRatio: Int?, volumeRatio: Int?, pitchRatio: Int?) {
// 1. 初始化引擎
val ret = mSpeechEngine!!.initEngine()
// 2. 设置监听器
mSpeechEngine!!.setListener(synthesisEngineListener)
// 3. 同步停止前一次请求
mSpeechEngine!!.sendDirective(SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE, "")
// 4. 设置TTS参数
setTTSParams(text, speedRatio, volumeRatio, pitchRatio)
// 5. 启动引擎
mSpeechEngine!!.sendDirective(SpeechEngineDefines.DIRECTIVE_START_ENGINE, "")
}
4. 参数验证与安全
  • 文本长度限制:单次合成文本不得超过80字
  • 参数范围控制:语速、音量、音高参数按比例缩放
  • 错误处理:完善的异常捕获和用户提示
  • 状态重置:引擎销毁时重置所有状态标志
与SynthesisEngineListener.kt的协作

SynthesisEngine.ktSynthesisEngineListener.kt紧密协作,形成完整的语音合成控制链:

  1. 引擎设置监听器SynthesisEngineSynthesisEngineListener设置为引擎回调监听器
  2. 状态同步:通过TTSContext共享引擎状态信息
  3. 事件处理SynthesisEngineListener处理引擎的各种状态通知
  4. 错误处理:监听器捕获引擎错误并通知主线程
设计特点
1. 封装性
  • 将复杂的火山引擎SDK API封装为简洁的接口
  • 隐藏底层实现细节,提供高层抽象
2. 状态管理
  • 维护引擎创建状态(isCreated)
  • 跟踪参数设置状态(isParametersBeenSet)
  • 确保操作的正确顺序
3. 线程安全
  • 使用Handler确保UI操作在主线程执行
  • 避免多线程环境下的竞态条件
4. 资源管理
  • 提供完整的生命周期管理
  • 确保引擎资源的正确释放
使用示例
1
2
3
4
5
6
7
8
9
10
val synthesisEngine = SynthesisEngine(context)

// 1. 创建引擎
val engine = synthesisEngine.create(appId, token, speakerId, serviceCluster, true)

// 2. 启动合成
synthesisEngine.startEngine("欢迎使用火山引擎TTS", 100, 100, 100)

// 3. 销毁引擎(使用完毕后)
synthesisEngine.destroy()

SynthesisEngine.kt是整个TTS应用的技术核心,它成功地将复杂的语音合成技术封装为易于使用的接口,为上层应用提供了稳定可靠的语音合成能力。

TTSService.java

文件概述
  • 文件路径 /app/src/main/java/com/github/lonepheasantwarrior/volcenginetts/tts/TTSService.java
  • 类名: TTSService
  • 继承: TextToSpeechService
  • 功能: 系统级TTS服务的核心实现,负责将火山引擎TTS集成到Android系统TTS框架中
核心功能
1. 系统TTS服务集成
  • 继承Android TTS框架: 继承TextToSpeechService,实现系统级TTS服务
  • 多语言支持: 支持多种语言的语音合成
  • 系统级调用: 可以被其他应用通过Android TTS API调用
2. 智能文本处理
  • 长文本拆分: 自动将超过80字符的文本拆分为合适片段
  • 智能断句: 按句子边界、逗号、空格等自然断点拆分
  • 连续合成: 支持长文本的连续流畅合成
3. 异步合成控制
  • 单线程作业维持: 通过阻塞队列维持TTS作业在单个线程中执行
  • 状态同步: 协调合成引擎、监听器和系统回调之间的状态
  • 错误处理: 完善的异常捕获和错误恢复机制
关键技术实现
1. TTS作业的线程控制机制

TTSService的核心挑战是将火山引擎SDK的异步回调转换为同步的TTS服务调用。这是通过以下机制实现的:

阻塞队列同步

1
2
3
4
5
6
7
8
9
10
11
// 在TTSService中
private static final byte[] CONTROL_SIGNAL = new byte[0];

// 音频数据处理循环
do {
byte[] chunk = ttsContext.audioDataQueue.take();
if (chunk != null && chunk != CONTROL_SIGNAL && chunk.length > 0) {
// 处理音频数据
callback.audioAvailable(chunk, offset, chunkSize);
}
} while (!ttsContext.isAudioQueueDone.get() && !ttsContext.isTTSInterrupted.get());

在SynthesisEngineListener中的对应实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// 各种状态通知时向队列发送控制信号
override fun onSpeechMessage(type: Int, data: ByteArray?, len: Int) {
when (type) {
SpeechEngineDefines.MESSAGE_TYPE_TTS_AUDIO_DATA -> {
ttsContext.audioDataQueue.put(data) // 发送音频数据
}
SpeechEngineDefines.MESSAGE_TYPE_TTS_FINISH_PLAYING -> {
ttsContext.isAudioQueueDone.set(true) // 设置完成标志
ttsContext.audioDataQueue.put(controlSignal) // 发送完成信号
}
// ... 其他状态处理
}
}
2. 状态控制系统

TTSContext状态容器

1
2
3
4
5
6
7
8
public class TTSContext {
public final BlockingQueue<byte[]> audioDataQueue = new LinkedBlockingQueue<>();
public final AtomicBoolean isAudioQueueDone = new AtomicBoolean(true);
public final AtomicBoolean isTTSInterrupted = new AtomicBoolean(false);
public final AtomicBoolean isTTSEngineError = new AtomicBoolean(false);
public final AtomicInteger currentEngineState = new AtomicInteger();
public final AtomicReference<String> currentEngineMsg = new AtomicReference<>();
}

状态流转流程

  1. 初始化状态: isAudioQueueDone=true, isTTSInterrupted=false
  2. 开始合成: isAudioQueueDone=false,开始监听音频队列
  3. 数据接收: SynthesisEngineListener接收音频数据并放入队列
  4. 数据处理: TTSService从队列取出数据并传递给系统
  5. 完成通知: 收到完成信号后设置isAudioQueueDone=true
  6. 中断处理: 任何时候都可以通过isTTSInterrupted中断作业
3. 长文本智能拆分算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private List<String> splitLongText(String text) {
List<String> segments = new ArrayList<>();

// 定义句子分隔符(中英文、全角半角)
String[] sentenceDelimiters = {".", "。", "!", "!", "?", "?", ";", ";"};
String[] clauseDelimiters = {",", ","};

int maxLength = 80;
int currentPos = 0;

while (currentPos < text.length()) {
// 优先按句子边界拆分,其次按逗号,最后按空格
int splitPos = findBestSplitPoint(text, currentPos, maxLength,
sentenceDelimiters, clauseDelimiters);

segments.add(text.substring(currentPos, splitPos).trim());
currentPos = splitPos;
}

return segments;
}
TTS作业完整流程
1. 单次合成流程(≤80字符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 1. 系统调用onSynthesizeText
@Override
protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
// 2. 验证配置
SettingsData settings = settingsFunction.getSettings();
if (!checkSettings(settings)) {
callback.error();
return;
}

// 3. 创建合成引擎
synthesisEngine.create(settings.getAppId(), settings.getToken(),
settings.getSelectedSpeakerId(), settings.getServiceCluster(), settings.isEmotional());

// 4. 启动合成
synthesisEngine.startEngine(text, request.getSpeechRate(), null, request.getPitch());

// 5. 初始化系统回调
callback.start(16000, AudioFormat.ENCODING_PCM_16BIT, 1);

// 6. 进入音频数据处理循环
do {
byte[] chunk = ttsContext.audioDataQueue.take();
if (chunk != CONTROL_SIGNAL && chunk.length > 0) {
callback.audioAvailable(chunk, offset, chunkSize);
}
} while (!ttsContext.isAudioQueueDone.get());

// 7. 完成回调
callback.done();
synthesisEngine.destroy();
}
2. 长文本合成流程(>80字符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void synthesizeLongText(String text, SynthesisRequest request,
SynthesisCallback callback, SettingsData settings) {
// 1. 智能拆分文本
List<String> segments = splitLongText(text);

// 2. 初始化系统回调
callback.start(16000, AudioFormat.ENCODING_PCM_16BIT, 1);

// 3. 分段合成
for (int i = 0; i < segments.size(); i++) {
String segment = segments.get(i);

// 4. 为每个片段创建新的引擎实例
synthesisEngine.create(settings.getAppId(), settings.getToken(),
settings.getSelectedSpeakerId(), settings.getServiceCluster(), settings.isEmotional());
synthesisEngine.startEngine(segment, request.getSpeechRate(), null, request.getPitch());

// 5. 处理当前片段的音频数据
boolean segmentCompleted = false;
ttsContext.isAudioQueueDone.set(false);

while (!segmentCompleted) {
byte[] chunk = ttsContext.audioDataQueue.take();
if (chunk != CONTROL_SIGNAL && chunk.length > 0) {
callback.audioAvailable(chunk, offset, chunkSize);
}

// 6. 检查片段是否完成
if (ttsContext.isAudioQueueDone.get()) {
segmentCompleted = true;
}
}

// 7. 销毁当前片段引擎
synthesisEngine.destroy();
}

// 8. 完成整个合成
callback.done();
}
3. 异步回调到同步处理的转换

关键挑战: 火山引擎SDK通过异步回调返回数据,但Android TTS框架要求同步处理。

解决方案:

  • 阻塞队列: 使用LinkedBlockingQueue作为数据缓冲区
  • 控制信号: 使用特殊的空字节数组作为状态信号
  • 状态标志: 使用原子布尔值确保线程安全的状态同步

工作流程:

  1. TTSService在onSynthesizeText中启动合成后进入等待状态
  2. SynthesisEngineListener在后台线程接收SDK回调
  3. 监听器将音频数据放入队列,并设置状态标志
  4. TTSService的主循环检测到状态变化后继续处理
  5. 整个过程对调用方来说是同步的
设计特点
1. 线程安全设计
  • 原子操作: 使用AtomicBooleanAtomicInteger等保证状态操作的原子性
  • 阻塞队列: LinkedBlockingQueue提供线程安全的数据传输
  • 主线程安全: 通过Handler确保UI操作在主线程执行
2. 资源管理
  • 引擎实例管理: 每个合成会话创建独立的引擎实例
  • 及时销毁: 合成完成后立即销毁引擎释放资源
  • 内存优化: 及时清理音频数据避免内存泄漏
3. 错误恢复机制
  • 配置验证: 合成前验证所有必要配置
  • 异常捕获: 完善的try-catch块处理各种异常
  • 状态重置: 发生错误时重置所有状态标志
技术点
  1. 异步到同步转换: 成功将火山引擎的异步回调转换为系统TTS的同步接口
  2. 文本处理: 支持任意长度文本的流畅合成
  3. 状态管理: 多线程环境下的可靠状态同步

TTSService.java是整个项目的技术核心。它成功解决了异步SDK与同步系统框架之间的集成难题,为用户提供了稳定可靠的系统级TTS服务。

额外注意

在设置是否使用豆包语音合成SDK内置播放器播放合成出的音频参数com.bytedance.speech.speechengine.SpeechEngineDefines#PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL时我选择了true也就是说SDK会自动播放合成出的音频,进而也就无需通过android.speech.tts.SynthesisCallback#audioAvailable将合成后的音频内容提交至系统接口交由系统来发出音频对应的声音了

之所以这样做是因为当选择了不通过SDK内置播放器播放合成出的音频的话SDK回调中将不会返回播放进度,考虑到得到合成音频数据后通过音频数据计算而来的播放时长并非完全准确,同时android.speech.tts.SynthesisCallback#audioAvailable并不会等待音频播放完成后返回(异步播放)而是在将音频数据通过该接口提交给系统后立刻返回了,进而导致android.speech.tts.TextToSpeechService#onSynthesizeText函数早于语音播放结束前返回最终导致了TTS任务结束时间和合成后的语音播放进度不对齐的问题

为了避免这种情况我选择了通过SDK来播放音频,这样我可以通过SDK的播放状态回调结合TTSContext的状态控制来合理阻塞android.speech.tts.TextToSpeechService#onSynthesizeText实现函数的执行来使得TTS作业进度与音频播放进度保持一致

Jetpack Compose

Jetpack Compose是Android官方推出的现代声明式UI工具包,它彻底改变了Android应用的UI开发方式。与传统的基于XML的视图系统不同,Compose使用Kotlin语言来构建用户界面,提供了更简洁、更直观的开发体验。

Compose的核心优势
  1. 声明式编程:描述UI应该是什么样子,而不是如何构建
  2. 响应式状态:UI自动响应状态变化,无需手动更新视图
  3. 组合优于继承:通过组合小型可重用组件构建复杂UI
  4. Kotlin原生支持:完全基于Kotlin,享受语言特性优势
工程中的Compose应用

VolcengineTTS项目全面采用Jetpack Compose构建用户界面,主要体现在以下几个关键组件中:

1. 主界面架构 (MainActivity.kt)

MVVM模式集成

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun VolcengineTTSUI(modifier: Modifier = Modifier) {
val viewModel: VolcengineTTSViewModel = viewModel(factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val application = checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY])
return VolcengineTTSViewModel(application) as T
}
})

// UI构建...
}

状态管理

  • 使用mutableStateOf管理UI状态
  • 通过ViewModel实现业务逻辑与UI分离
  • 自动响应状态变化,实现实时UI更新

组件化设计

1
2
3
4
5
LazyColumn {
item { TTSBasicConfigurationInputs(...) } // 基础配置组件
item { TTSVoiceConfigurationInputs(...) } // 声音配置组件
item { TTSSaveSettingsButton(...) } // 保存按钮组件
}
2. 配置输入组件

表单验证与错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Composable
fun TTSBasicConfigurationInputs(
appId: String,
token: String,
serviceCluster: String,
isAppIdError: Boolean,
isTokenError: Boolean,
isServiceClusterError: Boolean,
onAppIdChange: (String) -> Unit,
onTokenChange: (String) -> Unit,
onServiceClusterChange: (String) -> Unit
) {
// Material Design 3组件
OutlinedTextField(
value = appId,
onValueChange = onAppIdChange,
label = { Text(stringResource(id = R.string.input_app_id)) },
isError = isAppIdError,
supportingText = if (isAppIdError) {
{ Text(text = "App ID不能为空", color = MaterialTheme.colorScheme.error) }
} else { null }
)
}

下拉选择器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Composable
fun SceneDropdown(
selectedScene: String,
sceneCategories: Array<String>,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
onSceneSelect: (String) -> Unit
) {
var dropdownExpanded by remember { mutableStateOf(expanded) }

OutlinedButton(
onClick = { onExpandedChange(!dropdownExpanded) },
modifier = Modifier.fillMaxWidth()
) {
Text(selectedScene)
Icon(Icons.Default.ArrowDropDown, contentDescription = "下拉箭头")
}

DropdownMenu(
expanded = dropdownExpanded,
onDismissRequest = { onExpandedChange(false) }
) {
sceneCategories.forEach { scene ->
DropdownMenuItem(
text = { Text(scene) },
onClick = {
onSceneSelect(scene)
onExpandedChange(false)
}
)
}
}
}
3. 欢迎对话框 (WelcomeDialog.kt)

动画效果

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun WelcomeDialog(onDismiss: () -> Unit, onDontShowAgain: (Boolean) -> Unit) {
Dialog(onDismissRequest = { onDismiss() }) {
AnimatedVisibility(
visible = true,
enter = fadeIn() + scaleIn(), // 淡入+缩放进入
exit = fadeOut() + scaleOut() // 淡出+缩放退出
) {
// 对话框内容...
}
}
}

响应式设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val windowInfo = LocalWindowInfo.current
val containerSize = windowInfo.containerSize
val density = LocalDensity.current.density

// 自适应屏幕尺寸
val screenWidth = (containerSize.width / density).dp
val screenHeight = (containerSize.height / density).dp
val isSmallScreen = screenWidth < 360.dp || screenHeight < 640.dp

// 根据屏幕尺寸调整UI
Card(
modifier = Modifier
.width(screenWidth * 0.8f) // 宽度占屏幕80%
.height(screenHeight * 0.65f) // 高度占屏幕65%
) {
// 内容布局...
}

Material Design 3主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.verticalGradient(
colors = listOf(Color(0xFF6A11CB), Color(0xFF2575FC))
)
)
) {
// 渐变背景...
}

Button(
onClick = { onDismiss() },
colors = ButtonDefaults.buttonColors(
containerColor = Color.White, // 按钮背景色
contentColor = Color(0xFF2575FC) // 按钮文字色
)
) {
Text("开始使用")
}
4. Compose与现有系统的集成

与传统Activity的集成

1
2
3
4
5
6
7
8
9
10
11
12
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // 启用边缘到边缘显示

setContent { // 设置Compose内容
VolcengineTTSTheme { // 应用自定义主题
UpdateCheckContent()
}
}
}
}

生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Composable
fun UpdateCheckContent() {
val activity = LocalContext.current as MainActivity

// 使用LaunchedEffect处理协程生命周期
LaunchedEffect(Unit) {
activity.checkForUpdate(
onUpdateAvailable = { version, notes, url ->
// 处理更新可用逻辑
},
onError = { message ->
// 处理错误逻辑
}
)
}

VolcengineTTSUI()
}
Compose在工程中的技术特点
  1. 状态驱动UI:所有UI组件都基于ViewModel中的状态自动更新
  2. 组合式架构:通过小型可组合函数构建复杂界面
  3. Material Design 3:全面采用最新的Material Design设计语言
  4. 响应式布局:自适应不同屏幕尺寸和设备方向
  5. 动画集成:内置丰富的动画效果,提升用户体验
构建配置中的Compose支持

app/build.gradle.kts中启用了Compose支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
buildFeatures {
compose = true // 启用Jetpack Compose
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.4" // Compose编译器版本
}
}

dependencies {
implementation(libs.androidx.activity.compose) // Compose Activity支持
implementation(libs.androidx.compose.ui) // Compose UI核心
implementation(libs.androidx.compose.material3) // Material Design 3组件
}
  • 标题: 基于AI的语音合成软件
  • 作者: wxh
  • 创建于 : 2025-10-11 00:00:00
  • 更新于 : 2025-10-11 20:15:39
  • 链接: https://blog.private-cloud.site/2025/10/11/语音合成软件开发/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
基于AI的语音合成软件