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