|
|
|
@ -39,7 +39,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
// 防抖机制相关变量 - 只保留开始操作的防抖 |
|
|
|
private var lastAudioStartTime: Int64 = 0 |
|
|
|
private let audioOperationDebounceInterval: Int64 = 500 // 500毫秒防抖间隔 |
|
|
|
private let audioOperationDebounceInterval: Int64 = 0 // 500毫秒防抖间隔 |
|
|
|
private var audioStartWorkItem: DispatchWorkItem? |
|
|
|
// 串行队列和状态标记,用于顺序控制音频启动/停止,避免竞态 |
|
|
|
private let audioOpQueue = DispatchQueue(label: "com.azure.speech.audio.operations") |
|
|
|
@ -47,8 +47,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
private var isAudioStarted = false |
|
|
|
private var pendingStopRequest = false |
|
|
|
|
|
|
|
// 状态管理 |
|
|
|
private var _isContinuousRecognitionActive = false |
|
|
|
|
|
|
|
|
|
|
|
// 配置参数 |
|
|
|
private var currentLanguage = "zh-CN" |
|
|
|
@ -66,7 +65,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
case external |
|
|
|
} |
|
|
|
|
|
|
|
private var audioSourceType = AudioSourceType.microphone |
|
|
|
public var audioSourceType = AudioSourceType.microphone |
|
|
|
|
|
|
|
// 音频处理 |
|
|
|
// private var externalAudioStream: ExternalAudioPullStream? |
|
|
|
@ -240,7 +239,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
/// |
|
|
|
/// 修复点: |
|
|
|
/// 1) 新任务创建前,取消旧的未执行任务,确保只有“最后一次启动请求”会生效 |
|
|
|
/// 2) 任务执行前二次校验:必须是当前挂起任务且 _isContinuousRecognitionActive 仍为 true 才执行启动 |
|
|
|
/// 2) 任务执行前二次校验:必须是当前挂起任务且 audioStream?.isContinuousRecognitionActive 仍为 true 才执行启动 |
|
|
|
private func startAudioRecordAsync( |
|
|
|
audioSourceType: AudioSourceType, |
|
|
|
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil, |
|
|
|
@ -264,9 +263,9 @@ public class AzureAsrHelper: NSObject { |
|
|
|
completion(false) |
|
|
|
return |
|
|
|
} |
|
|
|
// print("startAudioRecordAsync\(_isContinuousRecognitionActive)") |
|
|
|
// print("startAudioRecordAsync\(audioStream?.isContinuousRecognitionActive)") |
|
|
|
// // 二次校验:stop 可能已发生,确认仍需启动 |
|
|
|
// guard self._isContinuousRecognitionActive else { |
|
|
|
// guard self.audioStream?.isContinuousRecognitionActive else { |
|
|
|
// os_log("已收到停止请求,取消启动音频录制", log: self.log, type: .info) |
|
|
|
// completion(false) |
|
|
|
// return |
|
|
|
@ -321,10 +320,12 @@ public class AzureAsrHelper: NSObject { |
|
|
|
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? |
|
|
|
) -> Bool { |
|
|
|
do { |
|
|
|
if(!audioStream!.isContinuousRecognitionActive){ |
|
|
|
audioStream?.startAudioRecord( |
|
|
|
audioSourceType: audioSourceType == .microphone ? .microphone : .external, |
|
|
|
audioDataCallback: audioDataCallback |
|
|
|
) |
|
|
|
} |
|
|
|
os_log("音频录制启动成功", log: log, type: .info) |
|
|
|
return true |
|
|
|
} catch { |
|
|
|
@ -382,7 +383,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* @param audioDataCallback 音频数据回调 |
|
|
|
* @return 是否成功开始识别(调度成功即返回 true) |
|
|
|
* |
|
|
|
* 修复点:在音频启动成功后的闭包中,先判断 _isContinuousRecognitionActive 是否仍然为 true, |
|
|
|
* 修复点:在音频启动成功后的闭包中,先判断 audioStream?.isContinuousRecognitionActive 是否仍然为 true, |
|
|
|
* 若 stop 已发生则直接跳过 recognizer 的启动,避免“已停止但仍启动识别”的情况。 |
|
|
|
*/ |
|
|
|
public func startContinuousRecognition( |
|
|
|
@ -399,7 +400,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
if _isContinuousRecognitionActive { |
|
|
|
if audioStream?.isContinuousRecognitionActive == true { |
|
|
|
print("连续识别已在进行中") |
|
|
|
return true |
|
|
|
} |
|
|
|
@ -421,7 +422,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
print("startContinuousRecognition:\(audioSourceType)") |
|
|
|
// 移除这里的状态设置,等音频启动成功后再设置 |
|
|
|
// self._isContinuousRecognitionActive = true |
|
|
|
// self.audioStream?.isContinuousRecognitionActive = true |
|
|
|
|
|
|
|
// 异步启动音频处理 |
|
|
|
startAudioRecordAsync(audioSourceType: audioSourceType, audioDataCallback: audioDataCallback) { [weak self] (success: Bool) in |
|
|
|
@ -429,8 +430,8 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
if success { |
|
|
|
// 只有在音频启动成功后才设置状态为 true |
|
|
|
self._isContinuousRecognitionActive = true |
|
|
|
print("startContinuousRecognition:\(self._isContinuousRecognitionActive)") |
|
|
|
self.audioStream?.isContinuousRecognitionActive = true |
|
|
|
print("startContinuousRecognition:\(self.audioStream?.isContinuousRecognitionActive)") |
|
|
|
|
|
|
|
// 启动语音识别 |
|
|
|
do { |
|
|
|
@ -439,18 +440,18 @@ public class AzureAsrHelper: NSObject { |
|
|
|
} catch { |
|
|
|
os_log("启动连续识别失败: %{public}@", log: self.log, type: .error, error.localizedDescription) |
|
|
|
self.stopContinuousRecognition() |
|
|
|
self._isContinuousRecognitionActive = false |
|
|
|
self.audioStream?.isContinuousRecognitionActive = false |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("音频启动失败,无法开始识别", log: self.log, type: .error) |
|
|
|
self._isContinuousRecognitionActive = false |
|
|
|
self.audioStream?.isContinuousRecognitionActive = false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
} catch { |
|
|
|
stopContinuousRecognition() |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
@ -478,7 +479,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
guard let self = self else { return } |
|
|
|
if success { |
|
|
|
// 标记不再活跃,供启动任务二次校验 |
|
|
|
self._isContinuousRecognitionActive = false |
|
|
|
self.audioStream?.isContinuousRecognitionActive = false |
|
|
|
os_log("音频停止成功", log: self.log, type: .info) |
|
|
|
} else { |
|
|
|
os_log("音频停止失败", log: self.log, type: .error) |
|
|
|
@ -497,7 +498,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
return true |
|
|
|
} catch { |
|
|
|
// 强制重置状态 |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
|
|
|
|
// 关键:即使异常,也取消未执行的启动任务 |
|
|
|
cancelPendingAudioStart() |
|
|
|
@ -532,7 +533,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* 检查连续识别是否活跃 |
|
|
|
*/ |
|
|
|
public func isContinuousRecognitionActive() -> Bool { |
|
|
|
return self._isContinuousRecognitionActive |
|
|
|
return self.audioStream?.isContinuousRecognitionActive == true |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -542,10 +543,10 @@ public class AzureAsrHelper: NSObject { |
|
|
|
do { |
|
|
|
print("释放所有资源:") |
|
|
|
// 如果正在进行连续识别,先停止 |
|
|
|
if _isContinuousRecognitionActive { |
|
|
|
if audioStream?.isContinuousRecognitionActive == true { |
|
|
|
// 直接停止,不等待结果 |
|
|
|
try? recognizer?.stopContinuousRecognition() |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
} |
|
|
|
// 取消防抖任务 - 只取消开始操作的防抖 |
|
|
|
audioStartWorkItem?.cancel() |
|
|
|
@ -559,11 +560,11 @@ public class AzureAsrHelper: NSObject { |
|
|
|
audioConfig = nil |
|
|
|
audioStream = nil |
|
|
|
// 确保状态被重置 |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
//externalAudioStream = nil |
|
|
|
} catch { |
|
|
|
// 确保状态被重置 |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
//externalAudioStream = nil |
|
|
|
audioConfig = nil |
|
|
|
recognizer = nil |
|
|
|
@ -599,12 +600,12 @@ public class AzureAsrHelper: NSObject { |
|
|
|
*/ |
|
|
|
private func setupRecognizer() -> Bool { |
|
|
|
do { |
|
|
|
print("设置识别器\(_isContinuousRecognitionActive)") |
|
|
|
print("设置识别器\(audioStream?.isContinuousRecognitionActive ?? false)") |
|
|
|
|
|
|
|
// 如果正在进行连续识别,先停止 |
|
|
|
if _isContinuousRecognitionActive { |
|
|
|
if audioStream?.isContinuousRecognitionActive == true { |
|
|
|
try? recognizer?.stopContinuousRecognition() |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
audioStream?.isContinuousRecognitionActive = false |
|
|
|
} |
|
|
|
|
|
|
|
// 【优化】只有在音频流未初始化时才创建,避免重复初始化 |
|
|
|
@ -741,7 +742,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
self.isAutoDetectLanguage = (validatedSourceLang != validatedTargetLang) |
|
|
|
|
|
|
|
// 如果正在识别,需要重新设置识别器 |
|
|
|
if _isContinuousRecognitionActive { |
|
|
|
if audioStream?.isContinuousRecognitionActive == true { |
|
|
|
let wasActive = stopContinuousRecognition() |
|
|
|
if wasActive { |
|
|
|
return setupRecognizer() |
|
|
|
@ -815,7 +816,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
print("会话结束事件:") |
|
|
|
// 直接在当前线程调用回调 |
|
|
|
callback.onSessionStopped() |
|
|
|
self._isContinuousRecognitionActive = false |
|
|
|
self.audioStream?.isContinuousRecognitionActive = false |
|
|
|
//self.stopAudioProcessing() |
|
|
|
} |
|
|
|
|
|
|
|
@ -865,12 +866,17 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
/** |
|
|
|
* 开启录音 |
|
|
|
* @param audioSourceType 音频源类型 |
|
|
|
* @param filePath 录音文件路径 |
|
|
|
* @param audioDataCallback 音频数据回调接口 |
|
|
|
*/ |
|
|
|
public func enableRecord(filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil) { |
|
|
|
public func enableRecord( audioSourceType: AudioSourceType = .microphone,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil) { |
|
|
|
os_log("开启录音: %{public}@", log: log, type: .info, filePath) |
|
|
|
|
|
|
|
self.audioSourceType = audioSourceType |
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log("外部音频源不在这里录音", log: log, type: .info) |
|
|
|
return |
|
|
|
} |
|
|
|
if audioStream == nil { |
|
|
|
// 创建外部音频拉流对象 |
|
|
|
audioStream = SimpleAudioReceiver() |
|
|
|
@ -881,21 +887,17 @@ public class AzureAsrHelper: NSObject { |
|
|
|
audioStream?.recordfile = RecordFile() |
|
|
|
} |
|
|
|
|
|
|
|
self.audioSourceType = .microphone |
|
|
|
|
|
|
|
// 修复:移除多余的 audioDataCallback 参数 |
|
|
|
// 修复:添加类型转换 |
|
|
|
audioStream?.startAudioRecord( |
|
|
|
audioSourceType: { |
|
|
|
switch audioSourceType { |
|
|
|
case .microphone: |
|
|
|
return SimpleAudioReceiver.AudioSourceType.microphone |
|
|
|
case .external: |
|
|
|
return SimpleAudioReceiver.AudioSourceType.external |
|
|
|
} |
|
|
|
}(), |
|
|
|
audioDataCallback: audioDataCallback |
|
|
|
) |
|
|
|
if(!audioStream!.isContinuousRecognitionActive){ |
|
|
|
// 异步启动音频处理 |
|
|
|
startAudioRecordAsync(audioSourceType: audioSourceType, audioDataCallback: audioDataCallback) { [weak self] (success: Bool) in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
audioStream?.isRecord = true |
|
|
|
audioStream?.recordfile?.closeFile(isSave: true) |
|
|
|
audioStream?.recordfile?.creatingFiles(atPath: filePath) |
|
|
|
} |
|
|
|
@ -905,7 +907,10 @@ public class AzureAsrHelper: NSObject { |
|
|
|
*/ |
|
|
|
public func moveFile(sourcePath: String, destPath: String)-> Bool { |
|
|
|
|
|
|
|
|
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log(" 外部音频源不在这里移动文件", log: log, type: .info) |
|
|
|
return true |
|
|
|
} |
|
|
|
audioStream?.recordfile?.moveFile(from: sourcePath, to: destPath) // Fixed method call |
|
|
|
return true |
|
|
|
} |
|
|
|
@ -915,6 +920,10 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* 重命名指定路径的音频文件 |
|
|
|
*/ |
|
|
|
public func renameFile(filePath: String, newName: String)-> Bool { |
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log(" 外部音频源不在这里重命名文件", log: log, type: .info) |
|
|
|
return true |
|
|
|
} |
|
|
|
audioStream?.recordfile?.renameFile(at: filePath, to: newName) // Fixed method call |
|
|
|
return true |
|
|
|
} |
|
|
|
@ -925,32 +934,47 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* 暂停录音 |
|
|
|
*/ |
|
|
|
public func pauseRecord() { |
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log(" 外部音频源不在这里暂停录音", log: log, type: .info) |
|
|
|
return |
|
|
|
} |
|
|
|
os_log("暂停录音", log: log, type: .info) |
|
|
|
guard let audioStream = audioStream, audioStream.recordfile != nil else { |
|
|
|
return |
|
|
|
} |
|
|
|
audioStream.stopMicrophoneCapture() |
|
|
|
audioStream.isRecord = false |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 继续录音 |
|
|
|
*/ |
|
|
|
public func resumeRecord() { |
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log(" 外部音频源不在这里继续录音", log: log, type: .info) |
|
|
|
return |
|
|
|
} |
|
|
|
os_log("继续录音", log: log, type: .info) |
|
|
|
guard let audioStream = audioStream, audioStream.recordfile != nil else { |
|
|
|
return |
|
|
|
} |
|
|
|
audioStream.resumeRecord() |
|
|
|
audioStream.isRecord = true |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 关闭录音 |
|
|
|
*/ |
|
|
|
public func stopRecord(isSave: Bool) { |
|
|
|
if(audioSourceType == .external){ |
|
|
|
os_log(" 外部音频源不在这里关闭录音", log: log, type: .info) |
|
|
|
return |
|
|
|
} |
|
|
|
guard let audioStream = audioStream, audioStream.recordfile != nil else { |
|
|
|
return |
|
|
|
} |
|
|
|
audioStream.isRecord = false |
|
|
|
if (!audioStream.isContinuousRecognitionActive) { |
|
|
|
audioStream.stopMicrophoneCapture() |
|
|
|
} |
|
|
|
audioStream.recordfile?.isPause = false // |
|
|
|
audioStream.recordfile?.closeFile(isSave: true) // |
|
|
|
} |
|
|
|
|