Choose specific input channel as a mono input from external USB device in AVAudioSession/AVAudioEngine

I'm working on an audio recording app which uses an external USB Audio interface (e.g, Focusrite Scarlett Solo) connected to an iPhone.

When I run AVAudioSession.sharedInstance().currentRoute.inputs it returns the interface correctly.

1 element
  - 0 : <AVAudioSessionPortDescription: 0x28307c650, type = USBAudio; name = Scarlett Solo USB; UID = AppleUSBAudioEngine:Focusrite:Scarlett Solo USB:130000:1,2; selectedDataSource = (null)>

Channels are returned correctly as well.

po AVAudioSession.sharedInstance().currentRoute.inputs.first?.channels
▿ Optional<Array<AVAudioSessionChannelDescription>>
  ▿ some : 2 elements
    - 0 : <AVAudioSessionChannelDescription: 0x283070b60, name = Scarlett Solo USB 1; label = 4294967295 (0xffffffff); number = 1; port UID = AppleUSBAudioEngine:Focusrite:Scarlett Solo USB:130000:1,2>
    - 1 : <AVAudioSessionChannelDescription: 0x283070b70, name = Scarlett Solo USB 2; label = 4294967295 (0xffffffff); number = 2; port UID = AppleUSBAudioEngine:Focusrite:Scarlett Solo USB:130000:1,2>

When I connect the inputNode to mainMixerNode in AVAudioEngine it uses multi-channel input so the Line/Instrument input is on the right channel and Microphone input is on the left.

How can I make it so that I use only the 2nd Channel (guitar) as a mono to be played back in both speakers?

I've been looking through some docs and discussions but could not find the answer.

I tried changing channels to 1 in audio format but as expected it plays the first channel in mono but I can't select 2nd channel to be played instead.

let input = engine.inputNode
let inputFormat = input.inputFormat(forBus: 0)
        
let preferredFormat = AVAudioFormat(
    commonFormat: inputFormat.commonFormat,
    sampleRate: inputFormat.sampleRate,
    channels: 1,
    interleaved: false
)!

engine.connect(input, to: engine.mainMixerNode, format: preferredFormat)

Replies

Have you found a solution for this issue? I'm facing this problem on macOS, adjusting channelMap did not help as well.

  • Not yet unfortunately but I’m going to have a look soon and if I find out something will post here as well. Today I’ve tested with a 20 input audio interface and Garage Band iOS version and it works pretty well so I think there should be a solution here and hope I’ll find one. Pretty strange that AVAudioSession doesn’t let me select the channel.

Add a Comment

I had the same question asked on stack overflow as well and received a working answer there. https://stackoverflow.com/questions/75684026/choose-specific-input-channel-as-a-mono-input-from-usb-device-in-avaudiosession/76079369#76079369

It seems to be a pretty simple one by setting channel map on auAudioUnit of the input node.

engine.inputNode.auAudioUnit.channelMap = [1, 1]

1 is the channel 1 in this case. If you need to use channel 0 it would be [0, 0] and same would apply for other channels.

Unfortunately solution I've mentioned seemed to be working at the first glance but later I've discovered pan wasn't working since it was lowering the volume of 2nd channel and since it was streaming in both speakers value of below 0 was lowering the overall volume resulting in silence at -1. Also, tested with more than 2 channel input interface and it wasn't streaming 3rd and above channels. I believe channel map is not what we want here.

This is how I fixed panning initially but then noticed it was passing through all the inputs as mono. I've just tested it in an empty project in app delegate and posting whole code that you could just run and see in action.

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    let engine = AVAudioEngine()
    var inputNode: AVAudioInputNode { engine.inputNode }
    var inputFormat: AVAudioFormat { inputNode.inputFormat(forBus: 0) }
    var outputNode: AVAudioOutputNode { engine.outputNode }
    var outputFormat: AVAudioFormat { outputNode.outputFormat(forBus: 0) }
    var mainMixerNode: AVAudioMixerNode { engine.mainMixerNode }
    
    let mixerNode: AVAudioMixerNode = .init()


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        
        engine.attach(mixerNode)
        
        let layoutTag: AudioChannelLayoutTag = kAudioChannelLayoutTag_Mono
        let layout = AVAudioChannelLayout(layoutTag: layoutTag)!
        let monoFormat: AVAudioFormat = .init(standardFormatWithSampleRate: inputFormat.sampleRate, channelLayout: layout)
       
        engine.connect(inputNode, to: mixerNode, format: inputFormat)
        engine.connect(mixerNode, to: mainMixerNode, format: monoFormat)
        
        try! engine.start()
        
        mixerNode.pan = -0.4
        
        return true
    }
}

Also, it didn't work when I tested with 10 input channel audio interface. It was only streaming first two signals. I've tested the same audio interface with Garage Band and it works there. Wonder how Apple does it and the same time doesn't provide us with a straightforward interface to do so. Ideally it should be possible to switch from AVAudioSession but it doesn't have that feature. I've looked through all the docs of AVAudioSession, AVAudioEngine and whatnot but couldn't find anything there.