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

iOS使用AudioUnit/AudioConverterRef实现48K转16K采样率报错求助

问题解决方案

核心错误原因

  1. 回调参数转换错误:你在conversionAudioController_Callback中错误将inAudioConverter转换器实例当做自定义对象转换,实际上传入的inUserData才是你注入的当前类实例指针,这是直接导致EXC_BAD_ACCESS崩溃的原因。
  2. 输入输出大小不匹配:错误码1768846202转四字符码为insz,对应kAudioConverterErr_InvalidInputSize,原因是你固定写死输出buffer大小684、输出包数384,没有按照48000转16000的3:1降采样比例动态计算输入输出帧数量。
  3. 类型转换逻辑错误: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采样率数据

注意事项

  1. 创建AudioConverter时需确保CAStreamBasicDescription的所有字段填充完整,尤其是formatID要显式指定为kAudioFormatLinearPCM,避免构造遗漏导致转换器创建失败。
  2. 实时录音回调属于高优先级线程,不要在其中执行打印、内存申请释放、锁等待等耗时操作,避免音频卡顿。

内容的提问来源于stack exchange,提问作者Muhammad Faizan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.18 21:24:05