共计 3276 个字符,预计需要花费 9 分钟才能阅读完成。
技术背景
离线语音合成(TTS)技术允许设备在没有网络连接的情况下将文本转换为语音,这对于隐私敏感型应用(如医疗、金融)或网络不稳定场景至关重要。与在线方案相比,离线 TTS 的主要优势包括:

- 零网络延迟:合成过程完全在本地完成,响应速度更快
- 隐私保护:用户文本数据无需上传到云端
- 可靠性保障:在网络信号差的环境(如地下室、偏远地区)仍可正常工作
但需要注意:
- 离线语音库通常较大(中文基础包约 50MB)
- 语音自然度可能略低于在线版本
- 需要定期更新语音模型
环境准备
1. SDK 获取与配置
- 前往讯飞开放平台下载最新版离线语音合成 SDK(包含
libmsc.so和.jar文件) - 将资源文件放入项目对应目录:
app/
├── libs/
│ ├── MSC.jar
│ └── armeabi-v7a/
│ └── libmsc.so
└── assets/
├── tts/
│ ├── common.jet
│ └── xiaoyan.jet
2. Gradle 配置
android {
defaultConfig {
ndk {abiFilters 'armeabi-v7a'}
}
sourceSets {
main {jniLibs.srcDirs = ['libs']
}
}
}
dependencies {implementation files('libs/MSC.jar')
}
3. 权限声明
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
核心实现
1. 初始化语音引擎
class TtsManager private constructor(context: Context) {
companion object {
private const val APP_ID = "你的 APP_ID"
private var instance: TtsManager? = null
fun init(context: Context): TtsManager {return instance ?: synchronized(this) {instance ?: TtsManager(context.applicationContext).also {
instance = it
// 设置合成参数
val params = "appid=$APP_ID,engine_mode=local"
SpeechUtility.createUtility(context, params)
}
}
}
}
private val mTts = SpeechSynthesizer.createSynthesizer(context) { code ->
if (code != SpeechConstant.SUCCESS) {Log.e("TTS", "初始化失败, 错误码:$code")
}
}
}
2. 文本转语音实现
fun speak(text: String, callback: (Int) -> Unit) {
// 设置参数(必须放在主线程)mTts?.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan") // 发音人
mTts?.setParameter(SpeechConstant.SPEED, "50") // 语速[0-100]
mTts?.setParameter(SpeechConstant.PITCH, "50") // 音调[0-100]
mTts?.setParameter(SpeechConstant.VOLUME, "80") // 音量[0-100]
// 开始合成(异步操作)val code = mTts?.startSpeaking(text, object : SynthesizerListener {override fun onSpeakBegin() {callback(SpeechConstant.SUCCESS)
}
override fun onCompleted(error: SpeechError?) {error?.let { callback(error.errorCode) }
}
// 其他回调方法省略...
}) ?: SpeechConstant.ERROR_ENGINE_NOT_INIT
if (code != SpeechConstant.SUCCESS) {callback(code)
}
}
性能优化
1. 资源加载策略
- 延迟加载:在首次使用时初始化引擎
- 按需加载:只下载当前需要的语音包(如仅中文)
- 预加载优化:
fun preLoadResources() {Handler(Looper.getMainLooper()).post {mTts?.setParameter(SpeechConstant.TTS_BUFFER_TIME, "3") // 预加载 3 秒音频
}
}
2. 内存泄漏预防
- 在 Activity 的
onDestroy中释放资源:
override fun onDestroy() {mTts?.stopSpeaking()
mTts?.destroy()
super.onDestroy()}
- 使用 WeakReference 持有 Context
3. 合成耗时监控
val startTime = System.currentTimeMillis()
mTts?.startSpeaking(text, object : SynthesizerListener {override fun onBufferProgress(percent: Int, pos: Int, end: Int) {Log.d("TTS", "缓冲进度:$percent%")
}
override fun onCompleted(error: SpeechError?) {val cost = System.currentTimeMillis() - startTime
Log.d("TTS", "合成耗时:${cost}ms")
}
})
避坑指南
常见错误码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 10106 | 引擎未初始化 | 检查 APP_ID 和网络权限 |
| 10114 | 本地资源缺失 | 验证 assets/tts 目录文件 |
| 10118 | 参数错误 | 检查 SpeechConstant 参数值范围 |
多线程注意事项
- 所有
setParameter调用必须在主线程 - 合成结果回调可能发生在非 UI 线程
离线资源更新
- 定期检查新版本语音包
- 使用差分更新减少下载量
- 通过 MD5 校验文件完整性
完整 Demo 示例
// Java 版参见 GitHub 仓库
class TtsActivity : AppCompatActivity() {
private lateinit var ttsManager: TtsManager
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
ttsManager = TtsManager.init(this)
binding.btnSpeak.setOnClickListener {ttsManager.speak(binding.etText.text.toString()) { code ->
if (code != SpeechConstant.SUCCESS) {Toast.makeText(this, "合成失败:$code", Toast.LENGTH_SHORT).show()}
}
}
}
override fun onDestroy() {ttsManager.release()
super.onDestroy()}
}
进阶思考
- 如何实现语音合成队列?
- 使用 PriorityQueue 管理待播报文本
-
通过回调链触发下一段合成
-
怎样动态切换在线 / 离线模式?
- 根据网络状态自动切换 engine_mode
-
保持两种引擎参数一致性
-
能否实现实时音频流处理?
- 通过 PCM 回调获取原始音频数据
- 结合 AudioTrack 实现低延迟播放
正文完
