在AVAudioSession/AVAudioEngine中指定USB音频设备的单声道输入
我正在开发一款录音应用,使用连接到iPhone的外置USB音频接口(如Focusrite Scarlett Solo)。AVAudioSession.sharedInstance().currentRoute.inputs能正确识别该接口及通道信息,但在AVAudioEngine中将inputNode连接到mainMixerNode时,默认是多声道输入:线路/乐器输入在右声道,麦克风输入在左声道。我需要仅将第2通道(吉他输入)作为单声道信号,在两个扬声器中播放。
试过以下方法但存在问题:
- 将音频格式通道数改为1:只能获取第一通道的单声道信号,无法选择第2通道
- 使用通道映射代码:未达到预期效果
- 附加自定义
mixerNode:解决了声道问题,但测试10通道音频接口时仅能传输前两个信号,而GarageBand可正常使用该接口,疑惑苹果的实现方式,且AVAudioSession未提供相关便捷功能。
1. 正确选择指定输入通道并转为单声道
注意:Core Audio中通道索引从0开始,所以第2通道对应索引1。以下两种方法可以实现需求:
方法一:用AVAudioMixerNode配置通道映射
通过自定义混音节点的通道映射规则,直接指定保留目标通道:
let engine = AVAudioEngine() let inputNode = engine.inputNode let inputFormat = inputNode.inputFormat(forBus: 0) // 创建自定义混音节点 let monoMixer = AVAudioMixerNode() engine.attach(monoMixer) // 定义单声道输出格式 let monoFormat = AVAudioFormat(commonFormat: inputFormat.commonFormat, sampleRate: inputFormat.sampleRate, channels: 1, interleaved: false)! // 按原格式连接输入节点到自定义混音,再用单声道格式连接到主混音 engine.connect(inputNode, to: monoMixer, format: inputFormat) engine.connect(monoMixer, to: engine.mainMixerNode, format: monoFormat) // 关键:设置输入通道映射,仅保留第2通道(索引1) if let auMixer = monoMixer.auAudioUnit as? AVAudioUnitMixer { // 数组元素为输入通道索引,映射到输出的0号通道 auMixer.inputChannelMap = [1] } do { try engine.start() } catch { print("引擎启动失败:\(error)") }
方法二:在输入回调中直接提取目标通道数据
这种方式更灵活,适合复杂多通道场景:
let engine = AVAudioEngine() let inputNode = engine.inputNode let inputFormat = inputNode.inputFormat(forBus: 0) // 给输入节点安装处理回调 inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { buffer, time in // 获取第2通道的原始数据 guard let channelData = buffer.floatChannelData else { return } let targetChannelData = channelData[1] // 创建单声道buffer let monoBuffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(commonFormat: inputFormat.commonFormat, sampleRate: inputFormat.sampleRate, channels: 1, interleaved: false)!, frameCapacity: buffer.frameLength)! monoBuffer.frameLength = buffer.frameLength // 把目标通道数据复制到单声道buffer memcpy(monoBuffer.floatChannelData?[0], targetChannelData, Int(buffer.frameLength) * MemoryLayout<Float>.stride) // 将单声道数据传给主混音节点 engine.mainMixerNode.render(monoBuffer, at: time) } do { try engine.start() } catch { print("引擎启动失败:\(error)") }
2. 解决多通道接口仅能使用前两通道的问题
对于10通道这类多通道USB接口,AVAudioEngine默认可能只暴露前两个通道,需要手动配置会话并结合底层API解锁全部通道:
步骤1:配置AVAudioSession支持多通道
let session = AVAudioSession.sharedInstance() do { // 设置会话类别为播放录制,开启多通道支持 try session.setCategory(.playAndRecord, mode: .default, options: [.mixWithOthers]) // 强制指定输入输出通道数 try session.setPreferredInputNumberOfChannels(10) try session.setPreferredOutputNumberOfChannels(10) try session.activate() } catch { print("会话配置失败:\(error)") }
步骤2:通过底层AudioUnit获取全部通道
AVAudioEngine的高层封装限制了多通道灵活性,苹果官方应用(如GarageBand)会直接用底层Core Audio API。你可以通过输入节点的底层AudioUnit配置通道数:
if let inputAU = inputNode.auAudioUnit { var inputChannels = UInt32(10) let status = AudioUnitSetProperty(inputAU, kAudioUnitProperty_ChannelCount, kAudioUnitScope_Input, 0, &inputChannels, UInt32(MemoryLayout<UInt32>.size)) if status != noErr { print("设置输入通道数失败:\(status)") } }
关于苹果的实现逻辑
GarageBand这类官方应用能完全利用多通道接口,是因为它们直接调用Core Audio的底层HAL(硬件抽象层)API,绕过了AVAudioEngine的高层封装限制。AVAudioEngine为了简化开发做了很多妥协,在多通道场景下灵活性不足,如果需要完全兼容复杂的多通道USB接口,建议结合底层Core Audio API实现。
内容的提问来源于stack exchange,提问作者Kakhi Kiknadze

