在SwiftUI中如何将文本转语音(TTS)输出写入音频文件?
问题根因
你定义的AVSpeechSynthesizer实例是generateSpeech函数内的局部变量,函数执行完成后该实例会被系统自动回收,而synth.write是异步操作,还没等到回调触发,实例就已经被释放了,所以回调永远不会执行。
原来在Storyboard项目中你大概率是把合成器实例定义成了ViewController的全局属性,实例生命周期和ViewController绑定,不会被提前释放,所以可以正常运行。
解决步骤
- 把AVSpeechSynthesizer实例从函数内部移到ContentView的属性中,保证它的生命周期和当前页面绑定
- 新增异步完成回调,在语音写入操作全部结束后再判断文件是否存在,原来的逻辑是函数刚返回就判断,此时异步写入还没完成,自然读不到文件
修改后的完整可运行代码如下:
import SwiftUI import AVFoundation struct ContentView: View { // 把合成器定义为页面属性,避免被提前释放 private let synth = AVSpeechSynthesizer() var body: some View { Button(action: runSpeech) { Text("Button") } } func runSpeech() { generateSpeech("hello world", 1.0) { fileURL in guard let fileURL = fileURL else { return } DispatchQueue.main.async { if FileManager.default.fileExists(atPath: fileURL.path) { print("🔴 Speech file exists at \(fileURL)") } else { print("🔴 no file exists at \(fileURL)") } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } // 增加完成回调,异步通知写操作结束 func generateSpeech(_ speech: String, _ speechRate: Float, completion: @escaping (URL?) -> Void) { let myUtterance = AVSpeechUtterance(string: speech) myUtterance.rate = speechRate myUtterance.voice = AVSpeechSynthesisVoice(language: "en")! myUtterance.postUtteranceDelay = 2 guard let dataPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { completion(nil) return } do { try FileManager.default.createDirectory(atPath: dataPath.path, withIntermediateDirectories: true) } catch { print("🔵Error creating directory: \(error.localizedDescription)") completion(nil) return } let fileName = "myspeech.caf" let fileURL = dataPath.appendingPathComponent(fileName) if FileManager.default.fileExists(atPath: fileURL.path) { print("🔵Speech file exists at \(fileURL)") completion(fileURL) return } print("🔵Speech file does not exist at \(fileURL)") var output: AVAudioFile? // 这里用外部传入的synth实例,不是局部变量 synth.write(myUtterance) { buffer in print("🔵callback") guard let pcmBuffer = buffer as? AVAudioPCMBuffer else { fatalError("unknown buffer type: \(buffer)") } if pcmBuffer.frameLength == 0 { // 写入完成,回调通知 completion(fileURL) } else { do { if output == nil { try output = AVAudioFile( forWriting: fileURL, settings: pcmBuffer.format.settings, commonFormat: .pcmFormatInt16, interleaved: false) } try output?.write(from: pcmBuffer) } catch { print("🔵error: \(error.localizedDescription)") completion(nil) } } } print("🔵return") }
内容的提问来源于stack exchange,提问作者Makoto Niijima
相关产品推荐
相关产品推荐

