一、TTS 简介
TTS(Text-to-Speech,文字转语音)是安卓系统内置的语音合成功能,可将文本转换为自然语音输出,广泛应用于语音播报、无障碍服务、语音助手等场景。安卓提供了 TextToSpeech 类来实现TTS功能,支持多语言、语速/音调调节、语音选择等核心能力。
二、核心知识点
1. 权限
安卓TTS基础功能无需额外权限(Android 6.0+),若需保存语音到本地,需添加存储权限:
xml
<!-- 可选:保存TTS音频到本地 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
2. 核心类
TextToSpeech:TTS核心类,负责初始化、语音合成、参数配置。TextToSpeech.OnInitListener:初始化回调接口,监听TTS引擎初始化结果。Locale:配置语音的语言/地区(如中文、英文、日语)。
3. 关键方法
| 方法 | 作用 |
|---|---|
TextToSpeech(context, listener) |
初始化TTS引擎 |
setLanguage(locale) |
设置语音语言/地区 |
speak(text, queueMode, params, utteranceId) |
播放语音 |
synthesizeToFile(text, params, file, utteranceId) |
将语音合成到文件 |
setPitch(pitch) |
设置音调(0.5-2.0,默认1.0) |
setSpeechRate(rate) |
设置语速(0.1-2.0,默认1.0) |
stop() |
停止播放 |
shutdown() |
释放TTS资源(必须调用) |
三、完整实现步骤
步骤1:添加依赖(无需额外依赖,系统内置)
安卓自带TTS引擎,无需在 build.gradle 中添加额外依赖。
步骤2:布局文件(activity_main.xml)
简单布局,包含输入框、播放按钮、语速/音调调节控件:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<EditText
android:id="@+id/etText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要朗读的文本"
android:maxLines="5"/>
<Button
android:id="@+id/btnPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放语音"
android:layout_marginTop="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="语速:"
android:gravity="center_vertical"/>
<SeekBar
android:id="@+id/sbRate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="20"
android:progress="10"/> <!-- 默认语速1.0 -->
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音调:"
android:gravity="center_vertical"/>
<SeekBar
android:id="@+id/sbPitch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="20"
android:progress="10"/> <!-- 默认音调1.0 -->
</LinearLayout>
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保存语音到文件"
android:layout_marginTop="16dp"/>
</LinearLayout>
步骤3:Activity逻辑实现(MainActivity.kt)
kotlin
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.util.Locale
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
// 日志标签
private val TAG = "TTS_DEMO"
// TTS核心对象
private lateinit var tts: TextToSpeech
// 控件
private lateinit var etText: EditText
private lateinit var btnPlay: Button
private lateinit var btnSave: Button
private lateinit var sbRate: SeekBar
private lateinit var sbPitch: SeekBar
// 语音合成文件路径(Android 10+建议使用应用私有目录)
private val ttsFilePath: String
get() {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 应用私有目录
filesDir.absolutePath + File.separator + "tts_audio.wav"
} else {
// 外部存储(需权限)
Environment.getExternalStorageDirectory().absolutePath + File.separator + "tts_audio.wav"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化控件
initViews()
// 初始化TTS引擎
tts = TextToSpeech(this, this)
// 设置进度条监听(语速/音调)
setSeekBarListeners()
// 设置按钮点击事件
setButtonClickListeners()
// 设置TTS播放进度监听
setTTSProgressListener()
}
/**
* 初始化控件
*/
private fun initViews() {
etText = findViewById(R.id.etText)
btnPlay = findViewById(R.id.btnPlay)
btnSave = findViewById(R.id.btnSave)
sbRate = findViewById(R.id.sbRate)
sbPitch = findViewById(R.id.sbPitch)
}
/**
* 设置SeekBar监听(语速/音调)
*/
private fun setSeekBarListeners() {
// 语速调节(0.1-2.0)
sbRate.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
val rate = progress / 10.0f // 进度0-20 → 0.0-2.0(修正:0.1-2.0)
val finalRate = if (rate < 0.1f) 0.1f else rate
tts.setSpeechRate(finalRate)
Log.d(TAG, "当前语速:$finalRate")
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
// 音调调节(0.5-2.0)
sbPitch.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
val pitch = progress / 10.0f // 进度0-20 → 0.0-2.0(修正:0.5-2.0)
val finalPitch = if (pitch < 0.5f) 0.5f else pitch
tts.setPitch(finalPitch)
Log.d(TAG, "当前音调:$finalPitch")
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
/**
* 设置按钮点击事件
*/
private fun setButtonClickListeners() {
// 播放语音
btnPlay.setOnClickListener {
val text = etText.text.toString().trim()
if (text.isEmpty()) {
Toast.makeText(this, "请输入要朗读的文本", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
playTTS(text)
}
// 保存语音到文件
btnSave.setOnClickListener {
val text = etText.text.toString().trim()
if (text.isEmpty()) {
Toast.makeText(this, "请输入要朗读的文本", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
saveTTSToFile(text)
}
}
/**
* TTS初始化回调
*/
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
// 设置默认语言(中文)
val result = tts.setLanguage(Locale.CHINA)
// 检查语言是否支持
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e(TAG, "中文语言包不支持,请安装TTS语言包")
Toast.makeText(this, "中文语言包不支持,即将跳转到TTS设置", Toast.LENGTH_LONG).show()
// 跳转到系统TTS设置页面
val intent = Intent("com.android.settings.TTS_SETTINGS")
startActivity(intent)
} else {
Log.d(TAG, "TTS初始化成功,语言设置为中文")
// 设置默认语速和音调(1.0)
tts.setSpeechRate(1.0f)
tts.setPitch(1.0f)
btnPlay.isEnabled = true
btnSave.isEnabled = true
}
} else {
Log.e(TAG, "TTS初始化失败,状态码:$status")
Toast.makeText(this, "TTS初始化失败", Toast.LENGTH_SHORT).show()
btnPlay.isEnabled = false
btnSave.isEnabled = false
}
}
/**
* 播放TTS语音
* @param text 要朗读的文本
*/
private fun playTTS(text: String) {
// 停止之前的播放(可选)
tts.stop()
// 设置播放参数
val params = HashMap<String, String>()
params[TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID] = "tts_play_utterance" // 唯一标识
// 播放语音
// queueMode:QUEUE_FLUSH(替换队列)/ QUEUE_ADD(添加到队列)
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, "tts_play_utterance")
} else {
@Suppress("DEPRECATION")
tts.speak(text, TextToSpeech.QUEUE_FLUSH, params)
}
if (result == TextToSpeech.ERROR) {
Log.e(TAG, "TTS播放失败")
Toast.makeText(this, "语音播放失败", Toast.LENGTH_SHORT).show()
}
}
/**
* 将TTS语音保存到文件
* @param text 要合成的文本
*/
private fun saveTTSToFile(text: String) {
val file = File(ttsFilePath)
// 删除已存在的文件
if (file.exists()) {
file.delete()
}
// 设置合成参数
val params = HashMap<String, String>()
params[TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID] = "tts_save_utterance"
// 合成到文件
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tts.synthesizeToFile(text, null, file, "tts_save_utterance")
} else {
@Suppress("DEPRECATION")
tts.synthesizeToFile(text, params, ttsFilePath)
}
if (result == TextToSpeech.SUCCESS) {
Log.d(TAG, "TTS文件保存成功:$ttsFilePath")
Toast.makeText(this, "语音已保存到:$ttsFilePath", Toast.LENGTH_LONG).show()
} else {
Log.e(TAG, "TTS文件保存失败")
Toast.makeText(this, "语音保存失败", Toast.LENGTH_SHORT).show()
}
}
/**
* 设置TTS播放进度监听
*/
private fun setTTSProgressListener() {
tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {
Log.d(TAG, "TTS开始播放:$utteranceId")
runOnUiThread {
Toast.makeText(this@MainActivity, "开始播放语音", Toast.LENGTH_SHORT).show()
}
}
override fun onDone(utteranceId: String?) {
Log.d(TAG, "TTS播放完成:$utteranceId")
runOnUiThread {
Toast.makeText(this@MainActivity, "语音播放完成", Toast.LENGTH_SHORT).show()
}
}
override fun onError(utteranceId: String?) {
Log.e(TAG, "TTS播放错误:$utteranceId")
runOnUiThread {
Toast.makeText(this@MainActivity, "语音播放出错", Toast.LENGTH_SHORT).show()
}
}
// Android 8.0+ 新增:播放暂停
override fun onPause(utteranceId: String?) {
super.onPause(utteranceId)
Log.d(TAG, "TTS暂停播放:$utteranceId")
}
// Android 8.0+ 新增:播放继续
override fun onResume(utteranceId: String?) {
super.onResume(utteranceId)
Log.d(TAG, "TTS恢复播放:$utteranceId")
}
// Android 11+ 新增:播放取消
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
Log.d(TAG, "TTS停止播放:$utteranceId,是否中断:$interrupted")
}
})
}
/**
* 释放TTS资源(必须调用,否则会内存泄漏)
*/
override fun onDestroy() {
super.onDestroy()
// 停止播放
tts.stop()
// 释放资源
tts.shutdown()
Log.d(TAG, "TTS资源已释放")
}
}
四、关键注意事项
1. 语言包支持
- 若系统未安装对应语言的TTS包,
setLanguage会返回LANG_MISSING_DATA或LANG_NOT_SUPPORTED,需引导用户跳转到系统TTS设置页面安装。 - 常用Locale:
- 中文(中国大陆):
Locale.CHINA/Locale.SIMPLIFIED_CHINESE - 中文(中国台湾):
Locale.TAIWAN - 英文(美国):
Locale.US - 英文(英国):
Locale.UK
- 中文(中国大陆):
2. 版本兼容
- Android 5.0(Lollipop)前后,
speak和synthesizeToFile方法参数有差异,需做版本适配。 - Android 10+ 限制外部存储访问,建议使用应用私有目录(
filesDir/cacheDir)保存合成文件。
3. 资源释放
- 必须在Activity/Fragment销毁时调用
tts.shutdown()释放TTS资源,否则会导致内存泄漏。 - 播放过程中可调用
tts.stop()停止当前语音播放。
4. Utterance ID
KEY_PARAM_UTTERANCE_ID是唯一标识,用于区分不同的语音合成请求,在UtteranceProgressListener中可监听对应ID的播放状态。
五、常见问题解决
1. TTS初始化失败(status=-1)
- 检查系统是否安装TTS引擎(如Google文字转语音、系统自带TTS)。
- 确认TTS引擎已启用:设置 → 系统 → 语言和输入法 → 文字转语音输出 → 选择默认引擎。
2. 语音播放无声音
- 检查设备音量(媒体音量)是否开启。
- 确认语言包已正确安装,且
setLanguage返回成功。
3. 保存文件失败(Android 10+)
- 避免使用外部存储根目录,改用应用私有目录(
filesDir)。 - 无需申请
WRITE_EXTERNAL_STORAGE权限(Android 10+)。
4. 语速/音调调节无效
- 确保在
onInit成功后再设置语速/音调,且数值在合法范围(语速0.1-2.0,音调0.5-2.0)。
六、扩展功能
-
多语音选择 :通过
tts.voices获取系统支持的语音列表,选择指定语音:kotlinval voices = tts.voices for (voice in voices) { if (voice.name.contains("zh-CN")) { tts.voice = voice break } } -
批量合成 :将文本分割为多个片段,通过
QUEUE_ADD加入播放队列。 -
暂停/恢复播放 (Android 8.0+):
kotlintts.pause() // 暂停 tts.resume() // 恢复
总结
安卓TTS是轻量级、易集成的语音合成方案,核心是 TextToSpeech 类的初始化、参数配置和资源管理。开发时需注意版本兼容、语言包支持和资源释放,结合 UtteranceProgressListener 可实现更精细的播放状态监听。以上代码可直接集成到项目中,根据业务需求扩展功能即可。