如何录制应用输出的全部音频?AVSpeechSynthesizer声波生成需求
嘿,这个需求我刚好踩过坑!AVSpeechSynthesizer确实没有直接导出音频的API,但咱们完全不用录制整个应用的声音——用AVAudioEngine就能精准捕获它的合成输出,不管是存成文件还是直接拿数据生成波形都没问题。下面是具体的实现思路和代码:
核心方案:用AVAudioEngine拦截合成音频
AVSpeechSynthesizer的语音输出会经过系统音频管线,我们可以通过AVAudioEngine的节点来“截胡”这段音频,既高效又不会混入其他应用的声音。
步骤1:实现音频捕获与保存
下面是Swift的完整实现代码,包含了从启动合成、捕获音频到保存文件的全流程:
import AVFoundation class SpeechAudioCapture { private let audioEngine = AVAudioEngine() private let synthesizer = AVSpeechSynthesizer() private var outputAudioFile: AVAudioFile? // 开始捕获并合成语音 func startCapturingSpeech(text: String) { // 定义音频格式(这里用16位PCM、44.1kHz采样率、单声道,适合后续处理) guard let audioFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 44100, channels: 1, interleaved: false) else { print("音频格式初始化失败") return } // 创建保存音频的文件路径(存在Documents目录) let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let audioSaveURL = documentsDir.appendingPathComponent("synthesized_speech.wav") // 初始化音频文件用于写入 do { outputAudioFile = try AVAudioFile(forWriting: audioSaveURL, settings: audioFormat.settings, commonFormat: audioFormat.commonFormat, interleaved: audioFormat.isInterleaved) } catch { print("创建音频文件失败:\(error.localizedDescription)") return } // 获取音频引擎的输入节点,用来接收合成器的输出 let inputNode = audioEngine.inputNode let bus = 0 // 安装Tap,实时捕获音频数据 inputNode.installTap(onBus: bus, bufferSize: 1024, format: audioFormat) { [weak self] buffer, time in guard let self = self, let audioFile = self.outputAudioFile else { return } do { try audioFile.write(from: buffer) } catch { print("写入音频数据失败:\(error.localizedDescription)") } } // 启动音频引擎 do { try audioEngine.start() } catch { print("启动音频引擎失败:\(error.localizedDescription)") return } // 开始语音合成 let utterance = AVSpeechUtterance(string: text) utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") // 可根据需求切换语言 synthesizer.speak(utterance) } // 停止捕获与合成 func stopCapturing() { synthesizer.stopSpeaking(at: .immediate) audioEngine.stop() audioEngine.inputNode.removeTap(onBus: 0) outputAudioFile = nil print("音频捕获完成,文件已保存到Documents目录") } }
步骤2:权限与注意事项
- 必须在
Info.plist中添加麦克风权限描述NSMicrophoneUsageDescription——虽然我们不是录制麦克风,但AVAudioEngine的inputNode会默认请求麦克风权限,这是系统的硬性要求。 - 要确保在语音合成之前启动音频引擎,否则可能会丢失开头的部分音频数据。
- 如果需要生成声波,可以读取保存的音频文件,通过
AVAudioPCMBuffer提取PCM数据,计算每个片段的振幅值,再绘制到UI上。
进阶:直接获取数据生成波形(无需保存文件)
如果不需要保存音频文件,只想实时获取数据来生成波形,可以在Tap的回调中直接处理PCM数据,省去写入文件的步骤:
inputNode.installTap(onBus: bus, bufferSize: 1024, format: audioFormat) { buffer, time in guard let pcmBuffer = buffer as? AVAudioPCMBuffer, let channelData = pcmBuffer.int16ChannelData else { return } let channelDataArray = Array(UnsafeBufferPointer(start: channelData.pointee, count: Int(pcmBuffer.frameLength))) // 计算当前片段的平均振幅(取绝对值的平均值,适合作为波形的一个点) let averageAmplitude = channelDataArray.reduce(0) { $0 + abs($1) } / channelDataArray.count // 回到主线程更新波形UI DispatchQueue.main.async { // 这里把averageAmplitude传给你的波形绘制逻辑 // 比如更新自定义View的波形数据 } }
内容的提问来源于stack exchange,提问作者Efraim
相关产品推荐
相关产品推荐

