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.

414 lines
15 KiB

import Foundation
import MicrosoftCognitiveServicesSpeech
import AVFoundation
/// Azure ASR工具类,负责实现语音识别服务接口
@available(iOS 13.0, *)
class AzureAsrHelper: NSObject {
// MARK: - 属性
/// 事件处理回调
private var eventHandler: (String, [String: Any]) -> Void
/// 语音配置信息
private var speechSubscriptionKey: String = ""
private var serviceRegion: String = ""
/// 语音识别相关
private var speechConfig: SPXSpeechConfiguration?
private var recognizer: SPXSpeechRecognizer?
private var audioConfig: SPXAudioConfiguration?
/// 状态标志
private var isInitialized = false
private var _isContinuousRecognitionActive = false
/// 当前语言和支持的语言
private var currentLanguage = "zh-CN"
private var supportedLanguages: [String] = ["zh-CN", "en-US"]
private var isAutoDetectLanguage = false
/// 音频会话管理器
private let audioSessionManager = AudioSessionManager.shared
// MARK: - 初始化
init(eventHandler: @escaping (String, [String: Any]) -> Void) {
self.eventHandler = eventHandler
super.init()
}
deinit {
dispose()
}
// MARK: - ASR Service 接口实现
/// 初始化语音识别服务
/// - Parameters:
/// - speechSubscriptionKey: Azure 语音服务订阅密钥
/// - serviceRegion: Azure 服务区域 (如 eastasia)
/// - supportedLanguages: 支持的语言代码数组 (可选)
/// - Returns: 初始化是否成功
func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String]? = nil) -> Bool {
print("[AzureAsrHelper] 初始化 Azure 语音服务")
// 检查配置是否为空
if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
eventHandler("error", ["message": "Azure 配置信息不完整"])
return false
}
// 释放之前的资源
dispose()
// 记录配置信息
self.speechSubscriptionKey = speechSubscriptionKey
self.serviceRegion = serviceRegion
// 设置语言
if let languages = supportedLanguages, !languages.isEmpty {
self.supportedLanguages = languages
}
// 根据支持的语言数量决定是否启用自动语言检测
isAutoDetectLanguage = self.supportedLanguages.count >= 2
// 如果只有一种语言,设置为当前语言
if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty {
currentLanguage = self.supportedLanguages[0]
}
// 创建识别器和设置回调
if !createRecognizerAndSetupCallbacks() {
return false
}
print("[AzureAsrHelper] Azure 语音服务初始化成功")
isInitialized = true
return true
}
/// 创建识别器并设置回调
private func createRecognizerAndSetupCallbacks() -> Bool {
// 释放之前的 recognizer
recognizer = nil
audioConfig = nil
do {
// 创建语音配置
speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion)
// 设置音频输入参数
try setupAudioSession()
// 直接使用麦克风音频配置
audioConfig = try SPXAudioConfiguration()
// 设置语言配置
if isAutoDetectLanguage {
// 设置自动语言检测
speechConfig?.setPropertyTo("Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode)
// 创建自动语言检测配置
let autoDetectSourceLanguageConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
// 创建识别器
recognizer = try SPXSpeechRecognizer(
speechConfiguration: speechConfig!,
autoDetectSourceLanguageConfiguration: autoDetectSourceLanguageConfig,
audioConfiguration: audioConfig!
)
} else {
// 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage
// 创建识别器
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
}
// 设置所有回调
setupAllCallbacks()
return true
} catch {
print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)")
eventHandler("error", ["message": "创建识别器失败: \(error.localizedDescription)"])
return false
}
}
/// 设置音频会话
private func setupAudioSession() throws {
print("[AzureAsrHelper] 开始配置音频会话...")
audioSessionManager.configureForVoiceInteraction()
}
/// 设置所有回调
private func setupAllCallbacks() {
guard let recognizer = recognizer else { return }
// 最终识别结果
recognizer.addRecognizedEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == SPXResultReason.recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
self.eventHandler("result", [
"text": event.result.text ?? "",
"detectedLanguage": detectedLanguage
])
}
}
// 识别中事件
recognizer.addRecognizingEventHandler { [weak self] _, event in
guard let self = self else { return }
if event.result.reason == SPXResultReason.recognizingSpeech {
let detectedLanguage = self.getDetectedLanguage(from: event.result)
// print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
self.eventHandler("recognizing", [
"text": event.result.text ?? "",
"detectedLanguage": detectedLanguage
])
}
}
// 会话事件
recognizer.addSessionStartedEventHandler { [weak self] _, _ in
guard let self = self else { return }
print("[AzureAsrHelper] 识别会话已开始")
self._isContinuousRecognitionActive = true
self.eventHandler("sessionStarted", [:])
}
recognizer.addSessionStoppedEventHandler { [weak self] _, _ in
guard let self = self else { return }
print("[AzureAsrHelper] 识别会话已结束")
self._isContinuousRecognitionActive = false
self.eventHandler("sessionStopped", [:])
}
// 取消事件
recognizer.addCanceledEventHandler { [weak self] _, event in
guard let self = self else { return }
let reason = event.reason.rawValue
let errorDetails = event.errorDetails ?? "未知错误"
print("[AzureAsrHelper] 识别取消: \(errorDetails)")
self.eventHandler("canceled", [
"reason": reason,
"errorDetails": errorDetails
])
self._isContinuousRecognitionActive = false
}
}
/// 执行一次性语音识别
/// - Returns: 是否成功启动识别
func recognizeOnce() -> Bool {
if !isInitialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
eventHandler("error", ["message": "语音服务未初始化"])
return false
}
// 如果正在连续识别,先停止
if _isContinuousRecognitionActive {
stopContinuousRecognition()
}
// 确保识别器已创建
if recognizer == nil && !createRecognizerAndSetupCallbacks() {
return false
}
do {
// 通知会话开始
eventHandler("sessionStarted", [:])
// 执行识别
try recognizer?.recognizeOnceAsync { [weak self] result in
guard let self = self else { return }
if result.reason == SPXResultReason.recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: result)
self.eventHandler("result", [
"text": result.text ?? "",
"detectedLanguage": detectedLanguage
])
} else if result.reason == SPXResultReason.noMatch {
print("[AzureAsrHelper] 无匹配结果")
self.eventHandler("noMatch", [:])
} else if result.reason == SPXResultReason.canceled {
do {
let details = try SPXCancellationDetails(fromCanceledRecognitionResult: result)
let errorDetails = details.errorDetails ?? "未知错误"
self.eventHandler("error", ["message": "识别取消: \(errorDetails)"])
} catch {
print("[AzureAsrHelper] 错误: 获取取消详情失败: \(error.localizedDescription)")
self.eventHandler("error", ["message": "识别取消,无法获取详细原因"])
}
}
}
return true
} catch {
print("[AzureAsrHelper] 错误: 识别异常: \(error.localizedDescription)")
eventHandler("error", ["message": "识别异常: \(error.localizedDescription)"])
return false
}
}
/// 开始连续语音识别
/// - Returns: 是否成功启动识别
func startContinuousRecognition() -> Bool {
if !isInitialized {
print("[AzureAsrHelper] 错误: 语音服务未初始化")
eventHandler("error", ["message": "语音服务未初始化"])
return false
}
// 检查是否已经在识别
if _isContinuousRecognitionActive {
print("[AzureAsrHelper] 已经在进行连续识别中,忽略请求")
return true
}
// 确保识别器已创建
if recognizer == nil {
print("[AzureAsrHelper] 尝试重新创建识别器...")
if !createRecognizerAndSetupCallbacks() {
eventHandler("error", ["message": "无法创建语音识别器"])
return false
}
}
// 尝试启动连续识别
do {
// 启动连续识别
try recognizer?.startContinuousRecognition()
_isContinuousRecognitionActive = true
print("[AzureAsrHelper] 连续识别已启动")
return true
} catch {
_isContinuousRecognitionActive = false
print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)")
// 发送错误通知
eventHandler("error", ["message": "开始连续识别失败: \(error.localizedDescription)"])
return false
}
}
/// 停止连续语音识别
/// - Returns: 是否成功停止识别
func stopContinuousRecognition() -> Bool {
if !_isContinuousRecognitionActive {
print("[AzureAsrHelper] 未进行连续识别,忽略停止请求")
return true
}
print("[AzureAsrHelper] 停止连续语音识别")
// 防止空指针异常
guard let recognizer = recognizer else {
print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃")
_isContinuousRecognitionActive = false
eventHandler("sessionStopped", [:])
return true
}
// 先标记为非活跃状态,防止重复调用
_isContinuousRecognitionActive = false
// do {
// try recognizer.stopContinuousRecognition()
// } catch {
// print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)")
// }
// // 异步执行停止操作,避免阻塞主线程
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
do {
// 在后台线程执行停止操作
try recognizer.stopContinuousRecognition()
// 主线程回调通知结果
DispatchQueue.main.async {
print("[AzureAsrHelper] 连续识别已停止")
self.eventHandler("sessionStopped", [:])
}
} catch {
// 主线程回调通知错误
DispatchQueue.main.async {
print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)")
// 停止失败,恢复状态
self._isContinuousRecognitionActive = true
self.eventHandler("error", ["error": "停止识别失败: \(error.localizedDescription)"])
}
}
}
// 立即返回,不阻塞调用方
return true
}
/// 检查连续识别是否活跃
/// - Returns: 连续识别是否处于活跃状态
func isContinuousRecognitionActive() -> Bool {
return _isContinuousRecognitionActive
}
/// 释放资源
func dispose() {
// 停止所有识别操作
if _isContinuousRecognitionActive {
stopContinuousRecognition()
}
// 释放识别器
recognizer = nil
audioConfig = nil
speechConfig = nil
// 重置状态
isInitialized = false
_isContinuousRecognitionActive = false
print("[AzureAsrHelper] 资源已释放")
}
/// 从结果中获取检测到的语言
private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String {
if isAutoDetectLanguage {
do {
let langResult = try SPXAutoDetectSourceLanguageResult(result)
return langResult.language ?? currentLanguage
} catch {
print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)")
return currentLanguage
}
} else {
return currentLanguage
}
}
}