Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS: Low audio volume when using Play and Record session category #88893

Closed
LeonardoDemartino opened this issue Feb 27, 2024 · 7 comments · Fixed by #89006
Closed

iOS: Low audio volume when using Play and Record session category #88893

LeonardoDemartino opened this issue Feb 27, 2024 · 7 comments · Fixed by #89006

Comments

@LeonardoDemartino
Copy link
Contributor

Tested versions

  • Reproducible: Any Godot 4 version

System information

iOS 16

Issue description

If you are creating an app for iOS that needs both to record and playback audio, you'll need to set "Play and Record" as the audio session category on Project Settings->Audio->General. However, for some reason, iOS decides that (I assume that in order to avoid interference) the audio playback should only be routed to the upper speaker and with ultra low volume (as you were in a phone call or something)

Similar issues I have found over the internet:
https://stackoverflow.com/questions/45155648/avaudiosessions-playandrecord-category-and-avaudiosessionmodemeasurement-are-in
https://forums.developer.apple.com/forums/thread/721535
https://stackoverflow.com/questions/22676367/audio-session-using-measurement-mode-causes-low-volume-no-sound-in-ios-7-1

As a suggestion, I think that by adding here:

[[AVAudioSession sharedInstance] setCategory:category withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];

withOptions: [AVAudioSessionCategoryOptionDefaultToSpeaker]
(https://developer.apple.com/documentation/avfaudio/avaudiosessioncategoryoptions/avaudiosessioncategoryoptiondefaulttospeaker)

Could fix the issue.

Also, if Godot sets by default to "AVAudioSessionModeMeasurement" (https://developer.apple.com/documentation/avfaudio/avaudiosessionmodemeasurement) another solution would be to simply:
[audioSession setMode: AVAudioSessionModeDefault]

Steps to reproduce

  1. Set up a Godot project with: Audio->Driver->Enable Input checked and Audio->General->Session Category->Play and Record (or use the attached MRP)
  2. Make the project to play sound.
  3. Run the app in an iOS device and hear how the volume is at ultra low levels. If you change the Session Category to, for example, Playback or Ambient, the sound plays at a correct volume but you can't obviously record anything from the microphone.

Minimal reproduction project (MRP)

mic_record.zip

@akien-mga
Copy link
Member

CC @bruvzg

@LeonardoDemartino
Copy link
Contributor Author

CC @georgwacker as well because he was the one who added Session Categories to iOS builds in the first place (thanks for adding this feature btw! 😃)
#81196

@bruvzg
Copy link
Member

bruvzg commented Feb 27, 2024

If I understand correctly AVAudioSessionCategoryOptionDefaultToSpeaker will send audio to the main speaker instead of headset if it's connected, so it should not be the default (or only) option, but can be exposed to be user configurable.

Also, if Godot sets by default to "AVAudioSessionModeMeasurement"

I do not think mode is set, so it should be AVAudioSessionModeDefault.

@LeonardoDemartino
Copy link
Contributor Author

@bruvzg Yes, from what I can see the mode is not set on Godot. Maybe it is by default on AVAudioSessionModeMeasurement somehow? Strange...
And you are right about AVAudioSessionCategoryOptionDefaultToSpeaker not taking into account a future headset/airpods being connected.

I'm curious about how other apps on iOS handle this, for example WhatsApp switches to "Play And Record" / "Record" while recording a voice message? And switchs it back to "Playback" after sending it? This is interesting and I'm quite lost, because it seems that the only solution would be to have a function to be called in runtime to dinamically switch between session categories.

I would love to find a more general solution since this will over complicate how Godot mic input/playback should be handled on iOS.

By the way, on the app I'm working on we are super careful to not playing audio while the user is recording. This works fantastic on Android, but iOS is somehow trying fix an audio feedback loop that we don't actually have.

@georgwacker
Copy link
Contributor

Looks like AVAudioSessionCategoryOptionDefaultToSpeaker only works when the category is set to PlayAndRecord.

The speaker option could be added along side the Mix with others options, but we would need to combine the options.

Currently, the variant of setCategory without the AVAudioSession.Mode is used, so the Mode should be default.
If needed, we could use the other variant of that function and expose all the Mode options to the user.

Here are some infos about Responding to audio route changes.

@LeonardoDemartino
Copy link
Contributor Author

LeonardoDemartino commented Feb 28, 2024

So I think I have found something interesting. In one of our apps published on the App Store we both record and play sounds. It is not made with Godot, it is pure C++ and SDL.

The interesting part is that on initialization, SDL always sets the mode to AVAudioSessionCategoryOptionDefaultToSpeaker if Play And Record is selected.
https://github.com/libsdl-org/SDL/blob/e03746b25f485fdf8637cbcb3126dc2c52bd32a7/src/audio/coreaudio/SDL_coreaudio.m#L422

And the app routes correctly the audio output if you plug a headset or use AirPods. And SDL don't have a routeChangeNotification observer.

I think this should be the way to go for Godot as well.

@LeonardoDemartino
Copy link
Contributor Author

Another interesting thing:
https://developer.apple.com/library/archive/qa/qa1754/_index.html
"When using AVAudioSessionCategoryOptionDefaultToSpeaker, user gestures will be honored. For example, plugging in a headset will cause the route to change to headset mic/headphones and unplugging the headset will cause the route to change to built-in mic/speaker (as opposed to built-in mic/receiver) when this override has been set."

It says something completely contradictory to the latest official documentation of AVAudioSessionCategoryOptionDefaultToSpeaker but matches what's actually happening in the device.

This seems to be a miswording in the Apple documentation.

Setting AVAudioSessionCategoryOptionDefaultToSpeaker will fix this issue for Godot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants