diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 5ccfc94e9..5258483c9 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -598,7 +598,7 @@ object AgentService : CoroutineScope { val success = azureAsrHelper?.setupEventListeners(object : AzureAsrHelper.ContinuousRecognizeCallback { override fun onRecognizing(recognizing: String, detectedLanguage: String) { - if (recognizing.isNotEmpty()) { + if (recognizing.isNotEmpty()&&isKeepResult==true) { // 检测到语音,更新状态 _hasSpeechDetected.set(true) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index e8e13210c..b294b6a05 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -69,7 +69,9 @@ class AgentServiceImpl: NSObject { // 用途:解决 start 异步、stop 先于 onSessionStarted 到来时被忽略的问题 private var isStartingRecognition: Bool = false private var stopRequestedDuringStart: Bool = false - + //是否保留最终结果 + private var isKeepResult = true; + var currsessionId: String = "" var language: String = "" private var serverurl = "" @@ -354,6 +356,8 @@ class AgentServiceImpl: NSObject { if self.isRecognizing && !self.isSpeaking && !self.isAiStreaming { os_log("空闲检测触发自动停止,模式: %{public}@", log: self.logger, type: .info, self.currentRecognitionMode) + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() self.stopRecognition() self.sendEvent(name: "auto_stop", data: [ "reason": "idle_timeout", @@ -377,6 +381,14 @@ class AgentServiceImpl: NSObject { } } func startRecognition(useBle: Bool = false, mode: String = "normal") -> Bool { + // 接收到唤醒信号,打开编码器 (设备侧) + print("ai启动语音\(useBle))") + var openResult = false // 添加openResult变量定义 + if useBle{ + isKeepResult = false + // 捕获openEncoder的返回值 + openResult = BleService.shared.openEncoder() // 正确捕获返回值 + } if !isInitialized { sendError("服务未初始化", code: "NOT_INITIALIZED") return false @@ -387,14 +399,11 @@ class AgentServiceImpl: NSObject { sendError("无麦克风权限", code: "PERMISSION_DENIED") return false } - } - stopTts(); - if isRecognizing { - stopRecognition() - } - - // 接收到唤醒信号,打开编码器 (设备侧) - let openResult = BleService.shared.openEncoder() + } + stopRecognition() + stopTts() + + os_log("🎙️ 尝试打开编码器结果: %@", log: logger, type: .info, openResult ? "成功" : "失败") wasMusicPlayingBeforeRecognition = MusicService.shared.isPlaying @@ -417,7 +426,7 @@ class AgentServiceImpl: NSObject { // 新增:标记为"启动中",用于允许 stop 在启动未完成时也能生效 isStartingRecognition = true - + print("ai启动语音=\(audioSourceType)=\(useBle)") guard let success = azureAsrHelper?.startContinuousRecognition( // callback: self, @@ -428,7 +437,9 @@ class AgentServiceImpl: NSObject { sendError("启动语音识别失败", code: "RECOGNITION_START_ERROR") return false } - + if(useBle){ + isKeepResult = true + } // 根据模式启动相应的空闲检测(此处 guard isRecognizing,会在 onSessionStarted 中再次启动) startIdleCheckForMode(mode: mode) @@ -450,6 +461,7 @@ class AgentServiceImpl: NSObject { /// 特性: /// 1) 即便处于"启动中"(isStartingRecognition)也会打印日志并尝试停止 /// 2) 如果底层尚未完全进入可停止状态,记录一次待停止请求,待会话启动后立即停止 + print("停止识别语音,isRecognizing=\(isRecognizing),isStartingRecognition=\(isStartingRecognition)") if !(isRecognizing || isStartingRecognition) { // 既不在识别中,也不在启动中,视为幂等处理 return true @@ -473,8 +485,6 @@ class AgentServiceImpl: NSObject { MusicService.shared.resume() } - // 关闭编码器 (设备侧) - BleService.shared.closeCodec() // 重置识别模式 currentRecognitionMode = "normal" @@ -492,7 +502,7 @@ class AgentServiceImpl: NSObject { return false } guard let audioStream = azureAsrHelper?.audioStream else { - os_log("音频流未初始化", log: logger,type: .error) + os_log("AI音频流未初始化", log: logger,type: .error) return false } audioStream.saveAudioDataTo(data: audioData) @@ -1055,6 +1065,8 @@ private func jsonToString(_ json: [String: Any]) -> String? { // 如果正在识别,需要先停止 let wasRecognizing = isRecognizing if wasRecognizing { + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() _ = stopRecognition() } @@ -1229,6 +1241,8 @@ private func jsonToString(_ json: [String: Any]) -> String? { func dispose() -> Bool { print("ai释放资源") if isRecognizing { + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() stopRecognition() } @@ -1390,6 +1404,8 @@ class ChatApiStreamCallback: StreamCallback { print("onFunctionCall=functionCall\(functionCall)") if (name == "exit_interaction") { + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() agentService.stopRecognition() } @@ -1515,9 +1531,6 @@ extension AgentServiceImpl: BleService.Callback { func onWakeupSignalReceived() { - stopTts() - - stopRecognition() startRecognition(useBle: true, mode: "ble_wakeup") } @@ -1717,7 +1730,7 @@ class AudioPlayer { extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { func onResult(_ text: String, _ detectedLanguage: String) { - if !text.isEmpty { + if !text.isEmpty && isKeepResult { var data: [String: Any] = ["text": text] if !detectedLanguage.isEmpty { data["language"] = detectedLanguage @@ -1736,7 +1749,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { } func onRecognizing(_ recognizing: String, _ detectedLanguage: String) { - if !recognizing.isEmpty { + if !recognizing.isEmpty && isKeepResult { let previousHasSpeech = hasSpeechDetected hasSpeechDetected = true @@ -1772,6 +1785,8 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { // 新增:若在启动期间已经收到停止请求,则立即执行停止 if stopRequestedDuringStart { stopRequestedDuringStart = false + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() _ = stopRecognition() } } diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift index 45457b848..e785c5192 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift @@ -2,7 +2,7 @@ import Flutter import UIKit import Foundation import os.log - +import ble_service /// 代理服务Flutter插件,负责将Flutter调用桥接到原生实现 public class AgentServicePlugin: NSObject, FlutterPlugin { @@ -90,6 +90,8 @@ public class AgentServicePlugin: NSObject, FlutterPlugin { return } print("stopConversation") + // 关闭编码器 (设备侧) + BleService.shared.closeCodec() impl.stopRecognition() result(true) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift index 02db119fd..5aef34be7 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift @@ -144,11 +144,11 @@ public class SimpleAudioReceiver: NSObject { while self.isRunning { if !self._isWriting { - // print("startWriteThread 111") + usleep(10_000) continue } - // print("startWriteThread 222") + guard let dataToWrite = self.writeQueue.take() else { continue } do { @@ -157,14 +157,12 @@ public class SimpleAudioReceiver: NSObject { self.audioDataCallback!.onAudio(dataToWrite) } if self.pushAudioStream != nil && isContinuousRecognitionActive { - try self.pushAudioStream?.write(dataToWrite) } if self.recordfile != nil && isRecord{ - // print("写入recordfile数据长度: \(dataToWrite.count)") recordfile?.saveAudioDataToWav(dataToWrite) } - // print("写入数据长度: \(dataToWrite.count)") + } catch { print("推送音频数据失败: \(error.localizedDescription)") } @@ -183,9 +181,8 @@ public class SimpleAudioReceiver: NSObject { if audioSourceType != .external { return } - // print("外部data=\(data)") // 放入队列,由写线程写入 - writeQueue.put(data) + self.writeQueue.put(data) } /** @@ -220,18 +217,22 @@ public class SimpleAudioReceiver: NSObject { } } + /** + * 运行外部音频捕获 + * 修复OSStatus error -50问题 + */ private func runExternalCapture() { if audioSourceType == .external { - do { - try audioSession.setCategory(.playback, - mode: .videoChat, - options: [ .mixWithOthers,.allowBluetoothA2DP // 允许蓝牙耳机,不占用hfp链路 - ]) // 添加音频优先级控制 - try audioSession.overrideOutputAudioPort(.none) - try audioSession.setActive(true) - } catch { - print("设置音频会话失败: \(error.localizedDescription)") - } + // do { + // try audioSession.setCategory(.playback, + // mode: .videoChat, + // options: [ .mixWithOthers,.allowBluetoothA2DP // 允许蓝牙耳机,不占用hfp链路 + // ]) // 添加音频优先级控制 + // try audioSession.overrideOutputAudioPort(.none) + // try audioSession.setActive(true) + // } catch { + // print("设置音频会话失败: \(error.localizedDescription)") + // } //pushAudioData(data: Data()) micCapture.stopCapture() @@ -336,7 +337,7 @@ public func restoreOriginalAudioState() { public func stopMicrophoneCapture() { - + self.writeQueue.clear() _isWriting=false micCapture.stopCapture() print("stopMicrophoneCapture") @@ -517,6 +518,23 @@ private class LinkedBlockingQueue { // 通知有新元素可用 availableItems.signal() + } + + /** + * 清空队列 + */ + func clear() { + queueLock.lock() + defer { queueLock.unlock() } + + // 检查队列是否已关闭 + if isClosed { + return + } + + // 添加元素(无容量限制,与Android一致) + queue.removeAll() + } /** diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 97e43996c..8abffc462 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -202,13 +202,7 @@ object BleService { Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) // 根据需要决定是否因为Opus初始化失败而返回false } - recordfile = RecordingFile(this.context) - recordfile!!.fileName = "不拆分" - - recordfile1 = RecordingFile(this.context) - recordfile1!!.fileName = "重新压缩" isInitialized = true - startOpusStreamDecoding(false, 1, 16000, 40) return true } catch (e: Exception) { Log.e(TAG, "BLE服务初始化失败: ${e.message}", e) @@ -1701,7 +1695,7 @@ startBytesStatistics() recordfile1!!.creatingFiles() recordfile!!.closeFile() recordfile!!.creatingFiles() - // startOpusStreamDecoding(false, 1, 16000, 40) + startOpusStreamDecoding(false, 1, 16000, 40) // // 目前仅发送命令通知设备开始编码。 // return sendCommand( // BleConst.CMD_CONTROL_CODEC.toByte(), diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift index caf6d9b72..2b1245106 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift @@ -586,22 +586,24 @@ private var cmdReplyType: UInt8 = 0 } /// 打开编码器 - func openEncoder() -> Bool { + /// - Returns: 操作是否成功 + public func openEncoder() -> Bool { os_log("🎙️ 准备打开编码(设备侧)...", log: logger, type: .info) // 确保Opus解码器处于活跃状态(如果不是则启动) // if opusProcessor?.isDecoding() != true { startOpusDecoding() //} - let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) - let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) + // let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) + // let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) - if result { - os_log("✅ 编码器命令发送成功", log: logger, type: .info) - } else { - os_log("❌ 编码器命令发送失败", log: logger, type: .error) - } + // if result { + // os_log("✅ 编码器命令发送成功", log: logger, type: .info) + // } else { + // os_log("❌ 编码器命令发送失败", log: logger, type: .error) + // } - return result + //return result + return true } /// 打开解码器