Swift保存语音Utterance到文件时的缓冲错误问题求助
之前使用的语音合成写入文件方案,在iOS 16中存在缓冲错误但功能仍能运行,到iOS 17首个测试版中直接完全失效,核心是音频缓冲格式不匹配导致的帧计数问题,以下是问题详情和修复方案:
问题表现
- iOS 16:运行时会打印约20次错误信息:
2023-06-17 15:35:33.811838-0400 RecordSpeechFix[3899:1958883] [AXTTSCommon] TTSPlaybackEnqueueFullAudioQueueBuffer: error -66686 enqueueing buffer
但完成回调能正常触发,文件可正常生成。 - iOS 17(首个测试版):报错信息更明确:
Input data proc returned inconsistent 512 packets for 2,048 bytes; at 2 bytes per packet, that is actually 1,024 packets
功能完全失效,完成回调无法触发,文件生成失败。
问题原因
AVSpeechSynthesizer输出的音频缓冲默认是浮点格式(pcmFormatFloat32),但原代码在创建AVAudioFile时强制指定了commonFormat: .pcmFormatInt16,这种跨格式写入会导致帧长度和字节数的计算不匹配。iOS 16对这种不匹配的校验较宽松,仍能勉强运行;iOS 17收紧了校验逻辑,直接触发错误导致流程中断。
修复方案
移除强制格式转换的参数,让AVAudioFile使用缓冲的原生格式写入即可解决问题。修改saveSpeechUtteranceToFile函数中创建AVAudioFile的代码:
修复后的完整代码
import AVFoundation import SwiftUI struct ContentView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Record Speech") } .padding() .onTapGesture { saveSpeechUtteranceToFile(phrase: "This produces warnings.") { print("In iOS 16, this will print. In iOS17, this will not.") } } } func documentsDirectory(fileName: String, ext: String) -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let documentsDirectoryURL = paths[0] return documentsDirectoryURL.appendingPathComponent("\(fileName).\(ext)") } let synthesizer = AVSpeechSynthesizer() func saveSpeechUtteranceToFile(phrase: String, completionHandler: @escaping () -> ()) { let fileURL = documentsDirectory(fileName: "Test", ext: "caf") let utterance = AVSpeechUtterance(string: phrase) utterance.voice = AVSpeechSynthesisVoice(language: "en-US") utterance.rate = 0.50 utterance.volume = 1.0 var output: AVAudioFile? synthesizer.write(utterance) {(buffer: AVAudioBuffer) in guard let pcmBuffer = buffer as? AVAudioPCMBuffer else { fatalError("unknown buffer type: \(buffer)") } if pcmBuffer.frameLength == 0 { // Done completionHandler() } else { do{ if output == nil { // 移除强制格式转换,使用缓冲原生格式写入 try output = AVAudioFile( forWriting: fileURL, settings: pcmBuffer.format.settings, interleaved: false) } try output?.write(from: pcmBuffer) }catch { print("Buffer error: \(error.localizedDescription)") } } } } }
关键修复点
- 移除
commonFormat: .pcmFormatInt16参数,避免强制格式转换导致的帧计数不匹配 - 修正文件扩展名的小问题(原代码中
ext: ".caf"会生成Test..caf,改为ext: "caf"更合理) - 优化错误打印,输出具体错误信息便于排查
内容的提问来源于stack exchange,提问作者ShadowDES

