|
|
@@ -0,0 +1,178 @@
|
|
|
+package com.nova.brain.glass.helper
|
|
|
+
|
|
|
+import android.util.Log
|
|
|
+import com.nova.brain.glass.model.RecognizeAction
|
|
|
+import com.rokid.online.speech.AsrClient
|
|
|
+import com.rokid.online.speech.OnlineSpeechSdk
|
|
|
+import com.rokid.online.speech.OnlineSpeechSdkConfig
|
|
|
+import com.rokid.online.speech.open.OpenSdkAudioSource
|
|
|
+import com.xuqm.base.extensions.showMessage
|
|
|
+
|
|
|
+object AsrHelper : OfflineCmdListener {
|
|
|
+
|
|
|
+ private const val TAG = "AsrHelper"
|
|
|
+
|
|
|
+ // 配置信息,从 online-speech-sdk-demo 参考项目复制
|
|
|
+ private const val DOMAIN = "api-test.rokid.com"
|
|
|
+ private const val ASR_PATH = "/ar/audio/api/ws/asr/streaming"
|
|
|
+ private const val TTS_PATH = "/ar/audio/api/ws/tts"
|
|
|
+ private const val AK = ""
|
|
|
+ private const val SK = ""
|
|
|
+ private const val UID = "demo-user"
|
|
|
+ private const val DEVICE_ID = "demo-device"
|
|
|
+
|
|
|
+ // 唤醒词:Nova Nova
|
|
|
+ private const val WAKE_WORD = "Nova Nova"
|
|
|
+ private const val WAKE_WORD_PINYIN = "nou wa nou wa"
|
|
|
+
|
|
|
+ private var sdk: OnlineSpeechSdk? = null
|
|
|
+ private var asr: AsrClient? = null
|
|
|
+ private val audioSource = OpenSdkAudioSource()
|
|
|
+
|
|
|
+ private var isConnected = false
|
|
|
+ private var isMicRunning = false
|
|
|
+
|
|
|
+ // 拼接每次识别会话中的中间结果
|
|
|
+ private var currentPartial = ""
|
|
|
+ // 拼接跨多次识别的最终结果
|
|
|
+ private val sessionBuilder = StringBuilder()
|
|
|
+
|
|
|
+ /** 当前页面的场景标识,由各 Activity 在 onResume/onPause 中维护 */
|
|
|
+ var scene: String = "home"
|
|
|
+
|
|
|
+ /** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */
|
|
|
+ var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null
|
|
|
+
|
|
|
+ fun init() {
|
|
|
+ val cfg = OnlineSpeechSdkConfig(
|
|
|
+ domain = DOMAIN,
|
|
|
+ ak = AK,
|
|
|
+ sk = SK,
|
|
|
+ uid = UID,
|
|
|
+ deviceId = DEVICE_ID,
|
|
|
+ asrPath = ASR_PATH,
|
|
|
+ ttsPath = TTS_PATH,
|
|
|
+ trustAllCerts = true,
|
|
|
+ staticHttpHeaders = mapOf(
|
|
|
+ "appCredential" to "userInfo",
|
|
|
+ "messageId" to "android-asr-${System.currentTimeMillis()}",
|
|
|
+ ),
|
|
|
+ staticMessageHeaders = mapOf(
|
|
|
+ "appCredential" to "userInfo",
|
|
|
+ "messageId" to "android-asr-${System.currentTimeMillis()}",
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+ sdk = OnlineSpeechSdk(cfg)
|
|
|
+ asr = sdk!!.createAsrClient().attachAudioSource(audioSource).also { setupAsrCallbacks(it) }
|
|
|
+
|
|
|
+ // 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR
|
|
|
+ OfflineCmdServiceHelper.registerAsrWakeWord()
|
|
|
+ OfflineCmdServiceHelper.addOnLineListener(this)
|
|
|
+
|
|
|
+ // 自动建立 ASR 连接
|
|
|
+ asrConnect()
|
|
|
+
|
|
|
+ Log.d(TAG, "AsrHelper init done")
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun asrConnect() {
|
|
|
+ asr?.connect()
|
|
|
+ Log.d(TAG, "ASR connect() called")
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun asrStartMic() {
|
|
|
+ if (!isConnected) {
|
|
|
+ Log.w(TAG, "ASR startMic ignored: not connected")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (isMicRunning) {
|
|
|
+ Log.w(TAG, "ASR startMic ignored: mic already running")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ runCatching { asr?.startAsrWithMic() }
|
|
|
+ .onSuccess {
|
|
|
+ isMicRunning = true
|
|
|
+ Log.d(TAG, "ASR startAsrWithMic()")
|
|
|
+ }
|
|
|
+ .onFailure { Log.e(TAG, "ASR startAsrWithMic failed: ${it.message}") }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun setupAsrCallbacks(asrClient: AsrClient) {
|
|
|
+ asrClient.setListener(object : AsrClient.Listener {
|
|
|
+ override fun onOpen() {
|
|
|
+ isConnected = true
|
|
|
+ Log.d(TAG, "ASR websocket open")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onStart(taskId: String) {
|
|
|
+ currentPartial = ""
|
|
|
+ sessionBuilder.clear()
|
|
|
+ Log.d(TAG, "ASR started: $taskId")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onPartialResult(taskId: String, text: String) {
|
|
|
+ // 滚动更新当前识别中间结果
|
|
|
+ currentPartial = text
|
|
|
+ Log.d(TAG, "ASR partial: $text")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onFinalResult(taskId: String, text: String) {
|
|
|
+ // 将最终结果追加拼接到会话字符串
|
|
|
+ sessionBuilder.append(text)
|
|
|
+ val fullText = sessionBuilder.toString()
|
|
|
+ isMicRunning = false
|
|
|
+ Log.d(TAG, "ASR final result: $fullText")
|
|
|
+ IntentRecognizeHelper.recognize(
|
|
|
+ text = fullText,
|
|
|
+ scence = scene,
|
|
|
+ onSuccess = { action ->
|
|
|
+ if (action.name == "goToDecisionCenter") {
|
|
|
+ onGoToDecisionCenter?.invoke(action)
|
|
|
+ } else {
|
|
|
+ "需要跳转任务列表".showMessage()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onFinished(taskId: String) {
|
|
|
+ Log.d(TAG, "ASR ended: $taskId")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onError(code: Int, message: String) {
|
|
|
+ Log.e(TAG, "ASR error code=$code msg=$message")
|
|
|
+ isMicRunning = false
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onClosed(code: Int, reason: String) {
|
|
|
+ isConnected = false
|
|
|
+ isMicRunning = false
|
|
|
+ Log.d(TAG, "ASR closed code=$code reason=$reason")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 离线关键词回调:匹配唤醒词时启动麦克风
|
|
|
+ override fun onOfflineCmd(cmd: String) {
|
|
|
+ if (cmd == WAKE_WORD) {
|
|
|
+ Log.d(TAG, "Wake word triggered, starting mic")
|
|
|
+ asrStartMic()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun close() {
|
|
|
+ OfflineCmdServiceHelper.removeOnLineListener(this)
|
|
|
+ if (isMicRunning) {
|
|
|
+ runCatching { asr?.stopAsrWithMic() }
|
|
|
+ isMicRunning = false
|
|
|
+ }
|
|
|
+ asr?.close()
|
|
|
+ sdk?.close()
|
|
|
+ asr = null
|
|
|
+ sdk = null
|
|
|
+ isConnected = false
|
|
|
+ sessionBuilder.clear()
|
|
|
+ Log.d(TAG, "AsrHelper closed")
|
|
|
+ }
|
|
|
+}
|