feat(speech): 集成TTS功能并优化语音配置管理

- 在gradle.properties中添加测试环境默认配置参数
- 通过BuildConfig统一管理语音服务相关配置信息
- 集成TtsClient实现文本转语音功能
- 添加TTS连接状态管理和回调处理
- 实现唤醒词触发后的TTS响应播报
- 优化ASR和TTS的连接与关闭流程
- 添加TTS播放完成后的麦克风自动启动逻辑
这个提交包含在:
徐勤民 2026-04-16 23:01:31 +08:00
父节点 b7f2405b7b
当前提交 e3bf95e4fe
共有 3 个文件被更改,包括 72 次插入13 次删除

查看文件

@ -20,6 +20,14 @@ android {
] ]
buildConfigField("String", "APP_Name", "\"" + apps.applicationName + "\"") buildConfigField("String", "APP_Name", "\"" + apps.applicationName + "\"")
buildConfigField("String", "SPEECH_DOMAIN", "\"api-test.rokid.com\"")
buildConfigField("String", "SPEECH_AK", "\"\"")
buildConfigField("String", "SPEECH_SK", "\"\"")
buildConfigField("String", "SPEECH_UID", "\"demo-user\"")
buildConfigField("String", "SPEECH_DEVICE_ID", "\"demo-device\"")
buildConfigField("String", "SPEECH_ASR_PATH", "\"/ar/audio/api/ws/asr/streaming\"")
buildConfigField("String", "SPEECH_TTS_PATH", "\"/ar/audio/api/ws/tts\"")
flavorDimensions "versioncode" flavorDimensions "versioncode"
} }
buildTypes { buildTypes {

查看文件

@ -1,10 +1,13 @@
package com.nova.brain.glass.helper package com.nova.brain.glass.helper
import android.util.Log import android.util.Log
import com.nova.brain.glass.BuildConfig
import com.nova.brain.glass.model.RecognizeAction import com.nova.brain.glass.model.RecognizeAction
import com.rokid.online.speech.AsrClient import com.rokid.online.speech.AsrClient
import com.rokid.online.speech.OnlineSpeechSdk import com.rokid.online.speech.OnlineSpeechSdk
import com.rokid.online.speech.OnlineSpeechSdkConfig import com.rokid.online.speech.OnlineSpeechSdkConfig
import com.rokid.online.speech.TtsClient
import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer
import com.rokid.online.speech.open.OpenSdkAudioSource import com.rokid.online.speech.open.OpenSdkAudioSource
import com.xuqm.base.extensions.showMessage import com.xuqm.base.extensions.showMessage
@ -12,14 +15,14 @@ object AsrHelper : OfflineCmdListener {
private const val TAG = "AsrHelper" private const val TAG = "AsrHelper"
// 配置信息,从 online-speech-sdk-demo 参考项目复制 // 配置信息来自 BuildConfig在 app/build.gradle 的 buildConfigField 中维护)
private const val DOMAIN = "api-test.rokid.com" private val DOMAIN get() = BuildConfig.SPEECH_DOMAIN
private const val ASR_PATH = "/ar/audio/api/ws/asr/streaming" private val AK get() = BuildConfig.SPEECH_AK
private const val TTS_PATH = "/ar/audio/api/ws/tts" private val SK get() = BuildConfig.SPEECH_SK
private const val AK = "" private val UID get() = BuildConfig.SPEECH_UID
private const val SK = "" private val DEVICE_ID get() = BuildConfig.SPEECH_DEVICE_ID
private const val UID = "demo-user" private val ASR_PATH get() = BuildConfig.SPEECH_ASR_PATH
private const val DEVICE_ID = "demo-device" private val TTS_PATH get() = BuildConfig.SPEECH_TTS_PATH
// 唤醒词Nova Nova // 唤醒词Nova Nova
private const val WAKE_WORD = "Nova Nova" private const val WAKE_WORD = "Nova Nova"
@ -27,10 +30,15 @@ object AsrHelper : OfflineCmdListener {
private var sdk: OnlineSpeechSdk? = null private var sdk: OnlineSpeechSdk? = null
private var asr: AsrClient? = null private var asr: AsrClient? = null
private var tts: TtsClient? = null
private val audioSource = OpenSdkAudioSource() private val audioSource = OpenSdkAudioSource()
private val ttsPlayer = AndroidPcmTtsStreamPlayer()
private var isConnected = false private var isConnected = false
private var isMicRunning = false private var isMicRunning = false
private var isTtsConnected = false
private const val WAKE_RESPONSE = "在呢,您请说"
// 拼接每次识别会话中的中间结果 // 拼接每次识别会话中的中间结果
private var currentPartial = "" private var currentPartial = ""
@ -63,13 +71,15 @@ object AsrHelper : OfflineCmdListener {
sdk = OnlineSpeechSdk(cfg) sdk = OnlineSpeechSdk(cfg)
asr = sdk!!.createAsrClient().attachAudioSource(audioSource).also { setupAsrCallbacks(it) } asr = sdk!!.createAsrClient().attachAudioSource(audioSource).also { setupAsrCallbacks(it) }
tts = sdk!!.createTtsClient().attachStreamPlayer(ttsPlayer).also { setupTtsCallbacks(it) }
// 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR // 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR
OfflineCmdServiceHelper.registerAsrWakeWord() OfflineCmdServiceHelper.registerAsrWakeWord()
OfflineCmdServiceHelper.addOnLineListener(this) OfflineCmdServiceHelper.addOnLineListener(this)
// 自动建立 ASR 连接 // 自动建立 ASR / TTS 连接
asrConnect() asrConnect()
tts?.connect()
Log.d(TAG, "AsrHelper init done") Log.d(TAG, "AsrHelper init done")
} }
@ -150,11 +160,40 @@ object AsrHelper : OfflineCmdListener {
}) })
} }
// 离线关键词回调:匹配唤醒词时启动麦克风 private fun setupTtsCallbacks(ttsClient: TtsClient) {
ttsClient.setListener(object : TtsClient.Listener {
override fun onOpen() {
isTtsConnected = true
Log.d(TAG, "TTS websocket open")
}
override fun onFinished(taskId: String) {
Log.d(TAG, "TTS ended: $taskId, starting mic")
asrStartMic()
}
override fun onError(code: Int, message: String) {
Log.e(TAG, "TTS error code=$code msg=$message, fallback to mic")
asrStartMic()
}
override fun onClosed(code: Int, reason: String) {
isTtsConnected = false
Log.d(TAG, "TTS closed code=$code reason=$reason")
}
})
}
// 离线关键词回调:唤醒词触发时先 TTS 播报,播报结束后启动麦克风
override fun onOfflineCmd(cmd: String) { override fun onOfflineCmd(cmd: String) {
if (cmd == WAKE_WORD) { if (cmd == WAKE_WORD) {
Log.d(TAG, "Wake word triggered, starting mic") Log.d(TAG, "Wake word triggered")
asrStartMic() if (isTtsConnected) {
tts?.speak(WAKE_RESPONSE)
} else {
Log.w(TAG, "TTS not connected, starting mic directly")
asrStartMic()
}
} }
} }
@ -165,10 +204,13 @@ object AsrHelper : OfflineCmdListener {
isMicRunning = false isMicRunning = false
} }
asr?.close() asr?.close()
tts?.close()
sdk?.close() sdk?.close()
asr = null asr = null
tts = null
sdk = null sdk = null
isConnected = false isConnected = false
isTtsConnected = false
Log.d(TAG, "AsrHelper closed") Log.d(TAG, "AsrHelper closed")
} }
} }

查看文件

@ -18,4 +18,13 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
# Demo-Android defaults (test environment)
online.demo.domain=api-test.rokid.com
online.demo.ak=20bc97fd19ef47b38a8c63f3b22ff401
online.demo.sk=d2131994176d4e08a5815cad1e06da4d
online.demo.uid=demo-user
online.demo.deviceId=demo-device
online.demo.asrPath=/ar/audio/api/ws/asr/streaming
online.demo.ttsPath=/ar/audio/api/ws/tts