详细介绍: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_DATALANG_NOT_SUPPORTED,需引导用户跳转到系统TTS设置页面安装。
  • 常用Locale:
    • 中文(中国大陆):Locale.CHINA / Locale.SIMPLIFIED_CHINESE
    • 中文(中国台湾):Locale.TAIWAN
    • 英文(美国):Locale.US
    • 英文(英国):Locale.UK

2. 版本兼容

  • Android 5.0(Lollipop)前后,speaksynthesizeToFile 方法参数有差异,需做版本适配。
  • 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)。

六、扩展功能

  1. 多语音选择:通过 tts.voices 获取系统支持的语音列表,选择指定语音:
    val voices = tts.voices
    for (voice in voices) {
    if (voice.name.contains("zh-CN")) {
    tts.voice = voice
    break
    }
    }
  2. 批量合成:将文本分割为多个片段,通过 QUEUE_ADD 加入播放队列。
  3. 暂停/恢复播放(Android 8.0+):
    tts.pause() // 暂停
    tts.resume() // 恢复

总结

安卓TTS是轻量级、易集成的语音合成方案,核心是 TextToSpeech 类的初始化、参数配置和资源管理。开发时需注意版本兼容、语言包支持和资源释放,结合 UtteranceProgressListener 可实现更精细的播放状态监听。以上代码可直接集成到项目中,根据业务需求扩展功能即可。

posted @ 2026-01-28 16:13  clnchanpin  阅读(3)  评论(0)    收藏  举报