使用EZOutput输出音频时出现机器人音及卡顿问题求助
问题:EZOutput输出音频出现机器人音、卡顿及丢音问题
我使用EZAudio框架的EZOutput组件实现类似LiveListen的功能,从self.myAudioBufferList获取音频流输出,同时持有音频基本描述self.asbd!。但实现EZOutput数据源的shouldFill方法后,输出声音呈现机器人音,还存在卡顿、部分音频丢失的情况。
当前实现的shouldFill方法代码:
func output(_ output: EZOutput!, shouldFill audioBufferList: UnsafeMutablePointer<AudioBufferList>!, withNumberOfFrames frames: UInt32, timestamp: UnsafePointer<AudioTimeStamp>!) -> OSStatus { if self.asbd != nil { output.inputFormat = self.asbd! } if self.audioBufferList != nil { audioBufferList.pointee.mNumberBuffers = self.myAudioBufferList!.mNumberBuffers audioBufferList.pointee.mBuffers.mNumberChannels = self.myAudioBufferList!.mBuffers.mNumberChannels audioBufferList.pointee.mBuffers.mData = self.myAudioBufferList!.mBuffers.mData } else { print("it is nil, and that is why it is making low noise") audioBufferList.pointee.mBuffers.mData = nil } return noErr; }
另外,当设置audioBufferList的mDataByteSize时,音质会变得更差。当前配置中dataByteSize为4096,但myAudioBufferList的大小不固定,波动但从未超过512。设置代码如下:
audioBufferList.pointee.mBuffers.mDataByteSize = self.myAudioBufferList!.mBuffers.mDataByteSize
数据源方法传入的frames值为1024,我也曾尝试创建EZOutput实例时传入inputFormat,但音质没有改善:
// Assign a delegate and datasource to the shared instance of the output to provide the output audio data self.ezOutput = EZOutput.init(dataSource: self, inputFormat: asbd) self.ezOutput?.delegate = self
问题原因分析
- 音频帧数量不匹配:
shouldFill要求提供1024帧数据,但myAudioBufferList实际帧数量远小于这个值(按16bit单声道计算,512字节仅对应256帧),直接赋值会导致输出端重复读取不足的数据,产生机器人音。 - 重复修改输入格式:每次回调都设置
output.inputFormat会打乱EZOutput的内部状态,音频格式应仅在初始化时设置一次。 - 缓冲区指针直接赋值风险:直接将
myAudioBufferList的mData指针传给输出缓冲区,既没处理数据长度不匹配的问题,还可能因原缓冲区被释放/覆盖导致异常。
修复方案
- 初始化时固定输入格式:移除回调内的
output.inputFormat设置,确保创建EZOutput时传入正确的asbd,后续不再修改。 - 新增数据缓存处理帧不匹配:维护一个音频数据缓存,将
myAudioBufferList的新数据存入缓存,当缓存数据量满足frames要求时再拷贝到输出缓冲区;不足时填充静音数据避免卡顿。 - 拷贝数据而非直接赋值指针:使用
memcpy将原缓冲区数据拷贝到输出缓冲区,根据实际数据量设置mDataByteSize,剩余部分填充静音。
修复后的示例代码:
// 新增缓存用的音频数据容器,需匹配asbd格式初始化 private var audioDataCache = Data() func output(_ output: EZOutput!, shouldFill audioBufferList: UnsafeMutablePointer<AudioBufferList>!, withNumberOfFrames frames: UInt32, timestamp: UnsafePointer<AudioTimeStamp>!) -> OSStatus { guard let asbd = self.asbd else { return noErr } // 计算单帧字节数:声道数 × 位深/8 let bytesPerFrame = Int(asbd.mChannelsPerFrame) * Int(asbd.mBitsPerChannel / 8) let requiredBytes = Int(frames) * bytesPerFrame // 将新的音频数据加入缓存 if let sourceBufferList = self.myAudioBufferList { let sourceBytes = Int(sourceBufferList.mBuffers.mDataByteSize) if let sourceData = sourceBufferList.mBuffers.mData { audioDataCache.append(UnsafeRawBufferPointer(start: sourceData, count: sourceBytes)) } } // 取出需要的字节数,剩余数据留在缓存中 let outputData = audioDataCache.prefix(requiredBytes) audioDataCache = audioDataCache.dropFirst(requiredBytes) // 填充输出缓冲区 let buffer = &audioBufferList.pointee.mBuffers[0] buffer.mNumberChannels = asbd.mChannelsPerFrame if outputData.count > 0 { memcpy(buffer.mData, outputData.baseAddress, outputData.count) } // 数据不足时填充静音(以16bit单声道为例,静音值为0) let missingBytes = requiredBytes - outputData.count if missingBytes > 0 { let muteData = Data(repeating: 0, count: missingBytes) memcpy(buffer.mData?.advanced(by: outputData.count), muteData.baseAddress, missingBytes) buffer.mDataByteSize = UInt32(requiredBytes) } else { buffer.mDataByteSize = UInt32(outputData.count) } return noErr }
额外注意事项
- 确保
asbd的采样率、声道数、位深等参数,与输入输出格式完全一致,格式不匹配也会导致音质异常。 - 避免在
shouldFill回调中执行耗时操作,该回调运行在音频线程,阻塞会引发卡顿。 - 注意
myAudioBufferList的生命周期,确保使用时数据有效,不会被提前释放。
内容的提问来源于stack exchange,提问作者NSCoder
相关产品推荐
相关产品推荐

