如何将AVAudioPCMBuffer转换为可播放音频?该方案是否最优?
实现步骤与方案分析
一、AVAudioPCMBuffer 转字节数组(Data)
AVAudioPCMBuffer的音频数据存在floatChannelData(浮点格式)或int16ChannelData(16位整数格式)里,得根据当前音频格式选对应的方式转成字节数组(Swift里用Data表示)。
代码示例:转换为Data
修改你的getAudio函数,在tap回调里加转换逻辑(顺便补上麦克风权限处理,不然会崩溃):
import AVFoundation let audioEngine = AVAudioEngine() // 用来存转换后的字节数组,也可以按需实时处理不用缓存 var audioDataBuffer = Data() // 播放节点,提前初始化好 let playerNode = AVAudioPlayerNode() func setupPlayerNode() { // 把播放节点加到音频引擎里 audioEngine.attach(playerNode) // 连接到扬声器输出 let outputFormat = audioEngine.outputNode.outputFormat(forBus: 0) audioEngine.connect(playerNode, to: audioEngine.outputNode, format: outputFormat) } func getAudio() { // 先初始化播放节点 setupPlayerNode() let inputNode = audioEngine.inputNode let bus = 0 let inputFormat = inputNode.inputFormat(forBus: bus) inputNode.installTap(onBus: bus, bufferSize: 2048, format: inputFormat) { buffer, time in // 1. 把AVAudioPCMBuffer转成Data guard let channelData = buffer.floatChannelData else { return } let channelCount = Int(inputFormat.channelCount) let frameCount = Int(buffer.frameLength) let bytesPerFrame = Int(inputFormat.streamDescription.pointee.mBytesPerFrame) // 遍历每个声道,把数据追加到Data里 for channel in 0..<channelCount { let channelPointer = channelData[channel] let data = Data(bytes: channelPointer, count: frameCount * bytesPerFrame) audioDataBuffer.append(data) } // 2. 实时把Data转回PCMBuffer并播放(也可以攒够数据再播) self.playAudio(from: audioDataBuffer, format: inputFormat) // 播放完可以清空缓存,避免内存占用过大 audioDataBuffer.removeAll() } // 请求麦克风权限 AVAudioSession.sharedInstance().requestRecordPermission { granted in guard granted else { print("麦克风权限没开,用不了") return } DispatchQueue.main.async { do { try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default) try AVAudioSession.sharedInstance().setActive(true) self.audioEngine.prepare() try self.audioEngine.start() } catch { print("引擎启动失败: \(error)") } } } }
二、字节数组(Data)转回AVAudioPCMBuffer并播放
用AVAudioPlayerNode(AVAudioEngine自带的播放节点)直接播PCMBuffer是最高效的,不用写本地文件,全程内存操作。
代码示例:播放转换后的音频
func playAudio(from data: Data, format: AVAudioFormat) { // 根据Data大小创建对应的PCMBuffer guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(data.count) / format.streamDescription.pointee.mBytesPerFrame) else { print("创建PCMBuffer失败") return } buffer.frameLength = buffer.frameCapacity guard let channelData = buffer.floatChannelData else { return } // 把Data里的字节写入PCMBuffer let channelCount = Int(format.channelCount) let frameCount = Int(buffer.frameLength) let bytesPerFrame = Int(format.streamDescription.pointee.mBytesPerFrame) for channel in 0..<channelCount { let channelPointer = channelData[channel] let dataRange = data.startIndex + channel * frameCount * bytesPerFrame ..< data.startIndex + (channel + 1) * frameCount * bytesPerFrame data.copyBytes(to: channelPointer, from: dataRange) } // 开始播放 if !playerNode.isPlaying { do { try audioEngine.start() } catch { print("引擎启动失败: \(error)") return } playerNode.play() } playerNode.scheduleBuffer(buffer, completionHandler: nil) }
三、这个方案是不是最优解?
为啥这个方案合适
- 实时性拉满:全程都是内存里的数据转换,不用写磁盘读磁盘,适合实时对讲、实时音效处理这种场景。
- 生态统一:从麦克风输入(InputNode)到转换、播放(PlayerNode)全用AVAudioEngine的API,链路完整,维护起来方便。
- 灵活度高:中间可以插各种音频处理节点,比如加个EQ、混响,扩展起来很容易。
对比其他方案
- 写文件再播放:适合需要保存音频的场景,但实时性差,还占磁盘空间,完全没必要用在实时播放需求上。
- 用AVAudioRecorder/AVAudioPlayer:API确实简单,但没法做内存级的实时转换,灵活性比AVAudioEngine差远了。
总结:如果你的需求是实时拿麦克风音频、转字节数组再播放,这个基于AVAudioEngine的方案就是最优解;要是需要存音频,转成Data后写文件就行,之后用AVAudioPlayer播也没问题。
内容的提问来源于stack exchange,提问作者ridvan
相关产品推荐
相关产品推荐

