[Feat]新增支持多语言的TTS
[Feat]新增支持多语言的TTS
This commit is contained in:
375
tts/tts-iflytek/src/main/java/com/mogo/tts/iflytek/IFlyTekTts.kt
Normal file
375
tts/tts-iflytek/src/main/java/com/mogo/tts/iflytek/IFlyTekTts.kt
Normal file
@@ -0,0 +1,375 @@
|
||||
package com.mogo.tts.iflytek
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.util.Pair
|
||||
import androidx.annotation.Keep
|
||||
import com.iflytek.cloud.*
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.i
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
|
||||
import com.mogo.tts.base.*
|
||||
import java.util.*
|
||||
|
||||
@Keep
|
||||
class IFlyTekTts : IMogoTTS, InitListener {
|
||||
companion object {
|
||||
const val TAG = "IFlyTekTts"
|
||||
}
|
||||
|
||||
private var context: Context? = null
|
||||
|
||||
private var ttsEngine: SpeechSynthesizer? = null
|
||||
private var engineType = SpeechConstant.TYPE_CLOUD
|
||||
|
||||
private var voicer: String = "xiaoyan"
|
||||
private var voicerEntries: Array<String>? = null
|
||||
|
||||
// 等级由低到高为0、1、2、3,默认为-1表示没有正在tts的
|
||||
private var curTtsLevel = -1
|
||||
|
||||
// 由于主动打断不会有回调事件,所以主动打断时清掉map中被打断的text和callback
|
||||
private var curTtsContent = ""
|
||||
private var curTtsEntity: MultiLangTtsEntity? = null
|
||||
private val linkedList = LinkedList<Pair<MultiLangTtsEntity, Int>>()
|
||||
|
||||
// 上一次语音合成的语言
|
||||
private var lastLang = LanguageType.CHINESE
|
||||
|
||||
private val speakVoiceMap by lazy {
|
||||
HashMap<String, IMogoTTSCallback>()
|
||||
}
|
||||
|
||||
override fun init(context: Context) {
|
||||
this.context = context
|
||||
|
||||
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
|
||||
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
|
||||
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
|
||||
// 参数间使用半角“,”分隔。
|
||||
// 设置你申请的应用appid,请勿在'='与appid之间添加空格及空转义符
|
||||
// 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
|
||||
SpeechUtility.createUtility(context, "appid=0c498b42")
|
||||
ttsEngine = SpeechSynthesizer.createSynthesizer(context, this)
|
||||
voicerEntries = context.resources?.getStringArray(R.array.voicer_cloud_values)
|
||||
}
|
||||
|
||||
override fun initTts(sn: String?) {}
|
||||
|
||||
override fun onInit(code: Int) {
|
||||
if (code != ErrorCode.SUCCESS) {
|
||||
CallerLogger.e(
|
||||
TAG,
|
||||
"初始化失败,错误码为:$code,请点击网址https://www.xfyun.cn/document/error-code查询解决方案"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
ttsEngine?.stopSpeaking()
|
||||
ttsEngine?.destroy()
|
||||
}
|
||||
|
||||
override fun flush() {
|
||||
}
|
||||
|
||||
override fun speakTTSVoice(tts: String?) {
|
||||
|
||||
}
|
||||
|
||||
override fun speakTTSVoice(tts: String?, callback: IMogoTTSCallback?) {
|
||||
}
|
||||
|
||||
override fun speakTTSVoice(tts: String?, type: PreemptType?, callback: IMogoTTSCallback?) {
|
||||
}
|
||||
|
||||
override fun speakTTSVoiceWithLevel(tts: String?, level: Int) {
|
||||
}
|
||||
|
||||
override fun speakTTSVoiceWithLevel(tts: String?, level: Int, callBack: IMogoTTSCallback?) {
|
||||
}
|
||||
|
||||
override fun speakMultiLangTTSWithLevel(
|
||||
ttsEntity: MultiLangTtsEntity,
|
||||
level: Int,
|
||||
callBack: IMogoTTSCallback?
|
||||
) {
|
||||
if (callBack != null) {
|
||||
speakVoiceMap[ttsEntity.toString()] = callBack
|
||||
}
|
||||
speakMultiLangTTSWithLevel(ttsEntity, level)
|
||||
}
|
||||
|
||||
override fun stopSpeakTts(text: String?) {
|
||||
stopTts()
|
||||
}
|
||||
|
||||
override fun stopTts() {
|
||||
curTtsEntity?.let {
|
||||
val string = it.toString()
|
||||
if (speakVoiceMap.containsKey(string)) {
|
||||
speakVoiceMap.remove(string)
|
||||
}
|
||||
curTtsEntity = null
|
||||
}
|
||||
curTtsContent = ""
|
||||
curTtsLevel = -1
|
||||
ttsEngine?.stopSpeaking()
|
||||
}
|
||||
|
||||
override fun speakQAndACmd(tts: String?, callback: IMogoTTSCallback?) {
|
||||
}
|
||||
|
||||
override fun speakQAndACmd(
|
||||
tts: String?,
|
||||
okWords: Array<out String>?,
|
||||
cancelWords: Array<out String>?,
|
||||
callback: IMogoTTSCallback?
|
||||
) {
|
||||
}
|
||||
|
||||
override fun registerUnWakeupCommand(
|
||||
cmd: String?,
|
||||
cmdWords: Array<out String>?,
|
||||
callback: IMogoTTSCallback?
|
||||
) {
|
||||
}
|
||||
|
||||
override fun unregisterUnWakeupCommand(cmd: String?) {
|
||||
}
|
||||
|
||||
override fun unregisterUnWakeupCommand(cmd: String?, callback: IMogoTTSCallback?) {
|
||||
}
|
||||
|
||||
override fun startAIAssist(context: Context?) {
|
||||
}
|
||||
|
||||
override fun startAIAssist(context: Context?, status: Int) {
|
||||
}
|
||||
|
||||
override fun breakOffSpeak() {
|
||||
}
|
||||
|
||||
// 降序插入Tts(目前Level0、1可排队)
|
||||
private fun insertTts(ttsEntity: MultiLangTtsEntity, level: Int) {
|
||||
var index = -1
|
||||
for (i in linkedList.indices.reversed()) {
|
||||
val nodeLevel = linkedList[i].second
|
||||
// 只有高优先级才插入到前面,等于的情况下是插到后面
|
||||
if (level > nodeLevel) {
|
||||
index = i
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
linkedList.add(index, Pair(ttsEntity, level))
|
||||
} else {
|
||||
linkedList.addLast(Pair(ttsEntity, level))
|
||||
}
|
||||
for (ttsPair in linkedList) {
|
||||
d(TAG, "tts文本为:" + ttsPair.first + ",level为:" + ttsPair.second)
|
||||
}
|
||||
d(TAG, "===================")
|
||||
}
|
||||
|
||||
private fun speakMultiLangTTSWithLevel(ttsEntity: MultiLangTtsEntity, ttsLevel: Int) {
|
||||
ttsEngine?.let {
|
||||
if (ttsLevel == curTtsLevel) {
|
||||
// 对应p3、p2级别的排队
|
||||
if (ttsLevel == 0 || ttsLevel == 1) {
|
||||
d(TAG, "===================")
|
||||
d(TAG, "插入消息:$ttsEntity,level为:$ttsLevel")
|
||||
insertTts(ttsEntity, ttsLevel)
|
||||
return
|
||||
} else {
|
||||
// 打断并合成新的
|
||||
stopTts()
|
||||
d(TAG, "非Level1同级别打断!")
|
||||
}
|
||||
} else {
|
||||
// 将要TTS的比现在正在TTS的优先级高
|
||||
if (ttsLevel > curTtsLevel) {
|
||||
if (curTtsLevel >= 0) {
|
||||
// 打断并合成高优先级的
|
||||
stopTts()
|
||||
}
|
||||
d(TAG, "高优先级打断低级别的!")
|
||||
} else {
|
||||
if (ttsLevel == 0 || ttsLevel == 1) {
|
||||
d(TAG, "===================")
|
||||
d(TAG, "插入消息:$ttsEntity,level为:$ttsLevel")
|
||||
insertTts(ttsEntity, ttsLevel)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
curTtsLevel = ttsLevel
|
||||
curTtsEntity = ttsEntity
|
||||
// 合成并播放
|
||||
d(TAG, "tts准备合成:$ttsEntity,curTtsLevel为:$curTtsLevel")
|
||||
startSpeak(ttsEntity.ttsNext())
|
||||
}
|
||||
}
|
||||
|
||||
private fun startSpeak(langTtsEntity: LangTtsEntity?) {
|
||||
langTtsEntity?.let {
|
||||
if (it.language != lastLang) {
|
||||
updateVoicer(it.language)
|
||||
updateTtsParam()
|
||||
lastLang = it.language
|
||||
}
|
||||
ttsEngine?.startSpeaking(it.ttsContent, ttsListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCompleteEvent() {
|
||||
curTtsLevel = -1
|
||||
if (curTtsEntity != null) {// 多语言
|
||||
val langTtsEntity = curTtsEntity!!.ttsNext()
|
||||
if (langTtsEntity != null) {
|
||||
speakVoiceMap[curTtsEntity.toString()]?.onSpeakEnd(curTtsEntity.toString())
|
||||
ttsNextLanguage(langTtsEntity)
|
||||
} else {
|
||||
speakVoiceMap.remove(curTtsEntity.toString())?.onSpeakEnd(curTtsEntity.toString())
|
||||
curTtsEntity = null
|
||||
ttsNextMultiLangEntity()
|
||||
}
|
||||
} else {// 单语言
|
||||
speakVoiceMap.remove(curTtsContent)?.onSpeakEnd(curTtsContent)
|
||||
curTtsContent = ""
|
||||
ttsNextMultiLangEntity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleErrorEvent(error: SpeechError) {
|
||||
curTtsLevel = -1
|
||||
if (curTtsEntity != null) {
|
||||
speakVoiceMap.remove(curTtsEntity.toString())
|
||||
?.onSpeakError(curTtsEntity.toString(), error.message)
|
||||
} else {
|
||||
speakVoiceMap.remove(curTtsContent)?.onSpeakError(curTtsContent, error.message)
|
||||
}
|
||||
curTtsEntity = null
|
||||
curTtsContent = ""
|
||||
}
|
||||
|
||||
private val ttsListener = object : SynthesizerListener {
|
||||
override fun onSpeakBegin() {
|
||||
|
||||
}
|
||||
|
||||
override fun onBufferProgress(p0: Int, p1: Int, p2: Int, p3: String?) {
|
||||
}
|
||||
|
||||
override fun onSpeakPaused() {
|
||||
}
|
||||
|
||||
override fun onSpeakResumed() {
|
||||
}
|
||||
|
||||
override fun onSpeakProgress(p0: Int, p1: Int, p2: Int) {
|
||||
}
|
||||
|
||||
override fun onEvent(p0: Int, p1: Int, p2: Int, p3: Bundle?) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* error - 错误信息,若为null,则没有出现错误。
|
||||
* 在音频播放完成,或会话出现错误时,将回调此函数。
|
||||
* 若应用主动调用SpeechSynthesizer.stopSpeaking()停止会话,则不会回调此函数。
|
||||
*/
|
||||
override fun onCompleted(error: SpeechError?) {
|
||||
if (Thread.currentThread() == Looper.getMainLooper().thread) {
|
||||
if (error == null) {// 播放完成
|
||||
handleCompleteEvent()
|
||||
} else {// 播放出错
|
||||
handleErrorEvent(error)
|
||||
}
|
||||
} else {
|
||||
UiThreadHandler.post {
|
||||
if (error == null) {
|
||||
handleCompleteEvent()
|
||||
} else {
|
||||
handleErrorEvent(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音合成下一个MultiLangTtsEntity
|
||||
*/
|
||||
private fun ttsNextMultiLangEntity() {
|
||||
if (!linkedList.isEmpty()) {
|
||||
val ttsPair = linkedList.removeFirst()
|
||||
i(TAG, "排队播放的下一条文本为:" + ttsPair.first + ",级别为:" + ttsPair.second)
|
||||
curTtsLevel = ttsPair.second
|
||||
startSpeak(ttsPair.first.ttsNext())
|
||||
} else {
|
||||
i(TAG, "队列为空")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音合成下一个MultiLangTtsEntity中的语言
|
||||
*/
|
||||
private fun ttsNextLanguage(langTtsEntity: LangTtsEntity) {
|
||||
startSpeak(langTtsEntity)
|
||||
}
|
||||
|
||||
private fun updateVoicer(language: LanguageType) {
|
||||
voicerEntries?.let {
|
||||
voicer = when (language) {
|
||||
LanguageType.ENGLISH -> it[5]
|
||||
LanguageType.KOREAN -> it[6]
|
||||
else -> it[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTtsParam() {
|
||||
// 清空参数
|
||||
ttsEngine?.setParameter(SpeechConstant.PARAMS, null)
|
||||
// 根据合成引擎设置相应参数
|
||||
if (engineType == SpeechConstant.TYPE_CLOUD) {
|
||||
ttsEngine?.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
|
||||
// 支持实时音频返回,仅在 synthesizeToUri 条件下支持
|
||||
ttsEngine?.setParameter(SpeechConstant.TTS_DATA_NOTIFY, "1")
|
||||
// ttsEngine?.setParameter(SpeechConstant.TTS_BUFFER_TIME,"1");
|
||||
// 设置在线合成发音人
|
||||
ttsEngine?.setParameter(SpeechConstant.VOICE_NAME, voicer)
|
||||
//设置合成语速
|
||||
ttsEngine?.setParameter(
|
||||
SpeechConstant.SPEED, "50"
|
||||
)
|
||||
//设置合成音调
|
||||
ttsEngine?.setParameter(
|
||||
SpeechConstant.PITCH, "50"
|
||||
)
|
||||
//设置合成音量
|
||||
ttsEngine?.setParameter(
|
||||
SpeechConstant.VOLUME, "50"
|
||||
)
|
||||
} else {
|
||||
ttsEngine?.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL)
|
||||
ttsEngine?.setParameter(SpeechConstant.VOICE_NAME, "")
|
||||
}
|
||||
|
||||
//设置播放器音频流类型
|
||||
ttsEngine?.setParameter(
|
||||
SpeechConstant.STREAM_TYPE, "3"
|
||||
)
|
||||
// 设置播放合成音频打断音乐播放,默认为true
|
||||
ttsEngine?.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "false")
|
||||
|
||||
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
|
||||
ttsEngine?.setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
|
||||
ttsEngine?.setParameter(
|
||||
SpeechConstant.TTS_AUDIO_PATH,
|
||||
context?.getExternalFilesDir("msc")?.absolutePath + "/tts.pcm"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user