Browse Source

修改讯飞语音识别

weicu
tanlongsheng 11 months ago
parent
commit
56349b4f13
  1. 143
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  2. 57
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt

143
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -34,6 +34,7 @@ class AzureAsrHelper(private val context: Context) {
// 添加前台服务管理标志 // 添加前台服务管理标志
private var isForegroundServiceRunning = false private var isForegroundServiceRunning = false
// 配置参数 // 配置参数
private var currentLanguage = "zh-CN" private var currentLanguage = "zh-CN"
private var supportedLanguages = arrayOf("zh-CN") private var supportedLanguages = arrayOf("zh-CN")
@ -41,6 +42,9 @@ class AzureAsrHelper(private val context: Context) {
private var subscriptionKey = "" private var subscriptionKey = ""
private var region = "" private var region = ""
private var useXunfei = false
private var xunFeiAsrHelper: XunFeiAsrHelper? = null
// 音频源配置 // 音频源配置
var audioSourceType = AudioSourceType.MICROPHONE var audioSourceType = AudioSourceType.MICROPHONE
@ -53,6 +57,7 @@ class AzureAsrHelper(private val context: Context) {
// 网络丢失计数器 // 网络丢失计数器
private var networkLostCount = 0 private var networkLostCount = 0
// 最大网络丢失次数(3秒检测) // 最大网络丢失次数(3秒检测)
private val maxNetworkLostCount = 3 private val maxNetworkLostCount = 3
@ -109,6 +114,8 @@ class AzureAsrHelper(private val context: Context) {
this.supportedLanguages = supportedLanguages this.supportedLanguages = supportedLanguages
} }
this.useXunfei = supportedLanguages.any { it == "zh-CN" || it == "en-US" }
// 根据支持的语言数量决定是否启用自动语言检测 // 根据支持的语言数量决定是否启用自动语言检测
this.isAutoDetectLanguage = supportedLanguages.size >= 2 this.isAutoDetectLanguage = supportedLanguages.size >= 2
@ -243,7 +250,7 @@ class AzureAsrHelper(private val context: Context) {
// 检查网络状态 // 检查网络状态
if (!checkNetworkStatus()) { if (!checkNetworkStatus()) {
Log.e(tag, "网络不可用,无法开始语音识别") Log.e(tag, "网络不可用,无法开始语音识别")
continuousCallback?.onError(currsessionid,1000,"网络连接不可用,请检查网络设置") continuousCallback?.onError(currsessionid, 1000, "网络连接不可用,请检查网络设置")
return false return false
} }
@ -259,7 +266,48 @@ class AzureAsrHelper(private val context: Context) {
Log.d(tag, "前台服务已启动") Log.d(tag, "前台服务已启动")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "启动前台服务失败: ${e.message}") Log.e(tag, "启动前台服务失败: ${e.message}")
continuousCallback?.onError(currsessionid,1003, "启动前台服务失败: ${e.message}") continuousCallback?.onError(currsessionid, 1003, "启动前台服务失败: ${e.message}")
return false
}
}
if (useXunfei) {
Log.d(tag, "Using Xunfei Recognition")
val callback = continuousCallback
if (callback == null) {
Log.e(tag, "无法启动连续识别:回调为空")
continuousCallback?.onError(currsessionid, 1001, "回调为空")
return false
}
if (xunFeiAsrHelper == null) {
xunFeiAsrHelper = XunFeiAsrHelper(context)
}
try {
xunFeiAsrHelper?.start(callback)
this.audioSourceType = audioSourceType
if (!audioStream.isContinuousRecognitionActive) {
audioStream.startAudioRecord(
when (audioSourceType) {
AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE
AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL
},
object : SimpleAudioReceiver.AudioDataCallback {
override fun onAudio(data: ByteArray) {
audioDataCallback?.onAudio(data)
xunFeiAsrHelper?.sendAudio(data)
}
}
)
}
audioStream.isContinuousRecognitionActive = true
return true
} catch (e: Exception) {
Log.e(tag, "Start Xunfei failed: ${e.message}")
xunFeiAsrHelper?.stop()
audioStream.isContinuousRecognitionActive = false
return false return false
} }
} }
@ -269,7 +317,7 @@ class AzureAsrHelper(private val context: Context) {
val callback = continuousCallback val callback = continuousCallback
if (callback == null) { if (callback == null) {
Log.e(tag, "无法重新启动连续识别:回调为空") Log.e(tag, "无法重新启动连续识别:回调为空")
continuousCallback?.onError(currsessionid,1001,"回调为空") continuousCallback?.onError(currsessionid, 1001, "回调为空")
return false return false
} }
setupEventListeners(callback) setupEventListeners(callback)
@ -309,7 +357,7 @@ class AzureAsrHelper(private val context: Context) {
fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean { fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean {
Log.d(tag, "设置ssssss监听器:${speechConfig ?: "null"} ") Log.d(tag, "设置ssssss监听器:${speechConfig ?: "null"} ")
if (speechConfig == null) { if (speechConfig == null) {
callback.onError(currsessionid,1002,"语音服务未初始化") callback.onError(currsessionid, 1002, "语音服务未初始化")
return false return false
} }
@ -331,7 +379,7 @@ class AzureAsrHelper(private val context: Context) {
} else { } else {
currentLanguage currentLanguage
} }
callback.onRecognizing(currsessionid,event.result.text, detectedLanguage) callback.onRecognizing(currsessionid, event.result.text, detectedLanguage)
} }
} }
) )
@ -346,7 +394,7 @@ class AzureAsrHelper(private val context: Context) {
} else { } else {
currentLanguage currentLanguage
} }
callback.onResult(currsessionid,event.result.text, detectedLanguage) callback.onResult(currsessionid, event.result.text, detectedLanguage)
currsessionid = UUID.randomUUID().toString() currsessionid = UUID.randomUUID().toString()
} }
} }
@ -381,11 +429,11 @@ class AzureAsrHelper(private val context: Context) {
val reason = event.reason.toString() val reason = event.reason.toString()
Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails") Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails")
callback.onCanceled(currsessionid,reason,errorDetails) callback.onCanceled(currsessionid, reason, errorDetails)
} }
) )
} catch (e: Exception) { } catch (e: Exception) {
callback.onError(currsessionid,1001,"启动连续识别失败: ${e.message}") callback.onError(currsessionid, 1001, "启动连续识别失败: ${e.message}")
return false return false
} }
return true return true
@ -398,6 +446,27 @@ class AzureAsrHelper(private val context: Context) {
* @return 是否成功停止 * @return 是否成功停止
*/ */
fun stopContinuousRecognition(): Boolean { fun stopContinuousRecognition(): Boolean {
if (useXunfei) {
try {
xunFeiAsrHelper?.stop()
stopAudioProcessing()
audioStream.isContinuousRecognitionActive = false
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
} catch (e: Exception) {
Log.e(tag, "停止前台服务失败: ${e.message}")
}
}
return true
} catch (e: Exception) {
Log.e(tag, "Stop Xunfei failed: ${e.message}")
return false
}
}
if (speechConfig == null) { if (speechConfig == null) {
Log.e(tag, "语音服务未初始化") Log.e(tag, "语音服务未初始化")
return false return false
@ -462,8 +531,12 @@ class AzureAsrHelper(private val context: Context) {
try { try {
// 如果正在进行连续识别,先停止 // 如果正在进行连续识别,先停止
if (audioStream.isContinuousRecognitionActive) { if (audioStream.isContinuousRecognitionActive) {
if (useXunfei) {
xunFeiAsrHelper?.stop()
} else {
// 直接停止,不等待结果 // 直接停止,不等待结果
recognizer?.stopContinuousRecognitionAsync() recognizer?.stopContinuousRecognitionAsync()
}
audioStream.isContinuousRecognitionActive = false audioStream.isContinuousRecognitionActive = false
} }
// 停止音频处理 // 停止音频处理
@ -477,6 +550,9 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.close() recognizer?.close()
recognizer = null recognizer = null
xunFeiAsrHelper?.stop()
xunFeiAsrHelper = null
// 释放speechConfig // 释放speechConfig
speechConfig?.close() speechConfig?.close()
speechConfig = null speechConfig = null
@ -526,17 +602,18 @@ class AzureAsrHelper(private val context: Context) {
} }
//录音音频文件 //录音音频文件
/** /**
* 开启录音 * 开启录音
*/ */
fun enableRecord(audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null) { fun enableRecord(
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,
filePath: String,
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null
) {
Log.i(tag, "开启录音:") Log.i(tag, "开启录音:")
this.audioSourceType = audioSourceType this.audioSourceType = audioSourceType
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里录音") Log.i(tag, "外部音频源不在这里录音")
return return
} }
@ -565,7 +642,8 @@ class AzureAsrHelper(private val context: Context) {
audioStream.setupStream() audioStream.setupStream()
audioStream.startAudioRecord( when (audioSourceType) { audioStream.startAudioRecord(
when (audioSourceType) {
AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE
AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL
}, },
@ -582,7 +660,7 @@ class AzureAsrHelper(private val context: Context) {
* 移动文件到新路径 * 移动文件到新路径
*/ */
fun moveFile(sourcePath: String, destPath: String): Boolean { fun moveFile(sourcePath: String, destPath: String): Boolean {
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里移动文件") Log.i(tag, "外部音频源不在这里移动文件")
return false return false
} }
@ -595,7 +673,7 @@ class AzureAsrHelper(private val context: Context) {
* 重命名指定路径的音频文件 * 重命名指定路径的音频文件
*/ */
fun renameFile(filePath: String, newName: String): Boolean { fun renameFile(filePath: String, newName: String): Boolean {
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里重命名文件") Log.i(tag, "外部音频源不在这里重命名文件")
return false return false
} }
@ -608,7 +686,7 @@ class AzureAsrHelper(private val context: Context) {
* 停止录音 * 停止录音
*/ */
fun pauseRecord() { fun pauseRecord() {
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里暂停录音") Log.i(tag, "外部音频源不在这里暂停录音")
return return
} }
@ -621,7 +699,7 @@ class AzureAsrHelper(private val context: Context) {
* 继续录音 * 继续录音
*/ */
fun resumeRecord() { fun resumeRecord() {
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里继续录音") Log.i(tag, "外部音频源不在这里继续录音")
return return
} }
@ -634,7 +712,7 @@ class AzureAsrHelper(private val context: Context) {
* 关闭录音 * 关闭录音
*/ */
fun stopRecord(isSave: Boolean) { fun stopRecord(isSave: Boolean) {
if(audioSourceType == AudioSourceType.EXTERNAL){ if (audioSourceType == AudioSourceType.EXTERNAL) {
Log.i(tag, "外部音频源不在这里关闭录音") Log.i(tag, "外部音频源不在这里关闭录音")
return return
} }
@ -661,7 +739,6 @@ class AzureAsrHelper(private val context: Context) {
} }
/** /**
* 一次性识别回调接口 * 一次性识别回调接口
*/ */
@ -680,7 +757,7 @@ class AzureAsrHelper(private val context: Context) {
* @param code 错误码 * @param code 错误码
* @param error 错误信息 * @param error 错误信息
*/ */
fun onError(code: Int,error: String) fun onError(code: Int, error: String)
} }
/** /**
@ -693,7 +770,7 @@ class AzureAsrHelper(private val context: Context) {
* @param text 识别的文本 * @param text 识别的文本
* @param detectedLanguage 检测到的语言 * @param detectedLanguage 检测到的语言
*/ */
fun onResult(sessiond:String, text: String, detectedLanguage: String) fun onResult(sessiond: String, text: String, detectedLanguage: String)
/** /**
* 识别进行中调用 * 识别进行中调用
@ -701,17 +778,17 @@ class AzureAsrHelper(private val context: Context) {
* @param recognizing 正在识别的文本 * @param recognizing 正在识别的文本
* @param detectedLanguage 检测到的语言 * @param detectedLanguage 检测到的语言
*/ */
fun onRecognizing(sessiond:String, recognizing: String, detectedLanguage: String) fun onRecognizing(sessiond: String, recognizing: String, detectedLanguage: String)
/** /**
* 会话开始时调用 * 会话开始时调用
*/ */
fun onSessionStarted(sessiond:String) fun onSessionStarted(sessiond: String)
/** /**
* 会话结束时调用 * 会话结束时调用
*/ */
fun onSessionStopped(sessiond:String) fun onSessionStopped(sessiond: String)
/** /**
@ -720,14 +797,14 @@ class AzureAsrHelper(private val context: Context) {
* @param reason 取消原因 * @param reason 取消原因
* @param errorDetails 错误详情 * @param errorDetails 错误详情
*/ */
fun onCanceled(sessiond:String,reason: String, errorDetails: String) fun onCanceled(sessiond: String, reason: String, errorDetails: String)
/** /**
* 识别出错时调用 * 识别出错时调用
* *
* @param error 错误信息 * @param error 错误信息
*/ */
fun onError(sessiond:String, code: Int,error: String) fun onError(sessiond: String, code: Int, error: String)
} }
@ -738,6 +815,7 @@ class AzureAsrHelper(private val context: Context) {
private fun checkNetworkStatus(): Boolean { private fun checkNetworkStatus(): Boolean {
return networkMonitor.checkNetworkStatus() return networkMonitor.checkNetworkStatus()
} }
/** /**
* 检查是否是网络相关错误 * 检查是否是网络相关错误
*/ */
@ -785,8 +863,8 @@ class AzureAsrHelper(private val context: Context) {
lastNetworkLostTime = 0L lastNetworkLostTime = 0L
isNetworkRecovering = true isNetworkRecovering = true
} else { } else {
continuousCallback?.onError(currsessionid,1000,"网络连接不可用,请检查网络设置") continuousCallback?.onError(currsessionid, 1000, "网络连接不可用,请检查网络设置")
Log.d(tag,"网络仍未恢复,继续检测") Log.d(tag, "网络仍未恢复,继续检测")
networkLostCount = 0 networkLostCount = 0
stopAudioProcessingImmediately() stopAudioProcessingImmediately()
} }
@ -805,14 +883,15 @@ class AzureAsrHelper(private val context: Context) {
Log.e(tag, "立即停止音频处理失败: ${e.message}") Log.e(tag, "立即停止音频处理失败: ${e.message}")
} }
} }
/**
/**
* 清理网络检测相关资源 * 清理网络检测相关资源
*/ */
private fun clearNetworkDetection() { private fun clearNetworkDetection() {
networkLostCount = 0 networkLostCount = 0
lastNetworkLostTime = 0L lastNetworkLostTime = 0L
} }
} }

57
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt

@ -4,11 +4,12 @@ import android.content.Context
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import okhttp3.* import okhttp3.*
import okio.ByteString import okio.ByteString.Companion.toByteString
import org.json.JSONObject import org.json.JSONObject
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
import java.util.UUID
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@ -16,77 +17,62 @@ class XunFeiAsrHelper(private val context: Context) {
private val tag = "XunFeiAsrHelper" private val tag = "XunFeiAsrHelper"
private val appId = "43d87d22" private val appId = "43d87d22"
private val accessKeyId = "2bd01ce06ac35957fc0c082f9b6284ec" private val accessKeyId = "83f063755e8d342670002f50a4471d42"
private var webSocket: WebSocket? = null private var webSocket: WebSocket? = null
private val client = OkHttpClient() private val client = OkHttpClient()
// Status: "UNDEFINED", "CONNECTING", "OPEN", "CLOSING", "CLOSED" private var currentSessionId = ""
private var btnStatus = "UNDEFINED"
private var resultText = "" fun start(callback: AzureAsrHelper.ContinuousRecognizeCallback) {
currentSessionId = UUID.randomUUID().toString()
var onResult: ((String) -> Unit)? = null
var onError: ((String) -> Unit)? = null
var onStatusChange: ((String) -> Unit)? = null
fun connectWebSocket() {
val url = getWebSocketUrl() val url = getWebSocketUrl()
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
changeBtnStatus("CONNECTING")
webSocket = client.newWebSocket(request, object : WebSocketListener() { webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
Log.d(tag, "WebSocket Opened") Log.d(tag, "WebSocket Opened")
changeBtnStatus("OPEN") callback.onSessionStarted(currentSessionId)
} }
override fun onMessage(webSocket: WebSocket, text: String) { override fun onMessage(webSocket: WebSocket, text: String) {
renderResult(text) renderResult(text, callback)
} }
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
Log.d(tag, "WebSocket Closing: $code / $reason") Log.d(tag, "WebSocket Closing: $code / $reason")
changeBtnStatus("CLOSING")
webSocket.close(1000, null) webSocket.close(1000, null)
} }
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
Log.d(tag, "WebSocket Closed: $code / $reason") Log.d(tag, "WebSocket Closed: $code / $reason")
changeBtnStatus("CLOSED") callback.onSessionStopped(currentSessionId)
} }
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Log.e(tag, "WebSocket Error", t) Log.e(tag, "WebSocket Error", t)
changeBtnStatus("CLOSED") callback.onError(currentSessionId, 1004, "Xunfei connection failed: ${t.message}")
onError?.invoke(t.message ?: "WebSocket connection failed")
} }
}) })
} }
fun sendAudio(frameBuffer: ByteArray) { fun sendAudio(frameBuffer: ByteArray) {
webSocket?.let { ws -> webSocket?.let { ws: WebSocket ->
ws.send(ByteString.of(frameBuffer, 0, frameBuffer.size)) ws.send(frameBuffer.toByteString(0, frameBuffer.size))
} }
} }
fun close() { fun stop() {
webSocket?.let { ws -> webSocket?.let { ws: WebSocket ->
ws.send("{\"end\": true}") ws.send("{\"end\": true}")
changeBtnStatus("CLOSING")
}
}
private fun changeBtnStatus(status: String) {
btnStatus = status
if (status == "CONNECTING") {
resultText = ""
} }
onStatusChange?.invoke(status)
} }
private fun renderResult(resultData: String) { private fun renderResult(
resultData: String,
callback: AzureAsrHelper.ContinuousRecognizeCallback
) {
try { try {
val jsonData = JSONObject(resultData) val jsonData = JSONObject(resultData)
val action = jsonData.optString("action") val action = jsonData.optString("action")
@ -120,16 +106,15 @@ class XunFeiAsrHelper(private val context: Context) {
} }
if (st.optInt("type") == 0) { if (st.optInt("type") == 0) {
resultText += resultTextTemp callback.onResult(currentSessionId, resultTextTemp, "zh-CN")
onResult?.invoke(resultText)
} else { } else {
onResult?.invoke(resultText + resultTextTemp) callback.onRecognizing(currentSessionId, resultTextTemp, "zh-CN")
} }
} }
"error" -> { "error" -> {
Log.e(tag, "Error: $resultData") Log.e(tag, "Error: $resultData")
onError?.invoke(jsonData.toString()) callback.onError(currentSessionId, 1005, "Xunfei error: $resultData")
} }
} }
} catch (e: Exception) { } catch (e: Exception) {

Loading…
Cancel
Save