最近要做一个资讯朗读功能,当用户点击播放按钮时,把文章中的文字转化成声音播报出来
搜索了一番,类似的功能实现有如下几种:
- 科大讯飞
- 百度语音
- 云知声
- ROKID
- 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
|
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) { 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 } } }
fun setPitch(pitch: Float) { val tts = tts ?: return tts.setPitch(pitch) }
fun setSpeechRate(rate: Float) { val tts = tts ?: return tts.setSpeechRate(rate) }
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) { tts.speak(textSeg[i], TextToSpeech.QUEUE_ADD, null, SEG_PREFIX + i) } } } } }
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 }
@SuppressLint("NewApi") abstract class GetLastSegUtteranceProgressListener : UtteranceProgressListener() {
private var lastSegId = ""
fun setLastSegId(lastSegId: String) { this.lastSegId = lastSegId }
override fun onDone(utteranceId: String?) { onDone(utteranceId, lastSegId == utteranceId) }
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) { if (isLastSeg) { } }
override fun onError(utteranceId: String?) { } } )
|
参考文章