Browse Source

修复语种识别问题

newdev_shunjiawei
fdp 1 year ago
parent
commit
df9493f44e
  1. 180
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  2. 214
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift

180
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift

@ -99,22 +99,28 @@ public class AzureAsrHelper: NSObject {
// 创建语音配置
speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region)
// 针对翻译场景的性能优化
optimizeForTranslation()
if isAutoDetectLanguage {
// 移除过时的属性设置方式
// speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode")
speechConfig?.setPropertyTo(supportedLanguages.joined(separator: ","), byName: "SpeechServiceConnection_AutoDetectSourceLanguages")
// 针对翻译场景优化的语言检测配置
speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode")
// 设置语言检测的置信度阈值,提高准确性
speechConfig?.setPropertyTo("0.7", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold")
// 不在这里设置语言,让SPXAutoDetectSourceLanguageConfiguration处理
os_log("启用语言检测模式,支持语言: %{public}@", log: log, type: .info, supportedLanguages.joined(separator: ","))
} else {
speechConfig?.speechRecognitionLanguage = currentLanguage
os_log("固定语言模式,当前语言: %{public}@", log: log, type: .info, currentLanguage)
}
// 录音文件类
recordfile = RecordFile()
// 【优化】预初始化音频流,避免首次启动延迟
// 预初始化音频组件
preInitializeAudioComponents()
return true
@ -144,7 +150,55 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 检测输入文本的语言,从支持的语言列表中选择最匹配的
*
* @param text 需要检测的文本
* @return 从supportedLanguages中选择的最匹配语言代码
*/
public func detectTextLanguage(_ text: String) -> String? {
// 如果只有一种支持的语言,直接返回
if supportedLanguages.count == 1 {
return supportedLanguages[0]
}
// 获取所有可能的语言及其置信度
let languageResults = LanguageDetector.detectLanguageWithConfidence(from: text)
// 遍历检测结果,找到第一个在supportedLanguages中的语言
for result in languageResults {
let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(result.language)
if supportedLanguages.contains(azureLanguageCode) {
return azureLanguageCode
}
}
// 如果没有找到匹配的语言,返回第一个支持的语言作为默认值
return supportedLanguages.first
}
/**
* 检测文本语言并返回详细信息
*
* @param text 需要检测的文本
* @return 包含语言代码、置信度和语言名称的字典
*/
public func detectTextLanguageWithDetails(_ text: String) -> [String: Any]? {
let results = LanguageDetector.detectLanguageWithConfidence(from: text)
guard let topResult = results.first else {
return nil
}
let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(topResult.language)
let languageName = LanguageDetector.getLanguageName(from: topResult.language)
return [
"languageCode": azureLanguageCode,
"confidence": topResult.confidence,
"languageName": languageName,
"originalCode": topResult.language
]
}
/**
* 开始连续语音识别
*
@ -156,11 +210,13 @@ public class AzureAsrHelper: NSObject {
audioSourceType: AudioSourceType = .microphone
) -> Bool {
guard speechConfig != nil else {
print("语音服务未初始化")
//callback.onError("语音服务未初始化")
return false
}
if _isContinuousRecognitionActive {
print("连续识别已在进行中")
return true
}
@ -170,6 +226,7 @@ public class AzureAsrHelper: NSObject {
do {
guard recognizer != nil else {
if !setupRecognizer() {
print("重置识别器失败")
// callback.onError("重置识别器失败")
return false
}
@ -312,7 +369,7 @@ public class AzureAsrHelper: NSObject {
*/
private func setupRecognizer() -> Bool {
do {
print("设置识别器${_isContinuousRecognitionActive}")
print("设置识别器\(_isContinuousRecognitionActive)")
// 如果正在进行连续识别,先停止
if _isContinuousRecognitionActive {
@ -332,6 +389,7 @@ public class AzureAsrHelper: NSObject {
if isAutoDetectLanguage {
// 使用正确的初始化方法
let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
print("autoDetectConfig: \(autoDetectConfig)")
recognizer = try SPXSpeechRecognizer(
speechConfiguration: speechConfig!,
autoDetectSourceLanguageConfiguration: autoDetectConfig,
@ -425,6 +483,94 @@ public class AzureAsrHelper: NSObject {
print("无法检测语言,使用默认语言: \(currentLanguage)")
return currentLanguage
}
/**
* 针对翻译场景的性能优化配置
*/
private func optimizeForTranslation() {
guard let speechConfig = speechConfig else { return }
// 启用实时转录,减少延迟
speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableAudioLogging")
speechConfig.setPropertyTo("INTERACTIVE", byName: "SpeechServiceConnection_RecoMode")
speechConfig.setPropertyTo("PCM", byName: "Speech_PushStreamFormat")
// 优化语音端点检测,适合翻译场景
speechConfig.setPropertyTo("300", byName: "SpeechServiceConnection_InitialSilenceTimeoutMs")
speechConfig.setPropertyTo("500", byName: "SpeechServiceConnection_EndSilenceTimeoutMs")
speechConfig.setPropertyTo("500", byName: "Speech_SegmentationSilenceTimeoutMs")
// 启用标点符号,提高翻译质量
speechConfig.setPropertyTo("true", byName: "SpeechServiceResponse_RequestProfanityFilterTrueFalse")
// 设置分段策略为时间模式
speechConfig.setPropertyTo("Time", byName: "Speech_SegmentationStrategy")
// 【优化】语言检测配置 - 减少检测延迟,实现快速响应
if isAutoDetectLanguage {
// 降低语言检测置信度阈值,提高检测灵敏度
speechConfig.setPropertyTo("0.3", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold")
// 使用Continuous模式,在每句话结束后重新检测语言
speechConfig.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode")
// 启用快速语言检测
speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionFast")
// 【关键优化】设置语言检测的最小音频长度为100毫秒,实现快速响应
speechConfig.setPropertyTo("100", byName: "SpeechServiceConnection_LanguageDetectionMinAudioLengthMs")
// 【新增】设置语言检测的最大等待时间,避免长时间等待
speechConfig.setPropertyTo("2000", byName: "SpeechServiceConnection_LanguageDetectionMaxWaitTimeMs")
// 强制在每个语音段开始时重新检测语言
speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_ForceLanguageDetectionOnNewSegment")
// 【新增】启用句子级别的语言检测
speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionPerSentence")
// 【新增】设置语言检测的频率 - 每个识别结果后都检测
speechConfig.setPropertyTo("PerResult", byName: "SpeechServiceConnection_LanguageDetectionFrequency")
// 【新增】启用实时语言检测,减少缓冲延迟
speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableRealtimeLanguageDetection")
// 【新增】设置语言检测的采样间隔为200毫秒
speechConfig.setPropertyTo("200", byName: "SpeechServiceConnection_LanguageDetectionSamplingIntervalMs")
}
os_log("翻译场景优化配置已应用,包含语言检测优化", log: log, type: .info)
}
/**
* 动态切换语言(翻译场景中的语言切换)
*
* @param newSourceLanguage 新的源语言
* @param newTargetLanguage 新的目标语言
* @return 切换是否成功
*/
public func switchTranslationLanguages(
newSourceLanguage: String,
newTargetLanguage: String
) -> Bool {
let validatedSourceLang = LanguageDetector.convertToAzureLanguageCode(newSourceLanguage)
let validatedTargetLang = LanguageDetector.convertToAzureLanguageCode(newTargetLanguage)
// 更新支持的语言
self.supportedLanguages = [validatedSourceLang, validatedTargetLang]
self.currentLanguage = validatedSourceLang
// 重新配置语言检测
self.isAutoDetectLanguage = (validatedSourceLang != validatedTargetLang)
// 如果正在识别,需要重新设置识别器
if _isContinuousRecognitionActive {
let wasActive = stopContinuousRecognition()
if wasActive {
return setupRecognizer()
}
}
os_log("语言已切换: %{public}@ -> %{public}@", log: log, type: .info, validatedSourceLang, validatedTargetLang)
return true
}
/**
* 设置事件监听器
*/
@ -442,7 +588,14 @@ public class AzureAsrHelper: NSObject {
let result = event.result
guard !(result.text?.isEmpty ?? true) else { return }
let detectedLanguage = self.getDetectedLanguage(from: result)
// let detectedLanguage = self.getDetectedLanguage(from: result)
// 检测文本语言,避免重复调用
let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN"
// 输出检测到的语言信息
print("检测到的语言: \(detectedLanguage)")
DispatchQueue.main.async {
callback.onRecognizing(result.text ?? "", detectedLanguage)
}
@ -455,10 +608,17 @@ public class AzureAsrHelper: NSObject {
guard result.reason == .recognizedSpeech,
!(result.text?.isEmpty ?? true) else { return }
let detectedLanguage = self.getDetectedLanguage(from: result)
// 检测文本语言,避免重复调用
let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN"
// 输出检测到的语言信息
print("检测到的语言: \(detectedLanguage)")
// 在主线程回调结果
DispatchQueue.main.async {
callback.onResult(result.text ?? "", detectedLanguage)
}
}
// 会话开始事件
recognizer.addSessionStartedEventHandler { (sender, event) in
@ -472,8 +632,8 @@ public class AzureAsrHelper: NSObject {
guard let self = self else { return }
print("会话结束事件:")
// 直接在当前线程调用回调
// callback.onSessionStopped()
// self._isContinuousRecognitionActive = false
callback.onSessionStopped()
self._isContinuousRecognitionActive = false
//self.stopAudioProcessing()
}

214
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/LanguageDetector.swift

@ -0,0 +1,214 @@
import Foundation
import NaturalLanguage
/**
* 语言检测工具类
* 使用iOS自带的自然语言处理框架检测文本语种
*/
public class LanguageDetector {
/**
* 检测文本的主要语言
*
* @param text 需要检测的文本
* @return 语言代码(如:"zh-Hans", "en", "ja"等),如果检测失败返回nil
*/
public static func detectLanguage(from text: String) -> String? {
// 检查输入文本是否为空
guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
return nil
}
// 使用NLLanguageRecognizer进行语言检测
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
// 获取最可能的语言
guard let dominantLanguage = recognizer.dominantLanguage else {
return nil
}
return dominantLanguage.rawValue
}
/**
* 检测文本的语言并返回置信度
*
* @param text 需要检测的文本
* @return 语言代码和置信度的字典,按置信度降序排列
*/
public static func detectLanguageWithConfidence(from text: String) -> [(language: String, confidence: Double)] {
// 检查输入文本是否为空
guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
return []
}
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
// 获取语言假设及其置信度
let languageHypotheses = recognizer.languageHypotheses(withMaximum: 5)
// 转换为元组数组并按置信度排序
return languageHypotheses.map { (language: $0.key.rawValue, confidence: $0.value) }
.sorted { $0.confidence > $1.confidence }
}
/**
* 将语言代码转换为人类可读的语言名称
*
* @param languageCode 语言代码(如:"zh-Hans", "en"等)
* @return 语言的本地化名称
*/
public static func getLanguageName(from languageCode: String) -> String {
let locale = Locale.current
return locale.localizedString(forLanguageCode: languageCode) ?? languageCode
}
/**
* 检测文本语言并返回Azure语音服务支持的语言代码格式
*
* @param text 需要检测的文本
* @return Azure支持的语言代码格式(如:"zh-CN", "en-US"等)
*/
public static func detectLanguageForAzure(from text: String) -> String? {
guard let detectedLanguage = detectLanguage(from: text) else {
return nil
}
// 将iOS语言代码转换为Azure语音服务支持的格式
return convertToAzureLanguageCode(detectedLanguage)
}
/**
* 将iOS语言代码转换为Azure语音服务支持的语言代码
*
* @param iosLanguageCode iOS检测到的语言代码
* @return Azure支持的语言代码
*/
public static func convertToAzureLanguageCode(_ iosLanguageCode: String) -> String {
let languageMapping: [String: String] = [
// 中文
"zh-Hans": "zh-CN", // 简体中文
"zh-Hant": "zh-TW", // 繁体中文
"zh-HK": "zh-HK", // 香港中文
// 英语系
"en": "en-US", // 英语(美国)
"en-GB": "en-US", // 英语(英国)
"en-AU": "en-US", // 英语(澳大利亚)
"en-CA": "en-US", // 英语(加拿大)
"en-IN": "en-US", // 英语(印度)
"en-IE": "en-US", // 英语(爱尔兰)
"en-NZ": "en-US", // 英语(新西兰)
"en-ZA": "en-US", // 英语(南非)
// 亚洲语言
"ja": "ja-JP", // 日语
"ko": "ko-KR", // 韩语
"hi": "hi-IN", // 印地语
"th": "th-TH", // 泰语
"vi": "vi-VN", // 越南语
"id": "id-ID", // 印尼语
"ms": "ms-MY", // 马来语
"tl": "fil-PH", // 菲律宾语
"my": "my-MM", // 缅甸语
"km": "km-KH", // 高棉语(柬埔寨)
"lo": "lo-LA", // 老挝语
"si": "si-LK", // 僧伽罗语(斯里兰卡)
"ta": "ta-IN", // 泰米尔语
"te": "te-IN", // 泰卢固语
"bn": "bn-IN", // 孟加拉语
"gu": "gu-IN", // 古吉拉特语
"kn": "kn-IN", // 卡纳达语
"ml": "ml-IN", // 马拉雅拉姆语
"mr": "mr-IN", // 马拉地语
"pa": "pa-IN", // 旁遮普语
"ur": "ur-PK", // 乌尔都语
// 欧洲语言
"fr": "fr-FR", // 法语(法国)
"fr-CA": "fr-CA", // 法语(加拿大)
"fr-BE": "fr-BE", // 法语(比利时)
"fr-CH": "fr-CH", // 法语(瑞士)
"de": "de-DE", // 德语(德国)
"de-AT": "de-AT", // 德语(奥地利)
"de-CH": "de-CH", // 德语(瑞士)
"es": "es-ES", // 西班牙语(西班牙)
"es-MX": "es-MX", // 西班牙语(墨西哥)
"es-AR": "es-AR", // 西班牙语(阿根廷)
"es-CO": "es-CO", // 西班牙语(哥伦比亚)
"es-CL": "es-CL", // 西班牙语(智利)
"es-PE": "es-PE", // 西班牙语(秘鲁)
"es-VE": "es-VE", // 西班牙语(委内瑞拉)
"it": "it-IT", // 意大利语
"pt": "pt-BR", // 葡萄牙语(巴西)
"pt-PT": "pt-PT", // 葡萄牙语(葡萄牙)
"ru": "ru-RU", // 俄语
"nl": "nl-NL", // 荷兰语
"nl-BE": "nl-BE", // 荷兰语(比利时)
"sv": "sv-SE", // 瑞典语
"da": "da-DK", // 丹麦语
"no": "nb-NO", // 挪威语
"fi": "fi-FI", // 芬兰语
"is": "is-IS", // 冰岛语
"pl": "pl-PL", // 波兰语
"cs": "cs-CZ", // 捷克语
"sk": "sk-SK", // 斯洛伐克语
"hu": "hu-HU", // 匈牙利语
"ro": "ro-RO", // 罗马尼亚语
"bg": "bg-BG", // 保加利亚语
"hr": "hr-HR", // 克罗地亚语
"sr": "sr-RS", // 塞尔维亚语
"sl": "sl-SI", // 斯洛文尼亚语
"et": "et-EE", // 爱沙尼亚语
"lv": "lv-LV", // 拉脱维亚语
"lt": "lt-LT", // 立陶宛语
"uk": "uk-UA", // 乌克兰语
"be": "be-BY", // 白俄罗斯语
"mk": "mk-MK", // 马其顿语
"sq": "sq-AL", // 阿尔巴尼亚语
"mt": "mt-MT", // 马耳他语
"ga": "ga-IE", // 爱尔兰语
"cy": "cy-GB", // 威尔士语
"eu": "eu-ES", // 巴斯克语
"ca": "ca-ES", // 加泰罗尼亚语
"gl": "gl-ES", // 加利西亚语
// 中东和非洲语言
"ar": "ar-SA", // 阿拉伯语(沙特阿拉伯)
"ar-EG": "ar-EG", // 阿拉伯语(埃及)
"ar-AE": "ar-AE", // 阿拉伯语(阿联酋)
"ar-BH": "ar-BH", // 阿拉伯语(巴林)
"ar-DZ": "ar-DZ", // 阿拉伯语(阿尔及利亚)
"ar-IQ": "ar-IQ", // 阿拉伯语(伊拉克)
"ar-JO": "ar-JO", // 阿拉伯语(约旦)
"ar-KW": "ar-KW", // 阿拉伯语(科威特)
"ar-LB": "ar-LB", // 阿拉伯语(黎巴嫩)
"ar-LY": "ar-LY", // 阿拉伯语(利比亚)
"ar-MA": "ar-MA", // 阿拉伯语(摩洛哥)
"ar-OM": "ar-OM", // 阿拉伯语(阿曼)
"ar-QA": "ar-QA", // 阿拉伯语(卡塔尔)
"ar-SY": "ar-SY", // 阿拉伯语(叙利亚)
"ar-TN": "ar-TN", // 阿拉伯语(突尼斯)
"ar-YE": "ar-YE", // 阿拉伯语(也门)
"he": "he-IL", // 希伯来语
"fa": "fa-IR", // 波斯语(伊朗)
"tr": "tr-TR", // 土耳其语
"az": "az-AZ", // 阿塞拜疆语
"ka": "ka-GE", // 格鲁吉亚语
"hy": "hy-AM", // 亚美尼亚语
"sw": "sw-KE", // 斯瓦希里语
"am": "am-ET", // 阿姆哈拉语(埃塞俄比亚)
"zu": "zu-ZA", // 祖鲁语(南非)
"af": "af-ZA", // 南非荷兰语
// 其他语言
"el": "el-GR", // 希腊语
"eo": "eo", // 世界语
"la": "la", // 拉丁语
]
return languageMapping[iosLanguageCode] ?? iosLanguageCode
}
}
Loading…
Cancel
Save