AVAudioSession .defaultToSpeaker changes mic input
Asked Answered
U

1

7

I have an app taps the microphone and also play sounds depending on mic input(don't have to be simultaneously tho) This code below works. But one problem is the output plays on the small top speaker and not the bottom real loud speakers. I could solve this problem strangely by putting the 3 lines below just before the player starts, Then I can hear the sound on speakers. But then the microphone stops listening! Even after the player stops playing. Basically mic does not like when it is

.defaultToSpeaker

Any idea?

Here also documented what I am trying to do is correct:

https://developer.apple.com/documentation/avfoundation/avaudiosession/categoryoptions/1616462-defaulttospeaker

UPDATE: I minimized the problem. No Player just mic. Code below, mic does not "work" when it is ".defaultToSpeaker". After some debugging I realized that defaultToSpeaker switches the mic from "bottom" to "front". And

 try preferredPort.setPreferredDataSource(source)

Cant seem to change it to bottom again. (I can provide code for this) And when category is defaultToSpeaker apperantly the tap buffer framelength is 4800 and not 4410. This difference seem causes trouble in my code because I need exactly 44100. So mic is actually working, but later in code it fails to do its job due to different SR. Below code can explain more.

 func tapMicrophone() {
    try? AVAudioSession.sharedInstance().setActive(false)
    try? AVAudioSession.sharedInstance().setCategory(.playAndRecord,  options: [.defaultToSpeaker])
    //setBottomMic()
    try? AVAudioSession.sharedInstance().setActive(true)

    //tracker.start()
    let input = engine.inputNode
    let inputFormat = input.outputFormat(forBus: 0)
    let sampleRate = Double(11025)
    let outputFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: sampleRate, channels: 1, interleaved: true)!
    let converter = AVAudioConverter(from: inputFormat, to: outputFormat)!
    let inputBufferSize = 44100 //  100ms of 44.1K = 4410 samples.
    let sampleRateRatio = 44100 / sampleRate

    input.installTap(onBus: 0, bufferSize: AVAudioFrameCount(inputBufferSize), format: inputFormat) {
        buffer, time in
        var error: NSError? = nil
        let capacity = Int(Double(buffer.frameCapacity) / sampleRateRatio)
        let bufferPCM16 = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(capacity))!
        converter.convert(to: bufferPCM16, error: &error) { inNumPackets, outStatus in
            outStatus.pointee = AVAudioConverterInputStatus.haveData
            return buffer
        }
    }

    engine.prepare()
    try! engine.start()

}

In this case I seem to have 2 options. Either solve problem on mic level, if possible make this code work with ".defaultToSpeaker". Or don't use category .playandrecord But switch between .playback and .record when mic is not needed. This didn't seem to be easy too, since it requires a lot of starting/stopping of all audio, which is necessary for activate and deactive AVAudioSession. But if this is the way to go I can provide more code.

Uchish answered 29/4, 2019 at 20:19 Comment(16)
@matt I just printed the preferred input and I see "selectedDataSource = Bottom" This was not a concious decision though. I just need to be able to listen to environmental sounds. Does this effect my problem?Uchish
andonly position available here is back or front. I dont see bottom?Uchish
I actually do not need the microphone while the sound is playing. But I am having a lot of difficulty changing back and forth AVAudioSession category, because I have to deactivate and activate the session again when I change the route. Which is slow and does not work half of the time. So i was trying find a setup that I set once in beginning and use forever. If you have a good suggestion of enabling disabling microphone smoothly during playback. That also works for meUchish
@matt I just set it to use the front mic (it was bottom) I started the app and mic still does not work when I set to ".defaultToSpeaker"Uchish
@matt because I see that "(AVAudioSession.sharedInstance().preferredInput)" is nil. So I couldnt set it properly..Uchish
Can you try avaudiosession APIs in this order 1) .setCategory with category:PlaynRecord, mode=voicechat, options:defaulttospeaker 2) .overrideOutputAudioPort with port=speaker 3) .setActive with true . Then call your input.installTap and let me know if it worksCampus
@Campus thx. I tried your suggestion. Mic don't work at all. Same problem.Uchish
I'm a bit rusty, are you sure you need to deactivate/activate the audio session when you change categories?Kannada
@Rhythmic Fistman When I dont use activate/deactive. It seems to switch faster. But has no effect on the problemUchish
Try this gist.github.com/manishganvir/bd4d5a8ebb0eef0fed9612a2c9e74745 to change the data source to bottom micCampus
@Campus Tnx. I tried. I confirm that mic has changed to bottom. But odd enough longBuffer.frameLength is still always 48000! thus same problem as I described in post. Do you have an explanation for 48000?Uchish
So it your original problem gone(mic not working when speaker is selected) ? It’s only the sample rate issue now is it?Campus
@Campus this was the original problem. I either cant listen on bottom speakers Or when I can mic dont work properly.Uchish
So what’s the behavior now? Still same issue?Campus
behaviour is, when it is ".defaultToSpeaker" my method runInference() receives frame length of 48000. So inference never runs.Uchish
I see difference comes from "buffer.frameCapacity" in the tap. This changes depends on categoryUchish
U
9

Seems like I found a solution. Which is actually very simple. When AVAudioSession category is .defaultToSpeaker (Or overrideOutputAudioPort) apparently the tap input buffer framelength changes to 4800 from 4410.

This strangely happens regardless of which microphone is used. So using

AVAudioSession.sharedInstance().setInputDataSource(datasource);

Does not help.

This difference seems to cause problems later in my code. So mic was actually working, but later in code it was failing to do its job due to a different framelength.

Solution/workaround was I basically hardcoded the framelength in the tap. Since I use a converter I don't expect this to be a problem. This means I can set ".defaultToSpeaker" And mic is still working as expected.

capacity = 4410 (DUH!)

Maybe there are different/better ways to approach this problem. So feel free to add your answer if so.

Uchish answered 3/5, 2019 at 22:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.