iOS使用AudioUnit/AudioConverterRef实现48K转16K采样率报错求助
问题解决方案
核心错误原因
- 回调参数转换错误:你在
conversionAudioController_Callback中错误将inAudioConverter转换器实例当做自定义对象转换,实际上传入的inUserData才是你注入的当前类实例指针,这是直接导致EXC_BAD_ACCESS崩溃的原因。 - 输入输出大小不匹配:错误码
1768846202转四字符码为insz,对应kAudioConverterErr_InvalidInputSize,原因是你固定写死输出buffer大小684、输出包数384,没有按照48000转16000的3:1降采样比例动态计算输入输出帧数量。 - 类型转换逻辑错误:
performConversion中你将inUserData强制转为AudioBufferList指针,但是你实际传入的是当前类实例的指针,类型完全不匹配。
修正方案
1. 修正输入回调逻辑
let conversionAudioController_Callback: AudioConverterComplexInputDataProc = {( inAudioConverter, ioNumberDataPackets, ioData, outDataPacketDescription, inUserData) -> OSStatus in guard let userData = inUserData else { ioNumberDataPackets.pointee = 0 return -1 } // 从userData中提取当前类实例,而非转换inAudioConverter let delegate = Unmanaged<你的当前类名>.fromOpaque(userData).takeUnretainedValue() return delegate.performConversion(ioNumberDataPackets: ioNumberDataPackets, ioData: ioData) }
2. 修正转换数据填充逻辑
func performConversion(ioNumberDataPackets: UnsafeMutablePointer<UInt32>, ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus { guard let inputBuf = inputBufferList?.mBuffers, let inputData = inputBuf.mData else { ioNumberDataPackets.pointee = 0 return -1 } // 单声道Int16格式下,每帧对应2字节、1个数据包 let availablePackets = inputBuf.mDataByteSize / 2 ioNumberDataPackets.pointee = min(ioNumberDataPackets.pointee, availablePackets) let ioListPtr = UnsafeMutableAudioBufferListPointer(ioData) ioListPtr[0].mNumberChannels = 1 ioListPtr[0].mDataByteSize = ioNumberDataPackets.pointee * 2 ioListPtr[0].mData = inputData return noErr }
3. 修正录音回调中的大小计算逻辑
func performRecording(_ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp: UnsafePointer<AudioTimeStamp>, inBufNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus { var err: OSStatus = noErr err = AudioUnitRender(audioUnit!, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData) guard err == noErr else { return err } inputBufferList = ioData.pointee // 48000转16000为3:1降采样,输出帧数为输入的1/3 let outputFrames = inNumberFrames / 3 let outputByteSize = outputFrames * 2 // 单声道Int16每帧占2字节 // 建议将outputBuffer预创建为类属性,避免实时线程频繁申请释放内存 let outputBuffer = UnsafeMutablePointer<CChar>.allocate(capacity: Int(outputByteSize)) defer { outputBuffer.deallocate() } var fillBufferList = AudioBufferList() fillBufferList.mNumberBuffers = 1 fillBufferList.mBuffers.mNumberChannels = 1 fillBufferList.mBuffers.mDataByteSize = outputByteSize fillBufferList.mBuffers.mData = UnsafeMutableRawPointer(outputBuffer) var ioOutputPackets: UInt32 = outputFrames let status = AudioConverterFillComplexBuffer( converter48To16!, conversionAudioController_Callback, Unmanaged.passUnretained(self).toOpaque(), &ioOutputPackets, &fillBufferList, nil ) if status != noErr { print("转换错误,错误码四字符标识:\(String(format: "%c%c%c%c", status >> 24, status >> 16, status >> 8, status))") } // fillBufferList中即为转换完成的16000采样率PCM数据 return err }
替代简化方案
如果不想处理AudioConverter的复杂回调,可以直接使用AVAudioConverter,API更简洁不易出错:
// 初始化时创建转换器 let inputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 48000, channels: 1, interleaved: false)! let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)! let audioConverter = AVAudioConverter(from: inputFormat, to: outputFormat)! // 录音回调中转换 let inputBuf = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: inNumberFrames)! inputBuf.frameLength = inNumberFrames memcpy(inputBuf.int16ChannelData!.pointee, ioData.pointee.mBuffers.mData, Int(inNumberFrames * 2)) let outputBuf = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: inNumberFrames / 3)! var convertError: NSError? audioConverter.convert(to: outputBuf, error: &convertError) { _, status in status.pointee = .haveData return inputBuf } // outputBuf中即为转换完成的16000采样率数据
注意事项
- 创建
AudioConverter时需确保CAStreamBasicDescription的所有字段填充完整,尤其是formatID要显式指定为kAudioFormatLinearPCM,避免构造遗漏导致转换器创建失败。 - 实时录音回调属于高优先级线程,不要在其中执行打印、内存申请释放、锁等待等耗时操作,避免音频卡顿。
内容的提问来源于stack exchange,提问作者Muhammad Faizan
相关产品推荐
相关产品推荐

