我想在我的 Watch 应用程序中进行语音识别,显示实时转录。由于
SFSpeechRecognizer
在 WatchOS 上不可用,我将应用程序设置为使用 WatchConnectivity
将音频流式传输到 iOS 伴侣。在尝试这个之前,我在 iPhone 上尝试了相同的代码,但不涉及 Watch - 它在那里工作。
通过我的流式传输尝试,伴侣将接收音频块并且不会抛出任何错误,但它也不会转录任何文本。我怀疑我做错了什么,从
AVAudioPCMBuffer
转换回来,但我可以'由于我缺乏使用原始数据和指针的经验,所以我不太确定。
现在,整个过程如下:
recognitionTask
recognitionTask
并回答 ok 或一些错误:guard let speechRecognizer = self.speechRecognizer else {
WCManager.shared.sendWatchMessage(.speechRecognitionRequest(.error("no speech recognizer")))
return
}
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let recognitionRequest = recognitionRequest else {
WCManager.shared.sendWatchMessage(.speechRecognitionRequest(.error("speech recognition request denied by ios")))
return
}
recognitionRequest.shouldReportPartialResults = true
if #available(iOS 13, *) {
recognitionRequest.requiresOnDeviceRecognition = true
}
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
if let result = result {
let t = result.bestTranscription.formattedString
WCManager.shared.sendWatchMessage(.recognizedSpeech(t))
}
if error != nil {
self.recognitionRequest = nil
self.recognitionTask = nil
WCManager.shared.sendWatchMessage(.speechRecognition(.error("?")))
}
}
WCManager.shared.sendWatchMessage(.speechRecognitionRequest(.ok))
do {
try startAudioSession()
} catch {
self.state = .error("couldn't start audio session")
return
}
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat)
{ (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
let data = Data(bytes: audioBuffer.mData!, count: Int(audioBuffer.mDataByteSize))
if self.state == .running {
WCManager.shared.sendWatchMessage(.speechRecognition(.chunk(data, frameCount: Int(buffer.frameLength))))
}
}
audioEngine.prepare()
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: recordingFormat, requiringSecureCoding: true)
WCManager.shared.sendWatchMessage(.speechRecognition(.audioFormat(data)),
errorHandler: { _ in
self.state = .error("iphone unavailable")
})
self.state = .sentAudioFormat
} catch {
self.state = .error("could not convert audio format")
}
.ok
或.error()
:guard let format = try? NSKeyedUnarchiver.unarchivedObject(ofClass: AVAudioFormat.self, from: data) else {
// ...send back .error, destroy the recognitionTask
}
self.audioFormat = format
// ...send back .ok
try audioEngine.start()
recognitionRequest
:guard let pcm = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: AVAudioFrameCount(frameCount)) else {
// ...send back .error, destroy the recognitionTask
}
let channels = UnsafeBufferPointer(start: pcm.floatChannelData, count: Int(pcm.format.channelCount))
let data = chunk as NSData
data.getBytes(UnsafeMutableRawPointer(channels[0]), length: data.length)
recognitionRequest.append(pcm)
任何想法都非常感谢。感谢您抽出宝贵时间!
我强烈怀疑问题是由于链接速度太慢,您甚至无法跟上实时。您要附加由长时间静音分隔的微小(可能短至 20 毫秒)声音样本。即使是人耳也无法识别。
我将从探索 CMSampleBuffers 开始,因为您可以设置它们的时间戳。这将使识别器知道何时记录此缓冲区并消除静音。
如果这不起作用,您需要进行缓冲以积累足够的 AVAudioPCMBuffers 来执行分析。这将变得更加复杂,因此希望 CMSampleBuffers 能够代替它工作。
无论哪种情况,您都可以考虑以压缩格式传输数据。我不确定 watchOS 支持哪些格式,但您可以显着降低手表和手机之间的带宽要求。请注意不要让手表的 CPU 不堪重负。您想要易于计算的压缩,而不是您可以获得的最严格的压缩。
此外,我看不到您在此处配置的采样频率。确保它很低。大概是 8kHz。绝对没有理由仅仅为了语音转录而录制 CD 质量的声音。实际上更糟,因为它包含了太多不在人声范围内的频率。
复制内存后忘记更新
AVAudioPCMBuffer.frameLength
了。它现在可以完美地工作,没有任何明显的延迟:)
// ...
data.getBytes(UnsafeMutableRawPointer(channels[0]), length: data.length)
pcm.frameLength = AVAudioFrameCount(frameCount)
// ...