You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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位格式),导致缓冲区字节数不匹配。问题出在两个关键点:

  1. 音频格式设置的作用域错误:设置录制格式时,作用域和总线的组合不正确,导致自定义格式未生效。
  2. 硬编码缓冲区大小: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:35:53