|
|
|
@ -149,7 +149,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
return false |
|
|
|
} |
|
|
|
// 启动音频处理 |
|
|
|
audioStream?.startAudioInput() |
|
|
|
audioStream?.startAudioInput(audioSourceType: audioSourceType) |
|
|
|
|
|
|
|
// 开始连续识别 |
|
|
|
try recognizer?.startContinuousRecognition() |
|
|
|
@ -172,7 +172,6 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* @return 是否成功停止 |
|
|
|
*/ |
|
|
|
public func stopContinuousRecognition() -> Bool { |
|
|
|
print("stopContinuousRecognition") |
|
|
|
guard speechConfig != nil else { |
|
|
|
os_log("语音服务未初始化", log: log, type: .error) |
|
|
|
return false |
|
|
|
@ -238,7 +237,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
recognizer = nil |
|
|
|
speechConfig = nil |
|
|
|
audioConfig = nil |
|
|
|
|
|
|
|
audioStream = nil |
|
|
|
// 确保状态被重置 |
|
|
|
_isContinuousRecognitionActive = false |
|
|
|
//externalAudioStream = nil |
|
|
|
@ -249,6 +248,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
audioConfig = nil |
|
|
|
recognizer = nil |
|
|
|
speechConfig = nil |
|
|
|
audioStream = nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -302,7 +302,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
if (audioStream == nil) { |
|
|
|
// 创建外部音频拉流对象 |
|
|
|
// 明确指定使用外部音频源(推流模式) |
|
|
|
audioStream = AudioStream(audioSourceType: audioSourceType) |
|
|
|
audioStream = AudioStream() |
|
|
|
audioStream?.initAudioRecord() |
|
|
|
} |
|
|
|
|
|
|
|
@ -328,28 +328,29 @@ public class AzureAsrHelper: NSObject { |
|
|
|
return false |
|
|
|
} |
|
|
|
guard let recognizer = recognizer else { return false} |
|
|
|
// 识别中事件 |
|
|
|
recognizer.addRecognizingEventHandler { [weak self] (sender, event) in |
|
|
|
guard let self = self else { return } |
|
|
|
print("识别中事件:") |
|
|
|
let result = event.result |
|
|
|
let detectedLanguage = self.getDetectedLanguage(from: result) |
|
|
|
// os_log("识别中: %{public}@", log: self.log, type: .info, result.text ?? "") |
|
|
|
// 直接在当前线程调用回调 |
|
|
|
// 正在识别事件 |
|
|
|
recognizer.addRecognizingEventHandler { [weak self] (_, event) in |
|
|
|
guard let self = self else { return } |
|
|
|
let result = event.result |
|
|
|
let detectedLanguage = self.detectLanguage(from: result) |
|
|
|
print("识别中事件: \(detectedLanguage)") |
|
|
|
DispatchQueue.main.async { |
|
|
|
callback.onRecognizing(result.text ?? "", detectedLanguage) |
|
|
|
} |
|
|
|
|
|
|
|
// 识别完成事件 |
|
|
|
recognizer.addRecognizedEventHandler { [weak self] (sender, event) in |
|
|
|
guard let self = self else { return } |
|
|
|
print("识别完成事件:") |
|
|
|
let result = event.result |
|
|
|
if result.reason == SPXResultReason.recognizedSpeech { |
|
|
|
let detectedLanguage = self.getDetectedLanguage(from: result) |
|
|
|
// 直接在当前线程调用回调 |
|
|
|
} |
|
|
|
|
|
|
|
// 识别完成事件 |
|
|
|
recognizer.addRecognizedEventHandler { [weak self] (_, event) in |
|
|
|
guard let self = self else { return } |
|
|
|
let result = event.result |
|
|
|
if result.reason == .recognizedSpeech { |
|
|
|
let detectedLanguage = self.detectLanguage(from: result) |
|
|
|
print("识别完成事件: \(detectedLanguage)") |
|
|
|
DispatchQueue.main.async { |
|
|
|
callback.onResult(result.text ?? "", detectedLanguage) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 会话开始事件 |
|
|
|
recognizer.addSessionStartedEventHandler { (sender, event) in |
|
|
|
@ -381,7 +382,16 @@ public class AzureAsrHelper: NSObject { |
|
|
|
} |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 提取的辅助方法 |
|
|
|
private func detectLanguage(from result: SPXSpeechRecognitionResult) -> String { |
|
|
|
if isAutoDetectLanguage { |
|
|
|
let langResult = try SPXAutoDetectSourceLanguageResult(result) |
|
|
|
return langResult.language ?? "" |
|
|
|
} else { |
|
|
|
return currentLanguage |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* 获取检测到的语言 |
|
|
|
*/ |
|
|
|
@ -399,6 +409,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
// 尝试另一种方式获取语言 |
|
|
|
do { |
|
|
|
let langResult = try SPXAutoDetectSourceLanguageResult(result) |
|
|
|
print("获取检测到的语言") |
|
|
|
return langResult.language ?? "" |
|
|
|
} catch { |
|
|
|
return "" |
|
|
|
@ -490,24 +501,26 @@ public class AzureAsrHelper: NSObject { |
|
|
|
private let writeQueue = LinkedBlockingQueue<Data>() |
|
|
|
private var audioEngine: AVAudioEngine? |
|
|
|
private var audioFormat: AVAudioFormat? |
|
|
|
private let audioSourceType: AudioSourceType // 添加引用 |
|
|
|
private let audioSession = AVAudioSession.sharedInstance() |
|
|
|
private var audioSourceType = AudioSourceType.microphone |
|
|
|
private var isRunning = false |
|
|
|
public var isWriting = false |
|
|
|
private let bufferSize: Int = 4096 |
|
|
|
private let writeThread = DispatchQueue(label: "audio.stream.writer") |
|
|
|
|
|
|
|
init(audioSourceType: AudioSourceType) { |
|
|
|
self.audioSourceType = audioSourceType |
|
|
|
} |
|
|
|
private var writeThread: DispatchQueue? |
|
|
|
private var currentRoute: AudioOutputRoute? |
|
|
|
|
|
|
|
/** |
|
|
|
* 初始化 |
|
|
|
*/ |
|
|
|
public func initAudioRecord() { |
|
|
|
print("初始化了") |
|
|
|
audioFormat = getOptimalAudioFormat() |
|
|
|
pushAudioStream = SPXPushAudioInputStream() |
|
|
|
isRunning=true |
|
|
|
// 创建新的写线程 |
|
|
|
writeThread = DispatchQueue(label: "audio.stream.writer") |
|
|
|
startWriteThread() |
|
|
|
|
|
|
|
self.setAudioOutputRoute(.receiver) |
|
|
|
} |
|
|
|
|
|
|
|
/// 获取最佳音频格式 (iOS 通常支持标准采样率) |
|
|
|
@ -523,8 +536,9 @@ public class AzureAsrHelper: NSObject { |
|
|
|
/** |
|
|
|
* 开始音频输入 |
|
|
|
*/ |
|
|
|
public func startAudioInput() { |
|
|
|
public func startAudioInput(audioSourceType: AudioSourceType = .microphone) { |
|
|
|
isWriting=true |
|
|
|
self.audioSourceType = audioSourceType; |
|
|
|
print("startAudioInput=audioSourceType\(audioSourceType)") |
|
|
|
switch audioSourceType { |
|
|
|
case .microphone: |
|
|
|
@ -538,10 +552,11 @@ public class AzureAsrHelper: NSObject { |
|
|
|
* 开启音频写入线程 |
|
|
|
*/ |
|
|
|
public func startWriteThread() { |
|
|
|
writeThread.async { [weak self] in |
|
|
|
writeThread?.async { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
while self.isRunning { |
|
|
|
print("是否写入: \(self.isWriting)") |
|
|
|
if !self.isWriting { |
|
|
|
usleep(10_000) |
|
|
|
continue |
|
|
|
@ -549,7 +564,7 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
guard let dataToWrite = self.writeQueue.take() else { continue } |
|
|
|
|
|
|
|
//print("写入数据长度: \(dataToWrite.count)") |
|
|
|
print("写入数据长度: \(dataToWrite.count)") |
|
|
|
self.pushAudioStream?.write(dataToWrite) |
|
|
|
} |
|
|
|
} |
|
|
|
@ -565,14 +580,13 @@ public class AzureAsrHelper: NSObject { |
|
|
|
if audioSourceType != .external { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
print("外部data=\(data)") |
|
|
|
// 放入队列,由写线程写入 |
|
|
|
writeQueue.put(data) |
|
|
|
} |
|
|
|
|
|
|
|
private func runMicrophoneCapture() { |
|
|
|
do { |
|
|
|
//self.setAudioOutputRoute(.speaker) |
|
|
|
audioEngine = AVAudioEngine() |
|
|
|
guard let inputNode = audioEngine?.inputNode else { |
|
|
|
throw NSError(domain: "AudioSetup", code: 1) |
|
|
|
@ -622,8 +636,10 @@ public class AzureAsrHelper: NSObject { |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
try audioEngine?.start() |
|
|
|
// 重新激活 |
|
|
|
try audioSession.setActive(true, options: [.notifyOthersOnDeactivation]) |
|
|
|
|
|
|
|
try audioEngine?.start() |
|
|
|
} catch { |
|
|
|
print("麦克风启动失败: \(error)") |
|
|
|
} |
|
|
|
@ -664,23 +680,27 @@ public class AzureAsrHelper: NSObject { |
|
|
|
if audioSourceType == .external { |
|
|
|
//pushAudioData(data: Data()) |
|
|
|
|
|
|
|
audioEngine?.stop() |
|
|
|
audioEngine?.inputNode.removeTap(onBus: 0) |
|
|
|
audioEngine?.pause() |
|
|
|
//audioEngine?.inputNode.removeTap(onBus: 0) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public func stopAudioCapture() { |
|
|
|
guard isWriting==true else { return } |
|
|
|
print("stopContinuousRecognition") |
|
|
|
isWriting=false |
|
|
|
audioEngine?.stop() |
|
|
|
audioEngine?.pause() |
|
|
|
audioEngine?.inputNode.removeTap(onBus: 0) |
|
|
|
writeThread.async { |
|
|
|
writeThread?.async { |
|
|
|
self.writeQueue.close() |
|
|
|
} |
|
|
|
} |
|
|
|
public func releaseAudioResources() { |
|
|
|
|
|
|
|
print("释放了") |
|
|
|
stopAudioCapture() |
|
|
|
audioEngine?.stop() |
|
|
|
writeThread = nil |
|
|
|
audioEngine?.inputNode.removeTap(onBus: 0) |
|
|
|
isRunning=false |
|
|
|
audioEngine = nil |
|
|
|
} |
|
|
|
@ -692,13 +712,18 @@ public class AzureAsrHelper: NSObject { |
|
|
|
case bluetooth |
|
|
|
} |
|
|
|
public func setAudioOutputRoute(_ route: AudioOutputRoute) { |
|
|
|
let audioSession = AVAudioSession.sharedInstance() |
|
|
|
guard currentRoute != route else { return } |
|
|
|
print("setAudioOutputRoute") |
|
|
|
do { |
|
|
|
isWriting=false |
|
|
|
try audioEngine?.stop() |
|
|
|
// |
|
|
|
if self.isWriting { |
|
|
|
print("当前正在识别") |
|
|
|
try audioEngine?.pause() |
|
|
|
// 先停用以避免冲突 |
|
|
|
try audioSession.setActive(false) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
switch route { |
|
|
|
case .speaker: |
|
|
|
// 使用扬声器时必须用videoChat模式 |
|
|
|
@ -728,12 +753,17 @@ public class AzureAsrHelper: NSObject { |
|
|
|
try audioSession.overrideOutputAudioPort(.none) |
|
|
|
// 不需要override,系统自动路由 |
|
|
|
} |
|
|
|
|
|
|
|
self.currentRoute = route |
|
|
|
if self.isWriting { |
|
|
|
|
|
|
|
// 重新激活 |
|
|
|
try audioSession.setActive(true, options: [.notifyOthersOnDeactivation]) |
|
|
|
|
|
|
|
try audioEngine?.start() |
|
|
|
isWriting=true |
|
|
|
writeThread?.async { |
|
|
|
self.writeQueue.close() |
|
|
|
} |
|
|
|
} |
|
|
|
} catch { |
|
|
|
print("路由切换失败: \(error)") |
|
|
|
} |
|
|
|
|