详细介绍:Android 系统TTS(文字转语音)解析
一、TTS 简介
TTS(Text-to-Speech,文字转语音)是安卓系统内置的语音合成功能,可将文本转换为自然语音输出,广泛应用于语音播报、无障碍服务、语音助手等场景。安卓提供了 TextToSpeech 类来实现TTS功能,支持多语言、语速/音调调节、语音选择等核心能力。
二、核心知识点
1. 权限
安卓TTS基础功能无需额外权限(Android 6.0+),若需保存语音到本地,需添加存储权限:
<!-- 可选:保存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 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)
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获取系统支持的语音列表,选择指定语音:val voices = tts.voices for (voice in voices) { if (voice.name.contains("zh-CN")) { tts.voice = voice break } } - 批量合成:将文本分割为多个片段,通过
QUEUE_ADD加入播放队列。 - 暂停/恢复播放(Android 8.0+):
tts.pause() // 暂停 tts.resume() // 恢复
总结
安卓TTS是轻量级、易集成的语音合成方案,核心是 TextToSpeech 类的初始化、参数配置和资源管理。开发时需注意版本兼容、语言包支持和资源释放,结合 UtteranceProgressListener 可实现更精细的播放状态监听。以上代码可直接集成到项目中,根据业务需求扩展功能即可。
浙公网安备 33010602011771号