|
|
@ -19,6 +19,8 @@ import ble_service |
|
|
private let tag = "AzureSpeechPlugin" |
|
|
private let tag = "AzureSpeechPlugin" |
|
|
// 日志对象 |
|
|
// 日志对象 |
|
|
internal let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") |
|
|
internal let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") |
|
|
|
|
|
/// 通话翻译专用日志,Xcode Console 过滤: subsystem=com.azure.speech category=CallTranslation |
|
|
|
|
|
internal let ctLog = OSLog(subsystem: "com.azure.speech", category: "CallTranslation") |
|
|
|
|
|
|
|
|
// MARK: - 防抖相关(iOS 原生层) |
|
|
// MARK: - 防抖相关(iOS 原生层) |
|
|
// 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发 |
|
|
// 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发 |
|
|
@ -91,7 +93,9 @@ import ble_service |
|
|
private var astChannel: FlutterMethodChannel? |
|
|
private var astChannel: FlutterMethodChannel? |
|
|
private var astEventChannel: FlutterEventChannel? |
|
|
private var astEventChannel: FlutterEventChannel? |
|
|
internal var astEventSink: FlutterEventSink? |
|
|
internal var astEventSink: FlutterEventSink? |
|
|
private let azureAstHelper = IntegratedSpeechTranslationService() |
|
|
// 双向 AST 服务:A(己方)和 B(对方),与 Android 对齐 |
|
|
|
|
|
private let azureAstHelperA = IntegratedSpeechTranslationService() |
|
|
|
|
|
private let azureAstHelperB = IntegratedSpeechTranslationService() |
|
|
|
|
|
|
|
|
// 端到端翻译 Helpers(豆包 / 阿里百炼) |
|
|
// 端到端翻译 Helpers(豆包 / 阿里百炼) |
|
|
private var doubaoAstHelperA = DoubaoE2ETranslateHelper() |
|
|
private var doubaoAstHelperA = DoubaoE2ETranslateHelper() |
|
|
@ -110,6 +114,9 @@ import ble_service |
|
|
private var audioDataEventChannel: FlutterEventChannel? |
|
|
private var audioDataEventChannel: FlutterEventChannel? |
|
|
internal var audioDataEventSink: FlutterEventSink? |
|
|
internal var audioDataEventSink: FlutterEventSink? |
|
|
|
|
|
|
|
|
|
|
|
// 通话翻译音频统计(日志用) |
|
|
|
|
|
private var bleAudioRecvCount: Int64 = 0 |
|
|
|
|
|
|
|
|
// 录音相关 |
|
|
// 录音相关 |
|
|
public var recordfile: RecordFile? |
|
|
public var recordfile: RecordFile? |
|
|
private var isRecord = false |
|
|
private var isRecord = false |
|
|
@ -414,15 +421,22 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
AzureAsrHelper.AudioSourceType.external : |
|
|
AzureAsrHelper.AudioSourceType.external : |
|
|
AzureAsrHelper.AudioSourceType.microphone |
|
|
AzureAsrHelper.AudioSourceType.microphone |
|
|
|
|
|
|
|
|
print("startContinuousRecognition: \(audioSourceType)") |
|
|
os_log("startContinuousRecognition: mode=%@, audioSourceType=%@, isExternal=%d, astProvider=%@", |
|
|
|
|
|
log: ctLog, type: .info, mode, String(describing: audioSourceType), isExternalActive ? 1 : 0, currentAstProvider) |
|
|
|
|
|
|
|
|
// 启动连续识别 |
|
|
// 通话翻译模式(external):AST 端到端服务已处理识别+翻译+TTS,无需启动 ASR |
|
|
let success = azureAsrHelper.startContinuousRecognition( |
|
|
if isExternalActive { |
|
|
mod: mode, |
|
|
os_log("startContinuousRecognition: 通话模式,AST端到端服务已激活(provider=%@),跳过ASR", log: ctLog, type: .info, currentAstProvider) |
|
|
audioSourceType: audioSourceType, |
|
|
result(true) |
|
|
isRemoveFirstPunctuation: removeFirstPunctuation |
|
|
} else { |
|
|
) |
|
|
let success = azureAsrHelper.startContinuousRecognition( |
|
|
result(success) |
|
|
mod: mode, |
|
|
|
|
|
audioSourceType: audioSourceType, |
|
|
|
|
|
isRemoveFirstPunctuation: removeFirstPunctuation |
|
|
|
|
|
) |
|
|
|
|
|
os_log("startContinuousRecognition 结果=%d", log: ctLog, type: .info, success ? 1 : 0) |
|
|
|
|
|
result(true) |
|
|
|
|
|
} |
|
|
case "recognizeCallback": |
|
|
case "recognizeCallback": |
|
|
// 确保事件通道已准备好 |
|
|
// 确保事件通道已准备好 |
|
|
guard asrEventSink != nil else { |
|
|
guard asrEventSink != nil else { |
|
|
@ -452,10 +466,16 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
print("stopContinuousRecognition") |
|
|
// ASR 没在运行时(通话模式下被跳过了)直接返回 |
|
|
let success = azureAsrHelper.stopContinuousRecognition() |
|
|
if azureAsrHelper.audioStream?.isContinuousRecognitionActive != true { |
|
|
currentAsrCallback = nil |
|
|
os_log("stopContinuousRecognition: ASR未运行,跳过", log: ctLog, type: .info) |
|
|
result(success) |
|
|
result(true) |
|
|
|
|
|
} else { |
|
|
|
|
|
os_log("stopContinuousRecognition: 停止ASR", log: ctLog, type: .info) |
|
|
|
|
|
let success = azureAsrHelper.stopContinuousRecognition() |
|
|
|
|
|
currentAsrCallback = nil |
|
|
|
|
|
result(success) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
case "isContinuousRecognitionActive": |
|
|
case "isContinuousRecognitionActive": |
|
|
result(azureAsrHelper.isContinuousRecognitionActive()) |
|
|
result(azureAsrHelper.isContinuousRecognitionActive()) |
|
|
@ -757,50 +777,64 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
// } |
|
|
// } |
|
|
|
|
|
|
|
|
case "startContinuousTranslation": |
|
|
case "startContinuousTranslation": |
|
|
|
|
|
os_log("[AST-START] provider=%{public}@", log: ctLog, type: .info, currentAstProvider) |
|
|
if currentAstProvider == "volcano" { |
|
|
if currentAstProvider == "volcano" { |
|
|
_ = doubaoAstHelperA.startContinuousTranslation() |
|
|
let rA = doubaoAstHelperA.startContinuousTranslation() |
|
|
_ = doubaoAstHelperB.startContinuousTranslation() |
|
|
let rB = doubaoAstHelperB.startContinuousTranslation() |
|
|
|
|
|
os_log("[AST-START] volcano: A=%d, B=%d", log: ctLog, type: .info, rA ? 1 : 0, rB ? 1 : 0) |
|
|
result(true) |
|
|
result(true) |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
_ = bailianAstHelperA.startContinuousConversation() |
|
|
let rA = bailianAstHelperA.startContinuousConversation() |
|
|
_ = bailianAstHelperB.startContinuousConversation() |
|
|
let rB = bailianAstHelperB.startContinuousConversation() |
|
|
|
|
|
os_log("[AST-START] alibaba: A=%d, B=%d", log: ctLog, type: .info, rA ? 1 : 0, rB ? 1 : 0) |
|
|
result(true) |
|
|
result(true) |
|
|
} else { |
|
|
} else { |
|
|
do { |
|
|
do { |
|
|
os_log("开启翻译", log: log, type: .info) |
|
|
os_log("[AST-START] azure: starting A+B", log: ctLog, type: .info) |
|
|
try azureAstHelper.startContinuousTranslation() |
|
|
try azureAstHelperA.startContinuousTranslation() |
|
|
|
|
|
try azureAstHelperB.startContinuousTranslation() |
|
|
|
|
|
os_log("[AST-START] azure: A+B started successfully", log: ctLog, type: .info) |
|
|
result(true) |
|
|
result(true) |
|
|
} catch { |
|
|
} catch { |
|
|
|
|
|
os_log("[AST-START] azure: FAILED: %{public}@", log: ctLog, type: .error, error.localizedDescription) |
|
|
result(FlutterError(code: "START_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
result(FlutterError(code: "START_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
case "stopContinuousTranslation": |
|
|
case "stopContinuousTranslation": |
|
|
|
|
|
os_log("[AST-STOP] provider=%{public}@", log: ctLog, type: .info, currentAstProvider) |
|
|
if currentAstProvider == "volcano" { |
|
|
if currentAstProvider == "volcano" { |
|
|
_ = doubaoAstHelperA.stopContinuousTranslation() |
|
|
let rA = doubaoAstHelperA.stopContinuousTranslation() |
|
|
_ = doubaoAstHelperB.stopContinuousTranslation() |
|
|
let rB = doubaoAstHelperB.stopContinuousTranslation() |
|
|
|
|
|
os_log("[AST-STOP] volcano: A=%d, B=%d", log: ctLog, type: .info, rA ? 1 : 0, rB ? 1 : 0) |
|
|
result(true) |
|
|
result(true) |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
_ = bailianAstHelperA.stopContinuousConversation() |
|
|
let rA = bailianAstHelperA.stopContinuousConversation() |
|
|
_ = bailianAstHelperB.stopContinuousConversation() |
|
|
let rB = bailianAstHelperB.stopContinuousConversation() |
|
|
|
|
|
os_log("[AST-STOP] alibaba: A=%d, B=%d", log: ctLog, type: .info, rA ? 1 : 0, rB ? 1 : 0) |
|
|
result(true) |
|
|
result(true) |
|
|
} else { |
|
|
} else { |
|
|
do { |
|
|
do { |
|
|
os_log("停止翻译", log: log, type: .info) |
|
|
os_log("[AST-STOP] azure: stopping A+B", log: ctLog, type: .info) |
|
|
try azureAstHelper.stopContinuousTranslation() |
|
|
try azureAstHelperA.stopContinuousTranslation() |
|
|
|
|
|
try azureAstHelperB.stopContinuousTranslation() |
|
|
|
|
|
os_log("[AST-STOP] azure: A+B stopped successfully", log: ctLog, type: .info) |
|
|
result(true) |
|
|
result(true) |
|
|
} catch { |
|
|
} catch { |
|
|
|
|
|
os_log("[AST-STOP] azure: FAILED: %{public}@", log: ctLog, type: .error, error.localizedDescription) |
|
|
result(FlutterError(code: "STOP_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
result(FlutterError(code: "STOP_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
case "dispose": |
|
|
case "dispose": |
|
|
os_log("释放AST资源", log: log, type: .info) |
|
|
os_log("[AST-DISPOSE] Releasing all AST resources, provider=%{public}@", log: ctLog, type: .info, currentAstProvider) |
|
|
azureAstHelper.dispose() |
|
|
azureAstHelperA.dispose() |
|
|
|
|
|
azureAstHelperB.dispose() |
|
|
doubaoAstHelperA.dispose() |
|
|
doubaoAstHelperA.dispose() |
|
|
doubaoAstHelperB.dispose() |
|
|
doubaoAstHelperB.dispose() |
|
|
bailianAstHelperA.dispose() |
|
|
bailianAstHelperA.dispose() |
|
|
bailianAstHelperB.dispose() |
|
|
bailianAstHelperB.dispose() |
|
|
|
|
|
os_log("[AST-DISPOSE] All AST resources released", log: ctLog, type: .info) |
|
|
result(true) |
|
|
result(true) |
|
|
|
|
|
|
|
|
case "recognizeCallback": |
|
|
case "recognizeCallback": |
|
|
@ -849,9 +883,25 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
let alibabaAppId = (args["alibabaAppId"] as? String) ?? "" |
|
|
let alibabaAppId = (args["alibabaAppId"] as? String) ?? "" |
|
|
let alibabaAppURL = (args["alibabaAppURL"] as? String) ?? "" |
|
|
let alibabaAppURL = (args["alibabaAppURL"] as? String) ?? "" |
|
|
|
|
|
|
|
|
os_log("initialize provider=%@ lang0=%@ lang1=%@ trans0=%@ trans1=%@", log: log, type: .info, provider, lang0, lang1, translationLang0, translationLang1) |
|
|
os_log("[AST-INIT] provider=%{public}@ lang0=%{public}@ lang1=%{public}@ trans0=%{public}@ trans1=%{public}@ tts0=%{public}@ tts1=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, provider, lang0, lang1, translationLang0, translationLang1, ttsLang0, ttsLang1) |
|
|
|
|
|
os_log("[AST-INIT] subscriptionKey=%{public}@ region=%{public}@ wsUrl=%{public}@ appKey=%{public}@ accessKey=%{public}@ resourceId=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, |
|
|
|
|
|
String(subscriptionKeyI.prefix(4)) + "***", |
|
|
|
|
|
regionI, |
|
|
|
|
|
wsUrl, |
|
|
|
|
|
String(appKey.prefix(4)) + "***", |
|
|
|
|
|
String(accessKeyD.prefix(4)) + "***", |
|
|
|
|
|
resourceId) |
|
|
|
|
|
os_log("[AST-INIT] alibabaAppKey=%{public}@ alibabaAppId=%{public}@ alibabaAppURL=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, |
|
|
|
|
|
alibabaAppKey.isEmpty ? "(empty)" : String(alibabaAppKey.prefix(4)) + "***", |
|
|
|
|
|
alibabaAppId, |
|
|
|
|
|
alibabaAppURL) |
|
|
|
|
|
|
|
|
if currentAstProvider == "azure" { |
|
|
if currentAstProvider == "azure" { |
|
|
|
|
|
os_log("[AST-INIT] Azure: configuring A(src=%{public}@ tgt=%{public}@) B(src=%{public}@ tgt=%{public}@)", |
|
|
|
|
|
log: ctLog, type: .info, lang0, lang1, lang1, lang0) |
|
|
let svcA = IntegratedSpeechTranslationService.ServiceConfiguration( |
|
|
let svcA = IntegratedSpeechTranslationService.ServiceConfiguration( |
|
|
sourceLanguage: lang0, |
|
|
sourceLanguage: lang0, |
|
|
targetLanguage: lang1, |
|
|
targetLanguage: lang1, |
|
|
@ -866,18 +916,26 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
translationSourceLanguage: translationLang1, |
|
|
translationSourceLanguage: translationLang1, |
|
|
translationTargetLanguage: translationLang0 |
|
|
translationTargetLanguage: translationLang0 |
|
|
) |
|
|
) |
|
|
let callbackA = AstEventCallback(plugin: self) |
|
|
let callbackA = AstEventCallback(plugin: self, serviceId: "A") |
|
|
let callbackB = AstEventCallback(plugin: self) |
|
|
let callbackB = AstEventCallback(plugin: self, serviceId: "B") |
|
|
astEventCallback = callbackA |
|
|
astEventCallback = callbackA |
|
|
|
|
|
|
|
|
let azureConfigI = AzureConfiguration(subscriptionKey: subscriptionKeyI, region: regionI) |
|
|
let azureConfigI = AzureConfiguration(subscriptionKey: subscriptionKeyI, region: regionI) |
|
|
let translationConfigI = TranslationConfiguration(region: azureTranslationRegion, subscriptionKey: azureTranslationKey, location: azureTranslationRegion) |
|
|
let translationConfigI = TranslationConfiguration(region: azureTranslationRegion, subscriptionKey: azureTranslationKey, location: azureTranslationRegion) |
|
|
|
|
|
|
|
|
Task { |
|
|
Task { |
|
|
await azureAstHelper.initialize(azureConfig: azureConfigI, translationConfig: translationConfigI, serviceConfig: svcA, callback: callbackA) |
|
|
os_log("[AST-INIT] Azure: initializing A service...", log: self.ctLog, type: .info) |
|
|
|
|
|
await azureAstHelperA.initialize(azureConfig: azureConfigI, translationConfig: translationConfigI, serviceConfig: svcA, callback: callbackA) |
|
|
|
|
|
os_log("[AST-INIT] Azure: A initialized, waiting 1.2s before B...", log: self.ctLog, type: .info) |
|
|
|
|
|
try? await Task.sleep(nanoseconds: 1_200_000_000) |
|
|
|
|
|
os_log("[AST-INIT] Azure: initializing B service...", log: self.ctLog, type: .info) |
|
|
|
|
|
await azureAstHelperB.initialize(azureConfig: azureConfigI, translationConfig: translationConfigI, serviceConfig: svcB, callback: callbackB) |
|
|
|
|
|
os_log("[AST-INIT] Azure: A+B both initialized", log: self.ctLog, type: .info) |
|
|
} |
|
|
} |
|
|
} else if currentAstProvider == "volcano" { |
|
|
} else if currentAstProvider == "volcano" { |
|
|
// 初始化豆包 AST(双路) |
|
|
// 初始化豆包 AST(双路) |
|
|
|
|
|
os_log("[AST-INIT] Volcano: configuring A(%{public}@->%{public}@) B(%{public}@->%{public}@)", |
|
|
|
|
|
log: ctLog, type: .info, translationLang0, translationLang1, translationLang1, translationLang0) |
|
|
let cfg = DoubaoE2ETranslateHelper.Config(wsUrl: wsUrl, appKey: appKey, accessKey: accessKeyD, resourceId: resourceId, sourceLanguage: translationLang0, targetLanguage: translationLang1, clientWaitMs: 60_000) |
|
|
let cfg = DoubaoE2ETranslateHelper.Config(wsUrl: wsUrl, appKey: appKey, accessKey: accessKeyD, resourceId: resourceId, sourceLanguage: translationLang0, targetLanguage: translationLang1, clientWaitMs: 60_000) |
|
|
astWsConfig = cfg |
|
|
astWsConfig = cfg |
|
|
let cbA = DoubaoCallbackProxy(plugin: self, serviceId: "A", direction: "\(translationLang0)->\(translationLang1)", targetLanguage: translationLang1) |
|
|
let cbA = DoubaoCallbackProxy(plugin: self, serviceId: "A", direction: "\(translationLang0)->\(translationLang1)", targetLanguage: translationLang1) |
|
|
@ -892,12 +950,18 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
cfgB.targetLanguage = translationLang0 |
|
|
cfgB.targetLanguage = translationLang0 |
|
|
|
|
|
|
|
|
Task { |
|
|
Task { |
|
|
_ = await doubaoAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
os_log("[AST-INIT] Volcano: initializing A service...", log: self.ctLog, type: .info) |
|
|
|
|
|
let rA = await doubaoAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
|
|
|
os_log("[AST-INIT] Volcano: A service result=%d, sleeping 500ms before B...", log: self.ctLog, type: .info, rA ? 1 : 0) |
|
|
try? await Task.sleep(nanoseconds: 500_000_000) |
|
|
try? await Task.sleep(nanoseconds: 500_000_000) |
|
|
_ = await doubaoAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
os_log("[AST-INIT] Volcano: initializing B service...", log: self.ctLog, type: .info) |
|
|
|
|
|
let rB = await doubaoAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
|
|
|
os_log("[AST-INIT] Volcano: B service result=%d. Both services ready.", log: self.ctLog, type: .info, rB ? 1 : 0) |
|
|
} |
|
|
} |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
// 初始化阿里百炼(双路) |
|
|
// 初始化阿里百炼(双路) |
|
|
|
|
|
os_log("[AST-INIT] Alibaba: configuring A(%{public}@->%{public}@) B(%{public}@->%{public}@)", |
|
|
|
|
|
log: ctLog, type: .info, translationLang0, translationLang1, translationLang1, translationLang0) |
|
|
var cfgA = AliyunBailianE2EHelper.Config() |
|
|
var cfgA = AliyunBailianE2EHelper.Config() |
|
|
cfgA.wsUrl = alibabaAppURL |
|
|
cfgA.wsUrl = alibabaAppURL |
|
|
cfgA.apiKey = alibabaAppKey |
|
|
cfgA.apiKey = alibabaAppKey |
|
|
@ -922,8 +986,11 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
bailianCallbackB = cbB |
|
|
bailianCallbackB = cbB |
|
|
|
|
|
|
|
|
Task { |
|
|
Task { |
|
|
_ = await bailianAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
os_log("[AST-INIT] Alibaba: initializing A service...", log: self.ctLog, type: .info) |
|
|
_ = await bailianAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
let rA = await bailianAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
|
|
|
os_log("[AST-INIT] Alibaba: A service result=%d, initializing B service...", log: self.ctLog, type: .info, rA ? 1 : 0) |
|
|
|
|
|
let rB = await bailianAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
|
|
|
os_log("[AST-INIT] Alibaba: B service result=%d. Both services ready.", log: self.ctLog, type: .info, rB ? 1 : 0) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
result(true) |
|
|
result(true) |
|
|
@ -935,7 +1002,18 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
|
|
|
|
|
|
/// 发送AST事件到Flutter层 |
|
|
/// 发送AST事件到Flutter层 |
|
|
internal func sendAstEvent(_ event: [String: Any]) { |
|
|
internal func sendAstEvent(_ event: [String: Any]) { |
|
|
if astEventSink == nil { return } |
|
|
let eventType = event["type"] as? String ?? "unknown" |
|
|
|
|
|
let sid = event["serviceId"] as? String ?? "?" |
|
|
|
|
|
if astEventSink == nil { |
|
|
|
|
|
os_log("[AST-EVENT] DROPPED (no sink): type=%{public}@ serviceId=%{public}@", |
|
|
|
|
|
log: ctLog, type: .error, eventType, sid) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
// 对于非高频事件(非 recognizing/translatedInterim),打印详细日志 |
|
|
|
|
|
if eventType != "recognizing" && eventType != "translatedInterim" { |
|
|
|
|
|
os_log("[AST-EVENT] sending: type=%{public}@ serviceId=%{public}@ direction=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, eventType, sid, event["direction"] as? String ?? "N/A") |
|
|
|
|
|
} |
|
|
DispatchQueue.main.async { [weak self] in |
|
|
DispatchQueue.main.async { [weak self] in |
|
|
self?.astEventSink?(event) |
|
|
self?.astEventSink?(event) |
|
|
} |
|
|
} |
|
|
@ -949,13 +1027,11 @@ private func sendAudioDataEvent(_ event: [String: Any]) { |
|
|
*/ |
|
|
*/ |
|
|
private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventCallback { |
|
|
private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventCallback { |
|
|
private weak var plugin: AzureSpeechPlugin? |
|
|
private weak var plugin: AzureSpeechPlugin? |
|
|
|
|
|
private let serviceId: String |
|
|
|
|
|
|
|
|
/** |
|
|
init(plugin: AzureSpeechPlugin, serviceId: String = "A") { |
|
|
* 构造函数 |
|
|
|
|
|
* @param plugin AzureSpeechPlugin 实例的弱引用 |
|
|
|
|
|
*/ |
|
|
|
|
|
init(plugin: AzureSpeechPlugin) { |
|
|
|
|
|
self.plugin = plugin |
|
|
self.plugin = plugin |
|
|
|
|
|
self.serviceId = serviceId |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -1136,22 +1212,14 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC |
|
|
public func onSynthesisAudioGenerated(text: String, audioData: Data) { |
|
|
public func onSynthesisAudioGenerated(text: String, audioData: Data) { |
|
|
guard let plugin = plugin else { return } |
|
|
guard let plugin = plugin else { return } |
|
|
|
|
|
|
|
|
os_log("语音合成音频生成,文本=%@,音频数据大小=%d", log: plugin.log, type: .info, text, audioData.count) |
|
|
// 只有 A 通道(己方翻译后的语音)推送到耳机,B 通道不推 |
|
|
|
|
|
guard serviceId == "A" else { return } |
|
|
|
|
|
|
|
|
// Azure TTS返回的是RIFF WAV格式,需要去除文件头提取PCM数据 |
|
|
|
|
|
let pcmData = plugin.extractPcmFromWav(audioData) |
|
|
let pcmData = plugin.extractPcmFromWav(audioData) |
|
|
if !pcmData.isEmpty { |
|
|
if !pcmData.isEmpty { |
|
|
os_log("提取PCM数据,大小=%d字节", log: plugin.log, type: .info, pcmData.count) |
|
|
os_log("[AzureAST-%{public}@] TTS音频推送到耳机,大小=%d字节", log: plugin.ctLog, type: .info, serviceId, pcmData.count) |
|
|
BleService.shared.writeExternalAudioData(data: pcmData) |
|
|
BleService.shared.writeExternalAudioData(data: pcmData) |
|
|
} else { |
|
|
|
|
|
os_log("提取PCM数据失败,音频数据可能不是WAV格式", log: plugin.log, type: .error) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// plugin?.sendAsrEvent([ |
|
|
|
|
|
// "type": "synthesisAudioGenerated", |
|
|
|
|
|
// "text": text, |
|
|
|
|
|
// "audioDataSize": audioData.count |
|
|
|
|
|
// ]) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1185,6 +1253,7 @@ extension AzureSpeechPlugin: BleService.Callback { |
|
|
azureAsrHelper.audioStream?.saveAudioDataTo(data: data) |
|
|
azureAsrHelper.audioStream?.saveAudioDataTo(data: data) |
|
|
} |
|
|
} |
|
|
else if channel == 2 { |
|
|
else if channel == 2 { |
|
|
|
|
|
bleAudioRecvCount += 1 |
|
|
if (isRecord) { |
|
|
if (isRecord) { |
|
|
recordfile?.setAudioConfig(sampleRate: 16000, channels: 2) |
|
|
recordfile?.setAudioConfig(sampleRate: 16000, channels: 2) |
|
|
recordfile?.saveAudioDataToWav(data) |
|
|
recordfile?.saveAudioDataToWav(data) |
|
|
@ -1219,23 +1288,53 @@ extension AzureSpeechPlugin: BleService.Callback { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// print("分离音频数据 - 左声道: \(leftBuffer.count) bytes, 右声道: \(rightBuffer.count) bytes") |
|
|
// 每200帧打印一次声道音量(与 Android 对齐) |
|
|
|
|
|
if bleAudioRecvCount % 200 == 1 { |
|
|
// 安全解包版本 |
|
|
var leftRms: Double = 0 |
|
|
guard let audioStream = azureAsrHelper.audioStream else { |
|
|
var rightRms: Double = 0 |
|
|
// os_log("音频流未初始化", type: .error) |
|
|
for i in stride(from: 0, to: leftBuffer.count - 1, by: 2) { |
|
|
return |
|
|
let lSample = Int16(leftBuffer[i]) | (Int16(leftBuffer[i+1]) << 8) |
|
|
|
|
|
let rSample = Int16(rightBuffer[i]) | (Int16(rightBuffer[i+1]) << 8) |
|
|
|
|
|
leftRms += Double(lSample) * Double(lSample) |
|
|
|
|
|
rightRms += Double(rSample) * Double(rSample) |
|
|
|
|
|
} |
|
|
|
|
|
let samples = max(leftBuffer.count / 2, 1) |
|
|
|
|
|
leftRms = sqrt(leftRms / Double(samples)) |
|
|
|
|
|
rightRms = sqrt(rightRms / Double(samples)) |
|
|
|
|
|
os_log("声道音量: left(己方)=%d, right(对方)=%d, provider=%@, count=%lld", |
|
|
|
|
|
log: ctLog, type: .info, Int(leftRms), Int(rightRms), currentAstProvider, bleAudioRecvCount) |
|
|
} |
|
|
} |
|
|
azureAsrHelper.audioStream?.saveAudioDataTo(data: rightBuffer) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存右声道到 ASR audioStream(可能为 nil) |
|
|
|
|
|
if let audioStream = azureAsrHelper.audioStream { |
|
|
|
|
|
audioStream.saveAudioDataTo(data: rightBuffer) |
|
|
|
|
|
} else if bleAudioRecvCount % 200 == 1 { |
|
|
|
|
|
os_log("audioStream 未初始化,rightBuffer 未保存到ASR", log: ctLog, type: .default) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 根据当前 AST 提供商路由音频数据 |
|
|
// 根据当前 AST 提供商路由音频数据 |
|
|
|
|
|
// 左声道 -> A(己方语音),右声道 -> B(对方语音) |
|
|
if currentAstProvider == "volcano" { |
|
|
if currentAstProvider == "volcano" { |
|
|
doubaoAstHelperA.pushAudioData(leftBuffer) |
|
|
doubaoAstHelperA.pushAudioData(leftBuffer) |
|
|
|
|
|
doubaoAstHelperB.pushAudioData(rightBuffer) |
|
|
|
|
|
if bleAudioRecvCount % 200 == 1 { |
|
|
|
|
|
os_log("[STS] pushAudio: volcano A(left)=%d bytes, B(right)=%d bytes, count=%lld", |
|
|
|
|
|
log: ctLog, type: .info, leftBuffer.count, rightBuffer.count, bleAudioRecvCount) |
|
|
|
|
|
} |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
bailianAstHelperA.pushAudioData(leftBuffer) |
|
|
bailianAstHelperA.pushAudioData(leftBuffer) |
|
|
|
|
|
bailianAstHelperB.pushAudioData(rightBuffer) |
|
|
|
|
|
if bleAudioRecvCount % 200 == 1 { |
|
|
|
|
|
os_log("[STS] pushAudio: alibaba A(left)=%d bytes, B(right)=%d bytes, count=%lld", |
|
|
|
|
|
log: ctLog, type: .info, leftBuffer.count, rightBuffer.count, bleAudioRecvCount) |
|
|
|
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
azureAstHelper.pushAudioData(audioData: leftBuffer) |
|
|
azureAstHelperA.pushAudioData(audioData: leftBuffer) |
|
|
|
|
|
azureAstHelperB.pushAudioData(audioData: rightBuffer) |
|
|
|
|
|
if bleAudioRecvCount % 200 == 1 { |
|
|
|
|
|
os_log("[STS] pushAudio: azure A(left)=%d bytes, B(right)=%d bytes, count=%lld", |
|
|
|
|
|
log: ctLog, type: .info, leftBuffer.count, rightBuffer.count, bleAudioRecvCount) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1328,70 +1427,95 @@ private class DoubaoCallbackProxy: DoubaoE2ETranslateHelper.Callback { |
|
|
private let serviceId: String |
|
|
private let serviceId: String |
|
|
private let direction: String |
|
|
private let direction: String |
|
|
private let targetLanguage: String |
|
|
private let targetLanguage: String |
|
|
|
|
|
private let ctLog = OSLog(subsystem: "com.azure.speech", category: "CallTranslation") |
|
|
|
|
|
|
|
|
|
|
|
/// 从 direction(如 "zh-Hans->en-US")提取源语言("zh-Hans") |
|
|
|
|
|
private var sourceLanguage: String { |
|
|
|
|
|
if let range = direction.range(of: "->") { |
|
|
|
|
|
return String(direction[direction.startIndex..<range.lowerBound]) |
|
|
|
|
|
} |
|
|
|
|
|
return direction |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
self.plugin = plugin |
|
|
self.plugin = plugin |
|
|
self.serviceId = serviceId |
|
|
self.serviceId = serviceId |
|
|
self.direction = direction |
|
|
self.direction = direction |
|
|
self.targetLanguage = targetLanguage |
|
|
self.targetLanguage = targetLanguage |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] created direction=%{public}@ targetLang=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, direction, targetLanguage) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionStarted(sessionId: String) { |
|
|
func onSessionStarted(sessionId: String) { |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onSessionStarted sessionId=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, sessionId) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "serviceInitialized", |
|
|
"type": "serviceInitialized", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
"direction": direction |
|
|
"direction": direction, |
|
|
|
|
|
"sessionId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onPartialSourceText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, text) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "recognizing", |
|
|
"type": "recognizing", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"text": text, |
|
|
"text": text, |
|
|
"language": direction, |
|
|
"language": sourceLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onFinalSourceText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText) |
|
|
if let p = plugin { p.doubaoFinalSourceTextCache["\(serviceId):\(sessionId)"] = finalText } |
|
|
if let p = plugin { p.doubaoFinalSourceTextCache["\(serviceId):\(sessionId)"] = finalText } |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "recognized", |
|
|
"type": "recognized", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"text": finalText, |
|
|
"text": finalText, |
|
|
"language": direction, |
|
|
"language": sourceLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onPartialText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, text) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translatedInterim", |
|
|
"type": "translatedInterim", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": text, |
|
|
"translatedText": text, |
|
|
|
|
|
"originalText": "", |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
let isRightChannel = (serviceId == "A") |
|
|
let isServiceA = (serviceId == "A") |
|
|
if data.count > 0 { |
|
|
if data.count > 0 && isServiceA { |
|
|
if isRightChannel { |
|
|
os_log("[DoubaoCallback-%{public}@] onPartialAudio writing %d bytes to BLE", |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
log: ctLog, type: .debug, serviceId, data.count) |
|
|
} else { |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onSessionFinished finalText=%{public}@ original=%{public}@ audioLen=%d", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText, original, finalAudio.count) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translated", |
|
|
"type": "translated", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": finalText, |
|
|
"translatedText": finalText, |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId, |
|
|
"utteranceId": sessionId, |
|
|
@ -1400,6 +1524,8 @@ private class DoubaoCallbackProxy: DoubaoE2ETranslateHelper.Callback { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onSessionError code=%d msg=%{public}@", |
|
|
|
|
|
log: ctLog, type: .error, serviceId, code, message) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "error", |
|
|
"type": "error", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
@ -1413,9 +1539,12 @@ private class DoubaoCallbackProxy: DoubaoE2ETranslateHelper.Callback { |
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
os_log("[DoubaoCallback-%{public}@] onFinalTranslatedText finalText=%{public}@ original=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText, original) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translated", |
|
|
"type": "translated", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": finalText, |
|
|
"translatedText": finalText, |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId, |
|
|
"utteranceId": sessionId, |
|
|
@ -1430,74 +1559,106 @@ private class AliyunCallbackProxy: AliyunBailianE2EHelper.Callback { |
|
|
private let serviceId: String |
|
|
private let serviceId: String |
|
|
private let direction: String |
|
|
private let direction: String |
|
|
private let targetLanguage: String |
|
|
private let targetLanguage: String |
|
|
|
|
|
private let ctLog = OSLog(subsystem: "com.azure.speech", category: "CallTranslation") |
|
|
|
|
|
private var sourceTextCache: [String: String] = [:] |
|
|
|
|
|
|
|
|
|
|
|
/// 从 direction(如 "zh-Hans->en-US")提取源语言("zh-Hans") |
|
|
|
|
|
private var sourceLanguage: String { |
|
|
|
|
|
if let range = direction.range(of: "->") { |
|
|
|
|
|
return String(direction[direction.startIndex..<range.lowerBound]) |
|
|
|
|
|
} |
|
|
|
|
|
return direction |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
self.plugin = plugin |
|
|
self.plugin = plugin |
|
|
self.serviceId = serviceId |
|
|
self.serviceId = serviceId |
|
|
self.direction = direction |
|
|
self.direction = direction |
|
|
self.targetLanguage = targetLanguage |
|
|
self.targetLanguage = targetLanguage |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] created direction=%{public}@ targetLang=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, direction, targetLanguage) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionStarted(sessionId: String) { |
|
|
func onSessionStarted(sessionId: String) { |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onSessionStarted sessionId=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, sessionId) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "serviceInitialized", |
|
|
"type": "serviceInitialized", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
"direction": direction |
|
|
"direction": direction, |
|
|
|
|
|
"sessionId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onPartialSourceText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, text) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "recognizing", |
|
|
"type": "recognizing", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"text": text, |
|
|
"text": text, |
|
|
"language": direction, |
|
|
"language": sourceLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onFinalSourceText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText) |
|
|
|
|
|
sourceTextCache["\(serviceId):\(sessionId)"] = finalText |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "recognized", |
|
|
"type": "recognized", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"text": finalText, |
|
|
"text": finalText, |
|
|
"language": direction, |
|
|
"language": sourceLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onPartialText text=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, text) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translatedInterim", |
|
|
"type": "translatedInterim", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": text, |
|
|
"translatedText": text, |
|
|
|
|
|
"originalText": "", |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
let isRightChannel = (serviceId == "A") |
|
|
let isServiceA = (serviceId == "A") |
|
|
if data.count > 0 { |
|
|
if data.count > 0 && isServiceA { |
|
|
if isRightChannel { |
|
|
os_log("[AliyunCallback-%{public}@] onPartialAudio writing %d bytes to BLE", |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
log: ctLog, type: .debug, serviceId, data.count) |
|
|
} else { |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
BleService.shared.writeExternalAudioData(data: data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
|
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
|
|
|
let original = sourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onSessionFinished finalText=%{public}@ original=%{public}@ audioLen=%d", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText, original, finalAudio.count) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translated", |
|
|
"type": "translated", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": finalText, |
|
|
"translatedText": finalText, |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId, |
|
|
|
|
|
"originalText": original |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onSessionError code=%d msg=%{public}@", |
|
|
|
|
|
log: ctLog, type: .error, serviceId, code, message) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "error", |
|
|
"type": "error", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
@ -1509,12 +1670,18 @@ private class AliyunCallbackProxy: AliyunBailianE2EHelper.Callback { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
|
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
|
|
|
let original = sourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
os_log("[AliyunCallback-%{public}@] onFinalTranslatedText finalText=%{public}@ original=%{public}@", |
|
|
|
|
|
log: ctLog, type: .info, serviceId, finalText, original) |
|
|
plugin?.sendAstEvent([ |
|
|
plugin?.sendAstEvent([ |
|
|
"type": "translated", |
|
|
"type": "translated", |
|
|
"serviceId": serviceId, |
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
"translatedText": finalText, |
|
|
"translatedText": finalText, |
|
|
"targetLanguage": targetLanguage, |
|
|
"targetLanguage": targetLanguage, |
|
|
"utteranceId": sessionId |
|
|
"utteranceId": sessionId, |
|
|
|
|
|
"originalText": original |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -1660,6 +1827,9 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
* @param errorDetails 错误详情 |
|
|
* @param errorDetails 错误详情 |
|
|
*/ |
|
|
*/ |
|
|
func onCanceled(sessionid:String,_ reason: String, _ errorDetails: String) { |
|
|
func onCanceled(sessionid:String,_ reason: String, _ errorDetails: String) { |
|
|
|
|
|
if let p = plugin { |
|
|
|
|
|
os_log("ASR onCanceled: sessionid=%@, reason=%@, error=%@", log: p.ctLog, type: .error, sessionid, reason, errorDetails) |
|
|
|
|
|
} |
|
|
plugin?.sendAsrEvent([ |
|
|
plugin?.sendAsrEvent([ |
|
|
"type": "canceled", |
|
|
"type": "canceled", |
|
|
"sessionid": sessionid, |
|
|
"sessionid": sessionid, |
|
|
|