Android TextToSpeech 使用指南

最近要做一个资讯朗读功能,当用户点击播放按钮时,把文章中的文字转化成声音播报出来

搜索了一番,类似的功能实现有如下几种:

  1. 科大讯飞
  2. 百度语音
  3. 云知声
  4. ROKID
  5. Google Cloud

以上的产品,要么就是收费,要么就是接入和维护相对这样的一个小功能来说,比较困难,性价比低,所以最后决定动用 Android 原生提供的 TextToSpeech 来处理文字转语音的功能

Android 原生的 TextToSpeech 目前已经支持中文播放,而在一些国产测试机如小米、华为、vivo上跑了 TextToSpeech 的测试 demo,也是可以播报中文的,不过只是语音引擎不一样而已

查看小米手机的系统设置,发现其默认的 tts 是小爱同学引擎,那么其实只要使用不同的引擎搭配不同的语音包,一样可以实现文字转语音功能,但是考虑到安装包大小的问题,还是老老实实使用原生的 TextToSpeech 了



谷歌的详细设置

小米的详细设置

华为的详细设置

Android文字转语音引擎(TTS)简单比较及下载

工具类代码如下:

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* 文本阅读(TTS) 管理器
* @author lv.qx on 2019/11/25
*/
class TextToSpeechManager(context: Context) {

private var ttsErrorStatus: Int = 0
private var tts: TextToSpeech? = null

init {
tts = TextToSpeech(context) { status ->
ttsErrorStatus = if (status == TextToSpeech.SUCCESS) {
TextToSpeech.SUCCESS
} else {
TextToSpeech.ERROR
}
}

val tts = tts

if (ttsErrorStatus == TextToSpeech.SUCCESS && tts != null) {
// 默认设定语言为中文,原生的 android 貌似不支持中文。
ttsErrorStatus = when {
tts.availableLanguages == null -> TextToSpeech.LANG_NOT_SUPPORTED
tts.availableLanguages.contains(Locale.CHINESE) -> tts.setLanguage(Locale.CHINESE)
tts.availableLanguages.contains(Locale.SIMPLIFIED_CHINESE) -> tts.setLanguage(Locale.SIMPLIFIED_CHINESE)
tts.availableLanguages.contains(Locale.TRADITIONAL_CHINESE) -> tts.setLanguage(Locale.TRADITIONAL_CHINESE)
else -> TextToSpeech.LANG_NOT_SUPPORTED
}
}
}

/**
* 设置音调,值越大声音越尖(女生),值越小则变成男声,默认值为 1.0
*/
fun setPitch(pitch: Float) {
val tts = tts ?: return
tts.setPitch(pitch)
}

/**
* 设置语速,默认值为 1
*/
fun setSpeechRate(rate: Float) {
val tts = tts ?: return
tts.setSpeechRate(rate)
}

/**
* 读文本
* @param content 文本内容
* @param utteranceProgressListener 阅读进度监听
*/
fun speak(
content: String,
utteranceProgressListener: GetLastSegUtteranceProgressListener
) {
val tts = tts ?: return
tts.setOnUtteranceProgressListener(utteranceProgressListener)

when (ttsErrorStatus) {
TextToSpeech.LANG_MISSING_DATA -> showToast(R.string.voice_missing_data)
TextToSpeech.LANG_NOT_SUPPORTED -> showToast(R.string.voice_lang_not_supported)
ANDROID_VERSION_NOT_SUPPORT -> showToast(R.string.voice_android_version_not_supported)
else -> {
if (tts.isSpeaking) {
tts.stop()
} else {
val textSeg = genSegment(content, 10)
utteranceProgressListener.setLastSegId(SEG_PREFIX + (textSeg.size - 1))
for (i in textSeg.indices) {
// QUEUE_ADD:播放完之前的语音任务后才播报本次内容,QUEUE_FLUSH:丢弃之前的播报任务,立即播报本次内容
tts.speak(textSeg[i], TextToSpeech.QUEUE_ADD, null, SEG_PREFIX + i)
}
}
}
}
}

/**
* 将源文本分段
*
* @param segmentLength 每一段的长度,最大设置 3999,大于 3999 将阅读出错
* @param originStr 源文本
* @return 分成的文本段
*/
private fun genSegment(originStr: String, segmentLength: Int = 3999): Array<String?> {
val originLength = originStr.length
val arraySize = originLength / segmentLength + 1
val result = arrayOfNulls<String>(arraySize)
for (i in 0 until arraySize) {
result[i] = originStr.substring(i * segmentLength, min((i + 1) * segmentLength, originLength))
}
return result
}

/**
* 取得文本内容大约分钟长度
*/
fun getSpeechLength(content: String): Int {
val totalSecond = (content.length / 3.7).toInt()
return totalSecond / 60
}

/**
* 扩展 onDone 方法的进度监听
*/
@SuppressLint("NewApi")
abstract class GetLastSegUtteranceProgressListener : UtteranceProgressListener() {

private var lastSegId = ""

/**
* 设置最后一段的 id
*/
fun setLastSegId(lastSegId: String) {
this.lastSegId = lastSegId
}

override fun onDone(utteranceId: String?) {
onDone(utteranceId, lastSegId == utteranceId)
}

/**
* @param isLastSeg 是否是最后一段
*/
abstract fun onDone(utteranceId: String?, isLastSeg: Boolean)
}

companion object {
private const val SEG_PREFIX = "seg" // 段落前缀
private const val ANDROID_VERSION_NOT_SUPPORT = -3 // 版本不支持
}
}

简单使用如下

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
val ttsManager = TextToSpeechManager(this)
···
ttsManager.speak(
binding.voiceText.text.toString(),
object : TextToSpeechManager.GetLastSegUtteranceProgressListener() {

override fun onStart(utteranceId: String?) {
// 开始阅读
}

override fun onStop(utteranceId: String?, interrupted: Boolean) {
// 停止阅读
}

override fun onDone(utteranceId: String?, isLastSeg: Boolean) {
// 每一段结束时调用,通过 stop() 停止则不会调用这里
if (isLastSeg) {
// 这里是最后一段
}
}

override fun onError(utteranceId: String?) {
// 阅读出错
}
}
)

参考文章


Android TextToSpeech 使用指南
https://enderhoshi.github.io/2019/11/25/Android TextToSpeech 使用指南/
作者
HoshIlIlI
发布于
2019年11月25日
许可协议