AsrHelper.kt 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package com.nova.brain.glass.helper
  2. import android.util.Log
  3. import com.nova.brain.glass.BuildConfig
  4. import com.nova.brain.glass.model.RecognizeAction
  5. import com.rokid.online.speech.AsrClient
  6. import com.rokid.online.speech.OnlineSpeechSdk
  7. import com.rokid.online.speech.OnlineSpeechSdkConfig
  8. import com.rokid.online.speech.TtsClient
  9. import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer
  10. import com.rokid.online.speech.open.OpenSdkAudioSource
  11. import com.xuqm.base.extensions.showMessage
  12. object AsrHelper : OfflineCmdListener {
  13. private const val TAG = "AsrHelper"
  14. // 配置信息来自 BuildConfig(在 app/build.gradle 的 buildConfigField 中维护)
  15. private val DOMAIN get() = BuildConfig.SPEECH_DOMAIN
  16. private val AK get() = BuildConfig.SPEECH_AK
  17. private val SK get() = BuildConfig.SPEECH_SK
  18. private val UID get() = BuildConfig.SPEECH_UID
  19. private val DEVICE_ID get() = BuildConfig.SPEECH_DEVICE_ID
  20. private val ASR_PATH get() = BuildConfig.SPEECH_ASR_PATH
  21. private val TTS_PATH get() = BuildConfig.SPEECH_TTS_PATH
  22. // 唤醒词:Nova Nova
  23. private const val WAKE_WORD = "Nova Nova"
  24. private const val WAKE_WORD_PINYIN = "nou wa nou wa"
  25. private var sdk: OnlineSpeechSdk? = null
  26. private var asr: AsrClient? = null
  27. private var tts: TtsClient? = null
  28. private val audioSource = OpenSdkAudioSource()
  29. private val ttsPlayer = AndroidPcmTtsStreamPlayer()
  30. private var isConnected = false
  31. private var isMicRunning = false
  32. private var isTtsConnected = false
  33. private const val WAKE_RESPONSE = "在呢,您请说"
  34. // 拼接每次识别会话中的中间结果
  35. private var currentPartial = ""
  36. /** 当前页面的场景标识,由各 Activity 在 onResume/onPause 中维护 */
  37. var scene: String = "home"
  38. /** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */
  39. var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null
  40. fun init() {
  41. val cfg = OnlineSpeechSdkConfig(
  42. domain = DOMAIN,
  43. ak = AK,
  44. sk = SK,
  45. uid = UID,
  46. deviceId = DEVICE_ID,
  47. asrPath = ASR_PATH,
  48. ttsPath = TTS_PATH,
  49. trustAllCerts = true,
  50. staticHttpHeaders = mapOf(
  51. "appCredential" to "userInfo",
  52. "messageId" to "android-asr-${System.currentTimeMillis()}",
  53. ),
  54. staticMessageHeaders = mapOf(
  55. "appCredential" to "userInfo",
  56. "messageId" to "android-asr-${System.currentTimeMillis()}",
  57. ),
  58. )
  59. sdk = OnlineSpeechSdk(cfg)
  60. asr = sdk!!.createAsrClient().attachAudioSource(audioSource).also { setupAsrCallbacks(it) }
  61. tts = sdk!!.createTtsClient().attachStreamPlayer(ttsPlayer).also { setupTtsCallbacks(it) }
  62. // 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR
  63. OfflineCmdServiceHelper.registerAsrWakeWord()
  64. OfflineCmdServiceHelper.addOnLineListener(this)
  65. // 自动建立 ASR / TTS 连接
  66. asrConnect()
  67. tts?.connect()
  68. Log.d(TAG, "AsrHelper init done")
  69. }
  70. private fun asrConnect() {
  71. asr?.connect()
  72. Log.d(TAG, "ASR connect() called")
  73. }
  74. private fun asrStartMic() {
  75. if (!isConnected) {
  76. Log.w(TAG, "ASR startMic ignored: not connected")
  77. return
  78. }
  79. if (isMicRunning) {
  80. Log.w(TAG, "ASR startMic ignored: mic already running")
  81. return
  82. }
  83. runCatching { asr?.startAsrWithMic() }
  84. .onSuccess {
  85. isMicRunning = true
  86. Log.d(TAG, "ASR startAsrWithMic()")
  87. }
  88. .onFailure { Log.e(TAG, "ASR startAsrWithMic failed: ${it.message}") }
  89. }
  90. private fun setupAsrCallbacks(asrClient: AsrClient) {
  91. asrClient.setListener(object : AsrClient.Listener {
  92. override fun onOpen() {
  93. isConnected = true
  94. Log.d(TAG, "ASR websocket open")
  95. }
  96. override fun onStart(taskId: String) {
  97. currentPartial = ""
  98. Log.d(TAG, "ASR started: $taskId")
  99. }
  100. override fun onPartialResult(taskId: String, text: String) {
  101. // 滚动更新当前识别中间结果
  102. currentPartial += text
  103. Log.d(TAG, "ASR partial: $text")
  104. }
  105. override fun onFinalResult(taskId: String, text: String) {
  106. // 将最终结果追加拼接到会话字符串
  107. isMicRunning = false
  108. // 滚动更新当前识别中间结果
  109. currentPartial += text
  110. Log.d(TAG, "ASR final result: $currentPartial")
  111. IntentRecognizeHelper.recognize(
  112. text = currentPartial,
  113. scence = scene,
  114. onSuccess = { action ->
  115. if (action.name == "goToDecisionCenter") {
  116. onGoToDecisionCenter?.invoke(action)
  117. } else {
  118. "需要跳转任务列表".showMessage()
  119. }
  120. }
  121. )
  122. }
  123. override fun onFinished(taskId: String) {
  124. Log.d(TAG, "ASR ended: $taskId")
  125. }
  126. override fun onError(code: Int, message: String) {
  127. Log.e(TAG, "ASR error code=$code msg=$message")
  128. isMicRunning = false
  129. }
  130. override fun onClosed(code: Int, reason: String) {
  131. isConnected = false
  132. isMicRunning = false
  133. Log.d(TAG, "ASR closed code=$code reason=$reason")
  134. }
  135. })
  136. }
  137. private fun setupTtsCallbacks(ttsClient: TtsClient) {
  138. ttsClient.setListener(object : TtsClient.Listener {
  139. override fun onOpen() {
  140. isTtsConnected = true
  141. Log.d(TAG, "TTS websocket open")
  142. }
  143. override fun onFinished(taskId: String) {
  144. Log.d(TAG, "TTS ended: $taskId, starting mic")
  145. asrStartMic()
  146. }
  147. override fun onError(code: Int, message: String) {
  148. Log.e(TAG, "TTS error code=$code msg=$message, fallback to mic")
  149. asrStartMic()
  150. }
  151. override fun onClosed(code: Int, reason: String) {
  152. isTtsConnected = false
  153. Log.d(TAG, "TTS closed code=$code reason=$reason")
  154. }
  155. })
  156. }
  157. // 离线关键词回调:唤醒词触发时先 TTS 播报,播报结束后启动麦克风
  158. override fun onOfflineCmd(cmd: String) {
  159. if (cmd == WAKE_WORD) {
  160. Log.d(TAG, "Wake word triggered")
  161. if (isTtsConnected) {
  162. tts?.speak(WAKE_RESPONSE)
  163. } else {
  164. Log.w(TAG, "TTS not connected, starting mic directly")
  165. asrStartMic()
  166. }
  167. }
  168. }
  169. fun close() {
  170. OfflineCmdServiceHelper.removeOnLineListener(this)
  171. if (isMicRunning) {
  172. runCatching { asr?.stopAsrWithMic() }
  173. isMicRunning = false
  174. }
  175. asr?.close()
  176. tts?.close()
  177. sdk?.close()
  178. asr = null
  179. tts = null
  180. sdk = null
  181. isConnected = false
  182. isTtsConnected = false
  183. Log.d(TAG, "AsrHelper closed")
  184. }
  185. }