|
|
|
@ -41,6 +41,11 @@ public class AzureAsrHelper: NSObject { |
|
|
|
private var lastAudioStartTime: Int64 = 0 |
|
|
|
private let audioOperationDebounceInterval: Int64 = 500 // 500毫秒防抖间隔 |
|
|
|
private var audioStartWorkItem: DispatchWorkItem? |
|
|
|
// 串行队列和状态标记,用于顺序控制音频启动/停止,避免竞态 |
|
|
|
private let audioOpQueue = DispatchQueue(label: "com.azure.speech.audio.operations") |
|
|
|
private var isAudioStarting = false |
|
|
|
private var isAudioStarted = false |
|
|
|
private var pendingStopRequest = false |
|
|
|
|
|
|
|
// 状态管理 |
|
|
|
private var _isContinuousRecognitionActive = false |
|
|
|
@ -269,9 +274,27 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
os_log("开始异步启动音频录制", log: self.log, type: .info) |
|
|
|
|
|
|
|
// 在后台队列执行耗时的音频启动操作 |
|
|
|
DispatchQueue.global(qos: .userInitiated).async { |
|
|
|
// 使用串行队列,保证与 stop 顺序执行,避免竞态 |
|
|
|
self.audioOpQueue.async { [weak self] in |
|
|
|
guard let self = self else { |
|
|
|
DispatchQueue.main.async { completion(false) } |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 准备启动 |
|
|
|
self.isAudioStarting = true |
|
|
|
self.pendingStopRequest = false // 启动前清空旧的停止请求 |
|
|
|
let success = self.performAudioStart(audioSourceType: audioSourceType, audioDataCallback: audioDataCallback) |
|
|
|
self.isAudioStarting = false |
|
|
|
self.isAudioStarted = success |
|
|
|
|
|
|
|
// 如果启动过程中有 stop 请求,立即顺序停止,确保麦克风不会被重新“开启” |
|
|
|
if self.pendingStopRequest { |
|
|
|
self.pendingStopRequest = false |
|
|
|
_ = self.performAudioStop() |
|
|
|
self.isAudioStarted = false |
|
|
|
os_log("启动完成后检测到待停止请求,已立即停止音频", log: self.log, type: .info) |
|
|
|
} |
|
|
|
|
|
|
|
// 回到主队列执行完成回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
@ -318,9 +341,18 @@ public class AzureAsrHelper: NSObject { |
|
|
|
private func stopAudioRecordAsync(completion: @escaping (Bool) -> Void) { |
|
|
|
os_log("开始立即停止音频录制", log: log, type: .info) |
|
|
|
|
|
|
|
// 在后台队列执行耗时的音频停止操作 |
|
|
|
DispatchQueue.global(qos: .userInitiated).async { |
|
|
|
// 在串行队列顺序执行停止,避免与启动并发导致的竞态 |
|
|
|
audioOpQueue.async { [weak self] in |
|
|
|
guard let self = self else { |
|
|
|
DispatchQueue.main.async { completion(false) } |
|
|
|
return |
|
|
|
} |
|
|
|
// 若启动过程中收到停止请求,记录标记,启动完成后会立即停止 |
|
|
|
if self.isAudioStarting { |
|
|
|
self.pendingStopRequest = true |
|
|
|
} |
|
|
|
let success = self.performAudioStop() |
|
|
|
self.isAudioStarted = false |
|
|
|
|
|
|
|
// 回到主队列执行完成回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
@ -333,7 +365,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* 执行音频停止操作 |
|
|
|
*/ |
|
|
|
private func performAudioStop() -> Bool { |
|
|
|
do { |
|
|
|
do { |
|
|
|
audioStream?.stopMicrophoneCapture() |
|
|
|
os_log("音频录制停止成功", log: log, type: .info) |
|
|
|
return true |
|
|
|
@ -433,16 +465,10 @@ public class AzureAsrHelper: NSObject { |
|
|
|
os_log("语音服务未初始化", log: log, type: .error) |
|
|
|
return false |
|
|
|
} |
|
|
|
guard let recognizer = recognizer else { |
|
|
|
os_log("识别器为空,重置状态", log: log, type: .info) |
|
|
|
// 仍需取消潜在的挂起启动任务,避免稍后被触发 |
|
|
|
cancelPendingAudioStart() |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
|
|
|
|
// 关键:取消尚未执行的启动任务,防止 stop 后音频又被启动 |
|
|
|
cancelPendingAudioStart() |
|
|
|
|
|
|
|
@ -457,7 +483,12 @@ public class AzureAsrHelper: NSObject { |
|
|
|
os_log("音频停止失败", log: self.log, type: .error) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
guard let recognizer = recognizer else { |
|
|
|
os_log("识别器为空,重置状态", log: log, type: .info) |
|
|
|
// 仍需取消潜在的挂起启动任务,避免稍后被触发 |
|
|
|
cancelPendingAudioStart() |
|
|
|
return true |
|
|
|
} |
|
|
|
// 停止连续识别 |
|
|
|
try recognizer.stopContinuousRecognition() |
|
|
|
self.recognizer = nil |
|
|
|
@ -480,6 +511,12 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
// 尝试安全地停止识别器 |
|
|
|
do { |
|
|
|
guard let recognizer = recognizer else { |
|
|
|
os_log("识别器为空,重置状态", log: log, type: .info) |
|
|
|
// 仍需取消潜在的挂起启动任务,避免稍后被触发 |
|
|
|
cancelPendingAudioStart() |
|
|
|
return true |
|
|
|
} |
|
|
|
try recognizer.stopContinuousRecognition() |
|
|
|
self.recognizer = nil |
|
|
|
} catch { |
|
|
|
|