Swift中AudioUnit录制时AudioUnitRender参数错误问题求助
问题描述
开发Flutter应用时,在Swift原生模块使用AudioUnit实现音频录制功能,RenderCallback中调用AudioUnitRender出现参数错误:
1024 frames, 4 bytes/frame, expected 4096-byte buffer; ioData.mBuffers[0].mDataByteSize=2048; kAudio_ParamError from AU (0x10660f5e0): auou/vpio/appl, render err: -50 AudioUnitRender failed with status: -50 throwing -1 from AU (0x10660f5e0): auou/vpio/appl, render err: -1
已在初始化方法中将音频格式设置为2字节/帧(16位单声道),但仍出现上述问题,相关代码如下:
startRecording函数
func startRecording() { let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playAndRecord, options: [.allowBluetooth]) try audioSession.setMode(.voiceChat) try audioSession.setActive(true) } catch { print("Failed to set up audio session: \(error.localizedDescription)") } initAudioUnit() AudioOutputUnitStart(ioUnitUdp!) }
初始化AudioUnit
func initAudioUnit() { var status: OSStatus var ioUnitDescription = AudioComponentDescription( componentType: kAudioUnitType_Output, componentSubType: kAudioUnitSubType_VoiceProcessingIO, componentManufacturer: kAudioUnitManufacturer_Apple, componentFlags: 0, componentFlagsMask: 0 ) let inputComponent = AudioComponentFindNext(nil, &ioUnitDescription) status = AudioComponentInstanceNew(inputComponent!, &ioUnitUdp) var one: UInt32 = 1 // Enable IO for recording status = AudioUnitSetProperty( ioUnitUdp!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &one, UInt32(MemoryLayout<UInt32>.size)) var audioFormat = AudioStreamBasicDescription() memset(&audioFormat, 0, MemoryLayout<AudioStreamBasicDescription>.size) audioFormat.mSampleRate = 8000.0 audioFormat.mFormatID = kAudioFormatLinearPCM audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked audioFormat.mFramesPerPacket = 1 audioFormat.mChannelsPerFrame = 1 audioFormat.mBytesPerFrame = 2 audioFormat.mBytesPerPacket = 2 audioFormat.mBitsPerChannel = 16 status = AudioUnitSetProperty( ioUnitUdp!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size)) // Set input callback var callbackStruct1: AURenderCallbackStruct = AURenderCallbackStruct() callbackStruct1.inputProc = renderCallbackFunction callbackStruct1.inputProcRefCon = UnsafeMutableRawPointer( Unmanaged.passUnretained(self).toOpaque()) // self status = AudioUnitSetProperty( ioUnitUdp!, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct1, UInt32(MemoryLayout<AURenderCallbackStruct>.size)) var bypassVP: UInt32 = 0 status = AudioUnitSetProperty( ioUnitUdp!, kAUVoiceIOProperty_BypassVoiceProcessing, kAudioUnitScope_Global, kOutputBus, &bypassVP, UInt32(MemoryLayout<UInt32>.size)) var vpAGC: UInt32 = 0 status = AudioUnitSetProperty( ioUnitUdp!, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kOutputBus, &vpAGC, UInt32(MemoryLayout<UInt32>.size)) var quality: UInt32 = 127 status = AudioUnitSetProperty( ioUnitUdp!, kAUVoiceIOProperty_VoiceProcessingQuality, kAudioUnitScope_Global, kInputBus, &quality, UInt32(MemoryLayout<UInt32>.size)) var unmuteOutput: UInt32 = 0 status = AudioUnitSetProperty( ioUnitUdp!, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, kOutputBus, &unmuteOutput, UInt32(MemoryLayout<UInt32>.size)) // Initialise status = AudioUnitInitialize(ioUnitUdp!) }
RenderCallback
let renderCallbackFunction: AURenderCallback = { ( inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData ) -> OSStatus in let appDelegate = Unmanaged<AppDelegate>.fromOpaque(inRefCon).takeUnretainedValue() var bufferList = AudioBufferList( mNumberBuffers: 1, mBuffers: AudioBuffer( mNumberChannels: 1, mDataByteSize: inNumberFrames * UInt32(MemoryLayout<Int16>.size), mData: malloc(Int(inNumberFrames) * MemoryLayout<Int16>.size) ) ) guard let data = bufferList.mBuffers.mData else { return noErr } // Call AudioUnitRender to obtain recorded samples let status = AudioUnitRender( appDelegate.ioUnitUdp!, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList) if status != noErr { print("AudioUnitRender failed with status: \(status)") return status } print("data: \(bufferList)") // DO Stuff with data here free(data) return noErr }
ioUnitUdp是AppDelegate中的变量。
问题排查与修复
核心原因
错误日志显示AudioUnit实际期望4字节/帧(32位格式),但你设置的是2字节/帧(16位格式),导致缓冲区字节数不匹配。问题出在两个关键点:
- 音频格式设置的作用域错误:设置录制格式时,作用域和总线的组合不正确,导致自定义格式未生效。
- 硬编码缓冲区大小:RenderCallback中固定用16位格式计算缓冲区大小,未适配AudioUnit实际使用的格式。
修复步骤
步骤1:修正音频格式设置的作用域
修改initAudioUnit中设置kAudioUnitProperty_StreamFormat的代码,将作用域和总线调整为正确的组合(输入总线的输出端对应录制数据的格式):
status = AudioUnitSetProperty( ioUnitUdp!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
步骤2:动态适配AudioUnit实际格式
1. 添加属性存储实际格式
在AppDelegate中新增属性:
var actualAudioFormat = AudioStreamBasicDescription()
2. 初始化后查询实际格式
在initAudioUnit的AudioUnitInitialize调用后,添加查询代码:
// 查询输入总线的实际输出格式 var formatSize = UInt32(MemoryLayout<AudioStreamBasicDescription>.size) status = AudioUnitGetProperty( ioUnitUdp!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &actualAudioFormat, &formatSize)
3. 按实际格式创建缓冲区
修改RenderCallback,根据实际格式计算缓冲区大小:
let renderCallbackFunction: AURenderCallback = { ( inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData ) -> OSStatus in let appDelegate = Unmanaged<AppDelegate>.fromOpaque(inRefCon).takeUnretainedValue() let format = appDelegate.actualAudioFormat // 根据实际格式计算缓冲区大小 let bufferSize = inNumberFrames * format.mBytesPerFrame var bufferList = AudioBufferList( mNumberBuffers: 1, mBuffers: AudioBuffer( mNumberChannels: format.mChannelsPerFrame, mDataByteSize: bufferSize, mData: malloc(Int(bufferSize)) ) ) guard let data = bufferList.mBuffers.mData else { return noErr } // Call AudioUnitRender to obtain recorded samples let status = AudioUnitRender( appDelegate.ioUnitUdp!, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList) if status != noErr { print("AudioUnitRender failed with status: \(status)") free(data) // 错误时也要释放内存 return status } print("data: \(bufferList)") // 处理录制数据 free(data) return noErr }
步骤3:可选优化
如果修改后仍存在格式强制转换问题,可以尝试启用VoiceProcessing旁路(将bypassVP设为1),绕过系统语音处理后,自定义格式的兼容性会更好:
var bypassVP: UInt32 = 1 status = AudioUnitSetProperty( ioUnitUdp!, kAUVoiceIOProperty_BypassVoiceProcessing, kAudioUnitScope_Global, kOutputBus, &bypassVP, UInt32(MemoryLayout<UInt32>.size))
内容的提问来源于stack exchange,提问作者Takrem
相关产品推荐
相关产品推荐

