You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
17 KiB

import Foundation
import AVFoundation
import MicrosoftCognitiveServicesSpeech
/// Azure 语音识别辅助类
class AzureAsrHelper: NSObject {
private var recognizer: SPXSpeechRecognizer?
private var speechConfig: SPXSpeechConfig?
private var audioConfig: SPXAudioConfig?
private var initialized = false
private var isContinuousRecognitionActive = false
private var currentLanguage = "zh-CN"
private var subscriptionKey = ""
private var serviceRegion = ""
private var isAutoDetectLanguage = false
private var supportedLanguages = ["zh-CN", "en-US"]
// 音频会话管理
private let audioSession = AVAudioSession.sharedInstance()
// 事件回调
private var eventHandler: (([String: Any]) -> Void)?
/// 设置事件处理器
///
/// - Parameter handler: 事件处理回调
func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) {
self.eventHandler = handler
}
/// 初始化语音识别服务
///
/// - Parameters:
/// - speechSubscriptionKey: Azure 语音服务订阅密钥
/// - serviceRegion: Azure 语音服务区域
/// - supportedLanguages: 支持的语言列表,默认为 ["zh-CN", "en-US"]
/// - Returns: 是否初始化成功
func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String] = ["zh-CN", "en-US"]) -> Bool {
print("[AzureAsrHelper] 初始化 Azure 语音服务")
// 检查配置是否为空
if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
return false
}
// 释放之前的资源
dispose()
// 保存配置
self.subscriptionKey = speechSubscriptionKey
self.serviceRegion = serviceRegion
// 设置语言
if supportedLanguages.isEmpty {
print("[AzureAsrHelper] 警告: 传入的支持语言列表为空,将使用默认语言")
} else {
self.supportedLanguages = supportedLanguages
}
// 根据支持的语言数量决定是否启用自动语言检测
self.isAutoDetectLanguage = supportedLanguages.count >= 2
// 如果只有一种语言,设置为当前语言
if !isAutoDetectLanguage && !supportedLanguages.isEmpty {
self.currentLanguage = supportedLanguages[0]
}
// 创建语音配置
do {
speechConfig = try SPXSpeechConfig(subscription: speechSubscriptionKey, region: serviceRegion)
// 设置语言配置
if isAutoDetectLanguage {
// 设置自动语言检测
try speechConfig?.setPropertyTo("Continuous", byId: SPXPropertyId.SpeechServiceConnection_LanguageIdMode)
} else {
// 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage
}
// 创建音频配置 - 使用默认麦克风
audioConfig = SPXAudioConfig.default()
// 创建识别器
if isAutoDetectLanguage {
let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(sourceLanguages: supportedLanguages)
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig!)
} else {
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
}
// 配置音频会话
try configureAudioSession()
initialized = true
print("[AzureAsrHelper] Azure 语音服务初始化成功")
return true
} catch {
print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)")
return false
}
}
/// 配置音频会话
private func configureAudioSession() throws {
print("[AzureAsrHelper] 开始配置音频会话...")
do {
// 设置音频会话类别和模式
try audioSession.setCategory(.record, mode: .measurement, options: [.duckOthers, .allowBluetooth])
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("[AzureAsrHelper] 警告: 通过AudioSessionManager配置音频会话失败")
throw error
}
}
/// 一次性语音识别
///
/// - Parameter completion: 完成回调,返回是否成功、识别文本、识别语言和可能的错误信息
func recognizeOnce(completion: @escaping (Bool, String?, String?, String?) -> Void) {
if !initialized {
completion(false, nil, nil, "语音服务未初始化")
return
}
// 重置 recognizer
if !resetRecognizer() {
completion(false, nil, nil, "重置识别器失败")
return
}
do {
// 激活音频会话
try audioSession.setActive(true)
// 添加识别事件处理
recognizer?.addRecognizedEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == .recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
completion(true, event.result.text, detectedLanguage, nil)
}
}
recognizer?.addRecognizingEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == .recognizingSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
// print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
}
}
// 添加会话事件处理
recognizer?.addSessionStartedEventHandler { _, _ in
print("[AzureAsrHelper] 识别会话已开始")
}
recognizer?.addSessionStoppedEventHandler { _, _ in
print("[AzureAsrHelper] 识别会话已结束")
}
// 添加取消事件处理
recognizer?.addCanceledEventHandler { _, event in
if let cancellationDetails = try? SPXCancellationDetails(fromCanceledRecognitionResult: event.result) {
let errorDetails = cancellationDetails.errorDetails ?? "未知错误"
print("[AzureAsrHelper] 识别取消: \(errorDetails)")
completion(false, nil, nil, "识别取消: \(errorDetails)")
}
}
// 执行识别
let result = try recognizer?.recognizeOnceAsync().get()
if result?.reason != .recognizedSpeech {
completion(false, nil, nil, "未能识别语音")
}
} catch {
completion(false, nil, nil, "识别异常: \(error.localizedDescription)")
}
}
/// 重置识别器
///
/// - Returns: 是否重置成功
private func resetRecognizer() -> Bool {
if !initialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
return false
}
// 检查配置是否为空
if subscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
return false
}
do {
// 释放之前的 recognizer
recognizer = nil
// 创建音频配置 - 使用默认麦克风
audioConfig = SPXAudioConfig.default()
// 重新创建识别器
if isAutoDetectLanguage {
let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(sourceLanguages: supportedLanguages)
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig!)
} else {
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
}
return true
} catch {
print("[AzureAsrHelper] 错误: 重置识别器失败: \(error.localizedDescription)")
return false
}
}
/// 获取检测到的语言
///
/// - Parameter result: 识别结果
/// - Returns: 检测到的语言代码
private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String {
if isAutoDetectLanguage {
do {
if let autoDetectResult = try SPXAutoDetectSourceLanguageResult(fromRecognitionResult: result) {
return autoDetectResult.language
}
return ""
} catch {
print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)")
return ""
}
} else {
return currentLanguage
}
}
/// 开始连续语音识别
///
/// - Returns: 是否成功启动连续识别
func startContinuousRecognition() -> Bool {
if !initialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
return false
}
// 检查配置是否为空
if subscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
return false
}
// 如果已经在进行连续识别,直接返回
if isContinuousRecognitionActive {
print("[AzureAsrHelper] 已经在进行连续识别中,忽略请求")
return true
}
// 重置 recognizer
if !resetRecognizer() {
print("[AzureAsrHelper] 尝试重新创建识别器...")
return false
}
do {
// 激活音频会话
try audioSession.setActive(true)
// 添加识别事件处理
recognizer?.addRecognizedEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == .recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
let eventData: [String: Any] = [
"eventType": "finalResult",
"text": event.result.text ?? "",
"language": detectedLanguage
]
self.eventHandler?(eventData)
}
}
// 识别中事件
recognizer?.addRecognizingEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == .recognizingSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
let eventData: [String: Any] = [
"eventType": "recognizing",
"text": event.result.text ?? "",
"language": detectedLanguage
]
self.eventHandler?(eventData)
}
}
// 会话事件
recognizer?.addSessionStartedEventHandler { [weak self] _, _ in
guard let self = self else { return }
let eventData: [String: Any] = [
"eventType": "sessionStarted"
]
self.eventHandler?(eventData)
}
recognizer?.addSessionStoppedEventHandler { [weak self] _, _ in
guard let self = self else { return }
self.isContinuousRecognitionActive = false
let eventData: [String: Any] = [
"eventType": "sessionStopped"
]
self.eventHandler?(eventData)
}
// 取消事件
recognizer?.addCanceledEventHandler { [weak self] _, event in
guard let self = self else { return }
self.isContinuousRecognitionActive = false
var errorMessage = "未知错误"
if let cancellationDetails = try? SPXCancellationDetails(fromCanceledRecognitionResult: event.result) {
errorMessage = cancellationDetails.errorDetails ?? "未知错误"
}
let eventData: [String: Any] = [
"eventType": "error",
"error": "识别取消: \(errorMessage)"
]
self.eventHandler?(eventData)
}
// 开始连续识别
try recognizer?.startContinuousRecognition()
isContinuousRecognitionActive = true
print("[AzureAsrHelper] 连续识别已启动")
return true
} catch {
print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)")
return false
}
}
/// 停止连续语音识别
///
/// - Returns: 是否成功停止连续识别
func stopContinuousRecognition() -> Bool {
if !initialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
return false
}
if !isContinuousRecognitionActive {
print("[AzureAsrHelper] 未进行连续识别,忽略停止请求")
return true
}
do {
print("[AzureAsrHelper] 停止连续语音识别")
if recognizer == nil {
if isContinuousRecognitionActive {
print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃")
}
isContinuousRecognitionActive = false
// 通知停止成功
let eventData: [String: Any] = [
"eventType": "success",
"message": "连续识别已停止"
]
eventHandler?(eventData)
return true
}
// 停止连续识别
try recognizer?.stopContinuousRecognition()
// 延迟一点时间确保处理完成
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
// 重置状态
self.isContinuousRecognitionActive = false
// 恢复音频会话
do {
try self.audioSession.setActive(false, options: .notifyOthersOnDeactivation)
} catch {
// 忽略错误
}
print("[AzureAsrHelper] 连续识别已停止")
// 通知停止成功
let eventData: [String: Any] = [
"eventType": "success",
"message": "连续识别已停止"
]
self.eventHandler?(eventData)
}
return true
} catch {
// 强制重置状态
isContinuousRecognitionActive = false
print("[AzureAsrHelper] 警告: 停止连续识别失败: \(error.localizedDescription)")
// 通知停止失败,但仍然视为处理完成
let eventData: [String: Any] = [
"eventType": "success",
"message": "连续识别已停止(但有错误)"
]
eventHandler?(eventData)
return false
}
}
/// 释放资源
func dispose() {
// 如果正在进行连续识别,先停止
if isContinuousRecognitionActive {
_ = stopContinuousRecognition()
}
// 恢复音频会话
do {
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
} catch {
// 忽略错误
}
// 释放资源
recognizer = nil
speechConfig = nil
audioConfig = nil
initialized = false
isContinuousRecognitionActive = false
print("[AzureAsrHelper] 资源已释放")
}
/// 检查连续识别是否处于活跃状态
///
/// - Returns: 是否正在进行连续识别
func isContinuousRecognitionActive() -> Bool {
return isContinuousRecognitionActive
}
}