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
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
|
|
}
|
|
}
|
|
}
|