|
|
|
@ -18,7 +18,7 @@ import ble_service |
|
|
|
// 日志标签 |
|
|
|
private let tag = "AzureSpeechPlugin" |
|
|
|
// 日志对象 |
|
|
|
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") |
|
|
|
internal let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") |
|
|
|
|
|
|
|
// MARK: - 防抖相关(iOS 原生层) |
|
|
|
// 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发 |
|
|
|
@ -264,6 +264,65 @@ private func sendAstEvent(_ event: [String: Any]) { |
|
|
|
sendAudioDataEvent(eventMap) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 从WAV格式音频数据中提取纯PCM数据 |
|
|
|
* @param wavData WAV格式的音频数据 |
|
|
|
* @return 纯PCM音频数据,如果提取失败则返回空Data |
|
|
|
*/ |
|
|
|
internal func extractPcmFromWav(_ wavData: Data) -> Data { |
|
|
|
// WAV文件头最小长度为44字节 |
|
|
|
guard wavData.count >= 44 else { |
|
|
|
os_log("WAV数据长度不足,无法解析文件头", log: log, type: .error) |
|
|
|
return Data() |
|
|
|
} |
|
|
|
|
|
|
|
// 检查RIFF标识符 |
|
|
|
let riffHeader = String(data: wavData.subdata(in: 0..<4), encoding: .ascii) ?? "" |
|
|
|
guard riffHeader == "RIFF" else { |
|
|
|
os_log("不是有效的RIFF WAV文件,标识符: %@", log: log, type: .error, riffHeader) |
|
|
|
return Data() |
|
|
|
} |
|
|
|
|
|
|
|
// 检查WAVE标识符 |
|
|
|
let waveHeader = String(data: wavData.subdata(in: 8..<12), encoding: .ascii) ?? "" |
|
|
|
guard waveHeader == "WAVE" else { |
|
|
|
os_log("不是有效的WAVE文件,标识符: %@", log: log, type: .error, waveHeader) |
|
|
|
return Data() |
|
|
|
} |
|
|
|
|
|
|
|
// 查找data chunk |
|
|
|
var offset = 12 // 跳过RIFF头部 |
|
|
|
while offset < wavData.count - 8 { |
|
|
|
let chunkIdData = wavData.subdata(in: offset..<(offset + 4)) |
|
|
|
let chunkId = String(data: chunkIdData, encoding: .ascii) ?? "" |
|
|
|
|
|
|
|
// 读取chunk大小(小端序) |
|
|
|
let chunkSizeData = wavData.subdata(in: (offset + 4)..<(offset + 8)) |
|
|
|
let chunkSize = chunkSizeData.withUnsafeBytes { bytes in |
|
|
|
return bytes.load(as: UInt32.self).littleEndian |
|
|
|
} |
|
|
|
|
|
|
|
if chunkId == "data" { |
|
|
|
// 找到data chunk,提取PCM数据 |
|
|
|
let dataOffset = offset + 8 |
|
|
|
let dataSize = min(Int(chunkSize), wavData.count - dataOffset) |
|
|
|
|
|
|
|
if dataSize > 0 { |
|
|
|
let pcmData = wavData.subdata(in: dataOffset..<(dataOffset + dataSize)) |
|
|
|
os_log("成功提取PCM数据: %d字节", log: log, type: .info, pcmData.count) |
|
|
|
return pcmData |
|
|
|
} |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
// 移动到下一个chunk |
|
|
|
offset += 8 + Int(chunkSize) |
|
|
|
} |
|
|
|
|
|
|
|
os_log("未找到data chunk", log: log, type: .error) |
|
|
|
return Data() |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - ASR 方法处理 |
|
|
|
|
|
|
|
private func handleAsrMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
|
|
|
@ -950,9 +1009,18 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC |
|
|
|
* @param audioData 音频数据 |
|
|
|
*/ |
|
|
|
public func onSynthesisAudioGenerated(text: String, audioData: Data) { |
|
|
|
print("语音合成音频数据生成回调", text, audioData.count) |
|
|
|
// 调用BLE服务写入音频数据 |
|
|
|
BleService.shared.writeExternalAudioData(data: audioData) |
|
|
|
guard let plugin = plugin else { return } |
|
|
|
|
|
|
|
os_log("语音合成音频生成,文本=%@,音频数据大小=%d", log: plugin.log, type: .info, text, audioData.count) |
|
|
|
|
|
|
|
// Azure TTS返回的是RIFF WAV格式,需要去除文件头提取PCM数据 |
|
|
|
let pcmData = plugin.extractPcmFromWav(audioData) |
|
|
|
if !pcmData.isEmpty { |
|
|
|
os_log("提取PCM数据,大小=%d字节", log: plugin.log, type: .info, pcmData.count) |
|
|
|
BleService.shared.writeExternalAudioData(data: pcmData) |
|
|
|
} else { |
|
|
|
os_log("提取PCM数据失败,音频数据可能不是WAV格式", log: plugin.log, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
// plugin?.sendAsrEvent([ |
|
|
|
// "type": "synthesisAudioGenerated", |
|
|
|
|