SwiftUI中AVAudioEngine采集音频经GCDAsyncUdpSocket传输后杂音问题
音频传输杂音问题排查与修复
以下是导致接收端出现滋滋杂音的核心问题及对应修复方案:
1. 采样率不匹配(最关键原因)
发送端采集音频时使用44100Hz采样率,但接收端初始化的audioFormat是16000Hz,采样率不匹配会导致音频播放速度异常,直接产生严重杂音。
修复:统一采样率
在初始化和发送端使用相同的采样率,比如统一用16000Hz:
初始化代码修改:
func setupAudio () { let sampleRate = 16000 self.audioEngine = AVAudioEngine() self.mixer = AVAudioMixerNode() self.mixer.volume = 0 self.audioEngine.attach(mixer) self.socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main) // 明确指定Int16格式,和发送端保持一致 audioFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: Double(sampleRate), channels: 1, interleaved: true)! }
发送端format修改:
// 复用初始化时的统一格式,避免硬编码 let format = self.audioFormat
2. 音频Data转换长度错误
发送端将AVAudioPCMBuffer转Data时,计算的长度错误:
// 错误:只取了frameLength,没考虑每个Int16采样点占2字节 let data = Data(bytes: (buffer.int16ChannelData![0]), count: Int(buffer.frameLength))
正确长度应该是frameLength * 2,否则传输的数据只有一半,接收端解析会出现乱码杂音。
修复:修正Data长度计算
let frameCount = Int(buffer.frameLength) let data = Data(bytes: buffer.int16ChannelData![0], count: frameCount * MemoryLayout<Int16>.stride)
3. UDP不可靠性导致的丢包/时序问题
UDP没有重传机制,丢包后直接播放会出现断层杂音;同时接收端直接调用scheduleBuffer,没有处理音频帧的时序对齐,也会导致播放卡顿或杂音。
修复:添加音频缓存队列
接收端维护一个线程安全的音频缓存队列,确保按顺序播放:
// 定义线程安全队列 private let audioQueue = DispatchQueue(label: "com.audio.queue") private var bufferCache: [AVAudioPCMBuffer] = [] func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) { let buffer = getComingAudio(with: data) audioQueue.async { [weak self] in guard let self = self else { return } self.bufferCache.append(buffer) // 如果播放器处于空闲状态,开始播放缓存 if !self.audioPlayer.isPlaying { self.playNextBuffer() } } } private func playNextBuffer() { audioQueue.async { [weak self] in guard let self = self, !self.bufferCache.isEmpty else { return } let buffer = self.bufferCache.removeFirst() DispatchQueue.main.async { self.audioPlayer.scheduleBuffer(buffer) { [weak self] in self?.playNextBuffer() } if !self.audioPlayer.isPlaying { try? self.audioPlayer.start() } } } }
4. AVAudioSession设置不严谨
发送端直接使用try!强制解包,一旦权限不足或设备不支持会崩溃,同时没有设置合适的session选项,可能导致音频采集异常。
修复:完善Session设置
do { try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .mixWithOthers]) try AVAudioSession.sharedInstance().setActive(true) } catch { print("Session setup error: \(error)") }
5. 重复创建Mixer节点
初始化代码中重复创建了mixer节点,属于冗余代码,可能导致未知问题:
// 错误:重复创建 self.mixer = AVAudioMixerNode() self.mixer = AVAudioMixerNode()
修复:移除重复创建
删除其中一行self.mixer = AVAudioMixerNode()即可。
最终接收端转换函数优化
确保接收端的格式和发送端完全一致,避免不必要的类型转换:
func getComingAudio(with data: Data) -> AVAudioPCMBuffer { let frameCapacity = UInt32(data.count) / UInt32(MemoryLayout<Int16>.stride) guard let audioBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: frameCapacity) else { fatalError("Failed to create PCM buffer") } data.withUnsafeBytes { ptr in let int16Ptr = ptr.bindMemory(to: Int16.self).baseAddress! audioBuffer.int16ChannelData!.pointee.assign(from: int16Ptr, count: Int(frameCapacity)) } audioBuffer.frameLength = frameCapacity return audioBuffer }
内容的提问来源于stack exchange,提问作者ridvan
相关产品推荐
相关产品推荐

