From e14efd859d6f60109e31c2add6c6daeb3aba5182 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 24 Jan 2026 10:22:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E4=B8=8ETTS=E6=92=AD=E6=94=BE=E7=9A=84?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E4=BC=9A=E8=AF=9D=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复iOS端音频会话冲突问题,避免语音识别时TTS仍在播放导致无声或异常。 - 注释掉AgentServiceImpl中初始化时设置识别模式和音频会话的代码,避免干扰 - 调整translation_controller中ASR服务启动逻辑,同时传译模式使用phone_call,面对面模式使用push_to_talk - 修复AzureTtsHelper中播放状态管理,增加播放失败处理和状态重置 - 重新启用音频路由变化处理,当检测到音频会话切换到录音时立即停止TTS播放 - 添加调试日志便于问题追踪 --- .../controllers/translation_controller.dart | 8 +- .../agent_service/AgentServiceImpl.swift | 4 +- .../Sources/azure_speech/AzureTtsHelper.swift | 87 +++++++------------ .../Sources/tools/SimpleAudioReceiver.swift | 9 +- 4 files changed, 46 insertions(+), 62 deletions(-) diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index c1d57845b..808a35bb4 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -957,10 +957,14 @@ class TranslationController extends GetxController with WidgetsBindingObserver { return; } if (currentMode.value == 'simultaneous') { - await _startAsrService('push_to_talk'); - } else if (currentMode.value == 'faceToFace') { + Logger.info('liwei-------- 开始语音识别,模式:同声传译,ASR phone_call'); await _startAsrService('phone_call'); + } else if (currentMode.value == 'faceToFace') { + Logger.info('iwei-------- 开始语音识别,模式:面对面,ASR push_to_talk'); + await _startAsrService('push_to_talk'); } else { + Logger.info( + 'iwei-------- 开始语音识别,模式:${currentMode.value},ASR 模式:normal'); await _startAsrService('normal'); } await _finalizeRecognitionStart(); 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 61f5e4d8a..1f1be4a8f 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 @@ -313,8 +313,8 @@ class AgentServiceImpl: NSObject { LocationService.shared.getCurrentLocation() { location in // self.locationInfo = location } - currentRecognitionMode = "normal" - checkAdnSetAudioSession() + // currentRecognitionMode = "normal" + // checkAdnSetAudioSession() return isInitialized } diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift index 68ae36cd2..cb1be7642 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift @@ -549,7 +549,7 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate { } speaking = true // lastSpokenText = text - // os_log("调用链: performSynthesis 准备合成 session=%{public}@ pushStream=%{public}@ suppress=%{public}@ textLen=%{public}d", log: log, type: .info, sessionid, isUsingPushStreamCapture.description, suppressStreamPlayback.description, text.count) + os_log("liwei----------- 调用链: performSynthesis 准备合成 session=%{public}@ pushStream=%{public}@ suppress=%{public}@ textLen=%{public}d", log: log, type: .info, sessionid, isUsingPushStreamCapture.description, suppressStreamPlayback.description, text.count) if !applyAudioSessionForTTS() { speaking = false notifyEvent(eventType: .error, params: [ @@ -1088,6 +1088,10 @@ private func checkStreamPlaybackCompletedIfNeeded() { * - Throws: 无 */ private func startPlaybackIfNeeded() { + if isPlaying, audioPlayer?.isPlaying != true { + isPlaying = false + audioPlayer = nil + } guard !isPlaying, !playbackQueue.isEmpty else { return } let next = playbackQueue.first! let playableData = ensureWavDataForPlayback(next) @@ -1106,15 +1110,20 @@ private func checkStreamPlaybackCompletedIfNeeded() { } audioPlayer?.delegate = self audioPlayer?.prepareToPlay() - isPlaying = true - // os_log("调用链: 开始播放 队首长度=%{public}dB session=%{public}@", log: log, type: .info, next.count, sessionid) - notifyEvent(eventType: .playbackStarted) - audioPlayer?.play() + let started = audioPlayer?.play() == true + if started { + isPlaying = true + // os_log("调用链: 开始播放 队首长度=%{public}dB session=%{public}@", log: log, type: .info, next.count, sessionid) + notifyEvent(eventType: .playbackStarted) + } else { + throw NSError(domain: "AzureTtsHelper", code: -1, userInfo: [NSLocalizedDescriptionKey: "AVAudioPlayer.play() 返回 false"]) + } } catch { os_log("播放初始化失败: %{public}@", log: log, type: .error, error.localizedDescription) // 异常情况下移除该条并尝试播放下一条 playbackQueue.removeFirst() isPlaying = false + audioPlayer = nil startPlaybackIfNeeded() } } @@ -1415,6 +1424,7 @@ private func ensureAudioSessionQueueSpecificKeySet() { */ private func applyAudioSessionForTTS() -> Bool { var success = true + os_log("liwei-------------TTS applyAudioSessionForTTS currentRecognitionMode:%{public}@", log: self.log, type: .info, currentRecognitionMode) ensureAudioSessionQueueSpecificKeySet() let applyBody = { if self.isApplyingAudioSessionConfig { return } @@ -1648,55 +1658,24 @@ private func deactivateAudioSessionAfterTTSIfIdle() { * - 配置过程中(isApplyingAudioSessionConfig = true)直接忽略 */ @objc private func handleRouteChange(_ notification: Notification) { - // guard let info = notification.userInfo, - // let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, - // let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } - - // if !isTtsBusy() { return } - // os_log("音频路由变化: %{public}@", log: log, type: .info, String(describing: reason)) - - // // 配置中触发的路由变化,直接忽略,避免重入导致死循环 - // if isApplyingAudioSessionConfig { - // os_log("配置进行中,忽略本次路由变化", log: log, type: .debug) - // return - // } - - // // 忽略会造成循环的原因:categoryChange(你的日志 reason=3),以及 routeConfigurationChange 等 - // switch reason { - // case .categoryChange, .routeConfigurationChange, .wakeFromSleep, .noSuitableRouteForCategory: - // os_log("忽略路由变化原因: %{public}@", log: log, type: .debug, String(describing: reason)) - // return - // default: - // break - // } - - // // 通话期间避免重配,避免与电话路由冲突 - // if isTelephonyActive() { - // os_log("通话期间检测到路由变化,跳过重配", log: log, type: .info) - // return - // } - - // let isActivelyPlayingNow = - // speaking || - // isPlaying || - // (audioPlayer?.isPlaying == true) || - // (playerNode?.isPlaying == true) - // if isActivelyPlayingNow { - // routeChangeDebounceWorkItem?.cancel() - // _ = stop() - // os_log("路由变化时正在播报,已停止以避免音源冲突", log: log, type: .info) - // return - // } - - // 去抖处理:300ms 内只执行最后一次重配 - // 忽略 categoryChange 等 - // 去抖:在 audioSessionQueue 上延迟调用 - // routeChangeDebounceWorkItem?.cancel() - // let work = DispatchWorkItem { [weak self] in - // self?.safeConfigureAudioSessionForTTS() - // } - // routeChangeDebounceWorkItem = work - // audioSessionQueue.asyncAfter(deadline: .now() + 0.3, execute: work) + guard let info = notification.userInfo, + let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, + let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } + + if reason != .categoryChange { return } + + let session = AVAudioSession.sharedInstance() + if session.category != .record { return } + + let isActivelyPlayingNow = + speaking || + isPlaying || + (audioPlayer?.isPlaying == true) || + (playerNode?.isPlaying == true) + if !isActivelyPlayingNow { return } + + _ = stop() + os_log("检测到音频会话切到录音,停止TTS以避免冲突", log: log, type: .info) } /** 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 3522a1630..364e0dd9f 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 @@ -135,7 +135,7 @@ public class SimpleAudioReceiver: NSObject { _isWriting = true - print("音源设置---------- startAudioRecord=mod:\(mod), audioSourceType:\(audioSourceType), isHeadphones:\(isHeadphones)") + print("liwei---------- startAudioRecord=mod:\(mod), audioSourceType:\(audioSourceType), isHeadphones:\(isHeadphones)") do { try configureAudioSession() @@ -284,11 +284,12 @@ public class SimpleAudioReceiver: NSObject { /** * 开启音频写入线程 - * 修复:使用userInitiated QoS避免优先级反转 + * 参数:无 + * 返回值:无 + * 异常:无 */ public func startWriteThread() { - // 使用userInitiated QoS匹配音频录制线程的优先级 - writeThread = DispatchQueue(label: "audio.stream.writer", qos: .userInitiated) + writeThread = DispatchQueue(label: "audio.stream.writer", qos: .default) writeThread?.async { [weak self] in guard let self = self else { return }