前言 之前有想过开发一款基于在线基于大模型的文本转语音(TTS)的安卓应用,记得当时还是搞测试的哥们儿喜欢看小说我答应人家去写这么个应用来着.当时尝试开发着试了试虽然能发出声音但是线程控制,与Android系统交互这些细节做的是一塌糊涂,最终也没好意思把打包好的应用交出手.
后来是家里我妈想要听书,实在是不想让我妈用什么七猫 阅读软件(全是广告),当然是用Legado / 开源阅读 阅读软件搭配我自己写的TTS软件VolcengineTTS 来达到最完美的听书体验啦XD
说干就干
开发 相关概念 Gradle Gradle是一个现代化的 构建自动化工具 ,主要用于Java、Kotlin和Android项目的构建管理。
核心作用
Gradle和Maven的关系 共享仓库标准
Maven仓库是行业标准 :Maven Central、JCenter、Google Maven仓库等已经成为Java/Kotlin生态的标准依赖仓库
Gradle兼容Maven仓库 :Gradle可以直接使用Maven仓库,无需额外配置
统一的坐标系统 :两者都使用 groupId:artifactId:version 的坐标格式
Gradle相比Maven的优势
构建性能
增量构建 :Gradle只重新编译变化的部分
构建缓存 :可以缓存构建结果
并行执行 :支持并行任务执行
灵活性
基于Groovy/Kotlin DSL :比Maven的XML更灵活
插件系统 :更强大的插件机制
多项目构建 :更好的多模块支持
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系统知道:
应用是什么 (包名、版本、图标等)
应用能做什么 (需要的权限)
应用包含什么 (Activity、Service等组件)
应用如何启动 (主入口点)
为什么这个文件如此重要?
系统识别 :没有它,Android系统不知道你的应用存在
权限控制 :定义应用需要访问的系统资源
组件注册 :声明所有的Activity、Service等
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) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) }
Android配置块 - 核心配置 1 2 3 4 5 6 7 8 9 10 11 12 android { namespace = "com.github.lonepheasantwarrior.volcenginetts" compileSdk = 36 defaultConfig { applicationId = "com.github.lonepheasantwarrior.volcenginetts" minSdk = 31 targetSdk = 36 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 }
依赖管理 - 最重要的部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.material3) 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" kotlin = "2.2.20" coreKtx = "1.17.0" speechengine_tob = "5.4.8"
[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引擎交互的主要界面 ,职能如下:
配置火山引擎TTS :让用户输入必要的认证信息
声音参数设置 :选择场景、声音类型、情感模式
实时演示 :提供声音预览功能
设置持久化 :保存用户偏好设置
文件概述 这个文件采用了现代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) { val viewModel: VolcengineTTSViewModel = viewModel(...) 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 ]) } }
工作流程:
用户选择场景(如”通用”、”客服”等)
动态过滤可用的声音列表
用户选择具体声音后自动播放演示
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应用的全局上下文和初始化入口 ,职能如下:
全局初始化 :负责应用启动时的核心组件初始化
生命周期管理 :管理应用级别的生命周期事件
组件持有 :作为全局容器持有核心业务组件
环境准备 :初始化火山引擎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() 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 val synthesisEngine = (applicationContext as TTSApplication).synthesisEngineval 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 ?) { val ret = mSpeechEngine!!.initEngine() mSpeechEngine!!.setListener(synthesisEngineListener) mSpeechEngine!!.sendDirective(SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE, "" ) setTTSParams(text, speedRatio, volumeRatio, pitchRatio) mSpeechEngine!!.sendDirective(SpeechEngineDefines.DIRECTIVE_START_ENGINE, "" ) }
4. 参数验证与安全
文本长度限制 :单次合成文本不得超过80字
参数范围控制 :语速、音量、音高参数按比例缩放
错误处理 :完善的异常捕获和用户提示
状态重置 :引擎销毁时重置所有状态标志
与SynthesisEngineListener.kt的协作 SynthesisEngine.kt与SynthesisEngineListener.kt紧密协作,形成完整的语音合成控制链:
引擎设置监听器 :SynthesisEngine将SynthesisEngineListener设置为引擎回调监听器
状态同步 :通过TTSContext共享引擎状态信息
事件处理 :SynthesisEngineListener处理引擎的各种状态通知
错误处理 :监听器捕获引擎错误并通知主线程
设计特点 1. 封装性
将复杂的火山引擎SDK API封装为简洁的接口
隐藏底层实现细节,提供高层抽象
2. 状态管理
维护引擎创建状态(isCreated)
跟踪参数设置状态(isParametersBeenSet)
确保操作的正确顺序
3. 线程安全
使用Handler确保UI操作在主线程执行
避免多线程环境下的竞态条件
4. 资源管理
使用示例 1 2 3 4 5 6 7 8 9 10 val synthesisEngine = SynthesisEngine(context)val engine = synthesisEngine.create(appId, token, speakerId, serviceCluster, true )synthesisEngine.startEngine("欢迎使用火山引擎TTS" , 100 , 100 , 100 ) 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 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 <>(); }
状态流转流程
初始化状态 : isAudioQueueDone=true, isTTSInterrupted=false
开始合成 : isAudioQueueDone=false,开始监听音频队列
数据接收 : SynthesisEngineListener接收音频数据并放入队列
数据处理 : TTSService从队列取出数据并传递给系统
完成通知 : 收到完成信号后设置isAudioQueueDone=true
中断处理 : 任何时候都可以通过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 @Override protected void onSynthesizeText (SynthesisRequest request, SynthesisCallback callback) { SettingsData settings = settingsFunction.getSettings(); if (!checkSettings(settings)) { callback.error(); return ; } synthesisEngine.create(settings.getAppId(), settings.getToken(), settings.getSelectedSpeakerId(), settings.getServiceCluster(), settings.isEmotional()); synthesisEngine.startEngine(text, request.getSpeechRate(), null , request.getPitch()); callback.start(16000 , AudioFormat.ENCODING_PCM_16BIT, 1 ); do { byte [] chunk = ttsContext.audioDataQueue.take(); if (chunk != CONTROL_SIGNAL && chunk.length > 0 ) { callback.audioAvailable(chunk, offset, chunkSize); } } while (!ttsContext.isAudioQueueDone.get()); 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) { List<String> segments = splitLongText(text); callback.start(16000 , AudioFormat.ENCODING_PCM_16BIT, 1 ); for (int i = 0 ; i < segments.size(); i++) { String segment = segments.get(i); synthesisEngine.create(settings.getAppId(), settings.getToken(), settings.getSelectedSpeakerId(), settings.getServiceCluster(), settings.isEmotional()); synthesisEngine.startEngine(segment, request.getSpeechRate(), null , request.getPitch()); 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); } if (ttsContext.isAudioQueueDone.get()) { segmentCompleted = true ; } } synthesisEngine.destroy(); } callback.done(); }
3. 异步回调到同步处理的转换 关键挑战 : 火山引擎SDK通过异步回调返回数据,但Android TTS框架要求同步处理。
解决方案 :
阻塞队列 : 使用LinkedBlockingQueue作为数据缓冲区
控制信号 : 使用特殊的空字节数组作为状态信号
状态标志 : 使用原子布尔值确保线程安全的状态同步
工作流程 :
TTSService在onSynthesizeText中启动合成后进入等待状态
SynthesisEngineListener在后台线程接收SDK回调
监听器将音频数据放入队列,并设置状态标志
TTSService的主循环检测到状态变化后继续处理
整个过程对调用方来说是同步的
设计特点 1. 线程安全设计
原子操作 : 使用AtomicBoolean、AtomicInteger等保证状态操作的原子性
阻塞队列 : LinkedBlockingQueue提供线程安全的数据传输
主线程安全 : 通过Handler确保UI操作在主线程执行
2. 资源管理
引擎实例管理 : 每个合成会话创建独立的引擎实例
及时销毁 : 合成完成后立即销毁引擎释放资源
内存优化 : 及时清理音频数据避免内存泄漏
3. 错误恢复机制
配置验证 : 合成前验证所有必要配置
异常捕获 : 完善的try-catch块处理各种异常
状态重置 : 发生错误时重置所有状态标志
技术点
异步到同步转换 : 成功将火山引擎的异步回调转换为系统TTS的同步接口
文本处理 : 支持任意长度文本的流畅合成
状态管理 : 多线程环境下的可靠状态同步
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的核心优势
声明式编程 :描述UI应该是什么样子,而不是如何构建
响应式状态 :UI自动响应状态变化,无需手动更新视图
组合优于继承 :通过组合小型可重用组件构建复杂UI
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 } }) }
状态管理 :
使用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 ) { 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 ) } ) } } }
动画效果 :
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.currentval containerSize = windowInfo.containerSizeval density = LocalDensity.current.densityval screenWidth = (containerSize.width / density).dpval screenHeight = (containerSize.height / density).dpval isSmallScreen = screenWidth < 360. dp || screenHeight < 640. dpCard( modifier = Modifier .width(screenWidth * 0.8f ) .height(screenHeight * 0.65f ) ) { }
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 { 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(Unit ) { activity.checkForUpdate( onUpdateAvailable = { version, notes, url -> }, onError = { message -> } ) } VolcengineTTSUI() }
Compose在工程中的技术特点
状态驱动UI :所有UI组件都基于ViewModel中的状态自动更新
组合式架构 :通过小型可组合函数构建复杂界面
Material Design 3 :全面采用最新的Material Design设计语言
响应式布局 :自适应不同屏幕尺寸和设备方向
动画集成 :内置丰富的动画效果,提升用户体验
构建配置中的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 } composeOptions { kotlinCompilerExtensionVersion = "1.5.4" } } dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.material3) }