CallKit does not activate audio session with higher probability after upgrading to iOS 18.4.1

Hi,

We've noticed that this issue occurs more frequently after upgrading to iOS 18.4.1 and can result in one-way audio.

Our app uses CallKit with WebRTC to establish VoIP connections.

However, on iOS 18.4.1, CallKit no longer triggers:

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)

We're currently comparing the occurrence rate across different iOS versions to better understand the impact.

Could you please help analyze the root cause of this issue?

Our app uses CallKit with WebRTC to establish VoIP connections. However, on iOS 18.4.1, CallKit no longer triggers: func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)

We're currently comparing the occurrence rate across different iOS versions to better understand the impact.

Could you please help analyze the root cause of this issue?

I can't tell you what the exact issue issue is but what typically causes this is that the app itself activated the audio session itself. That can result in "one way" audio because the system ends up allowing playback activation but blocks recording (since that's what the system actually protects against). Having and active audio session means that CallKit cannot activate the audio session (since you can't activate an active audio session), which then means it doesn't call "didActivate".

That leads to here:

Our app uses CallKit with WebRTC to establish VoIP connections.

The biggest issue with using any kind of library that doesn't directly integrate CallKit is that these libraries integrate their own audio session management and activation. If timing means that CallKit activates first, then everything will actually still work fine. What actually happens is that their setActive call fails, but that doesn't actually matter since the session is already active. However, if/when the timing of activation changes even slightly, then the library ends up calling "setActive" first, creating exactly the problem you're seeing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer

Thank you for your response and insights.

We understand your point about AVAudioSession possibly being activated too early by third-party libraries like WebRTC. However, we would like to clarify that our app does not call AVAudioSession.setActive(true) before CallKit triggers didActivate, and we've ensured that WebRTC is initialized only after this delegate method is called.

We've also double-checked and confirmed that there is no early activation of the audio session before CallKit is expected to activate it. Despite this, on iOS 18.4.1, we’re seeing a statistically significant increase in the failure rate of didActivate, as shown in our metrics.

Given that our app version remains unchanged, and the issue correlates strongly with the iOS 18.4.1 system update, could you help us further investigate whether there might be behavior changes in AVAudioSession or CallKit that could explain this?

Any guidance or diagnostic suggestions would be greatly appreciated?

We've also double-checked and confirmed that there is no early activation of the audio session before CallKit is expected to activate it.

How did you make that determination? The problem here is that AVAudioSession does not provide great visibility into it's activation state, which makes "knowing" what happened trickier than it might seem.

More to the point, here are two key points in your original email that I want to expand on:

...result in one-way audio.

The way I understood this is that the receiver of the call can hear audio ("Playback") but cannot be heard ("Record") by the initiator of the call. In other words, your app DOES have an active audio session, the problem is that it either:

  1. Is failing to properly start it's own recording "process" (meaning, it could be capturing audio data but simply "isn't").

  2. It activated as a Playback session (not PlayAndRecord), so it cannot record.

However, on iOS 18.4.1, CallKit no longer triggers: func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)

This would happen for one of two reasons:

  1. Audio handling failed entirely.

  2. CallKit never activated the audio session.

This is clearly #2 and the proof of that is the same reason activation failed. The audio system is clearly "working" (otherwise you wouldn't be able to play...) and simplest reason CallKit would fail to activate your apps audio session is... that you ALREADY had a playback session active.

That context lead to here:

Given that our app version remains unchanged, and the issue correlates strongly with the iOS 18.4.1 system update, could you help us further investigate whether there might be behavior changes in AVAudioSession or CallKit that could explain this?

It terms of the systems role in this kind of issue, the basic question here is whether you reproduce the issue in Speakerbox, either directly or by modifying the sample in some straightforward way. If you can, then the system clearly has some role* in the issue. If you can't, then the issue isn't in CallKit.

*In the modification case, that role could simply be "the framework doesn't work when used like that".

One point I want to expand on is around this point:

...the issue correlates strongly with the iOS 18.4.1 system update

One thing I didn't explain in my previous post is that it is reasonably likely that changes in the system are in fact what caused the increase. The audio system is an extremely complex pile of code that's under very active development, so "something" is nearly always changing.

However, that doesn't mean you code isn't the problem. The complexity of the audio system also means that it can "hide" problems, so the fact your code used to "work" only means that it happened to not fail, not that it was actually correct. Case in point, your own data shows a failure rate of ~0.1% prior to 18.4. Assuming those represent the same underlying issue, then that says that an existing bug may simply have become more common, not that this is a new issue.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer I would like to follow up this issue. From our metics, looks like after upgrading to iOS 18.4.1,** the incoming call no audio issue is increased**. we did not observe outbound call increased. Here is the incoming call process in our app

  1. incoming call from push notification, app call reportNewIncomingCall
  2. after report incoming call success, our app will configure audio session
  3. our app will post the configureAudioSession to another thread
  4. reportNewIncomingCall callback
  5. configureAudioSession in another thread

(app will wait for the user press answer button, may take several seconds)

  1. user answer the call and AnswerCallAction will callback
  2. app will finish the signaling answering logic
  3. action.fulfill()
  4. callkit shall setActive Audio session
  5. didactivate audio session shall be triggerd, but it's not.
  6. our app detect no audio for 5 seconds
  7. try to setActive(YES) for workaround, sometimes it will success, sometimes it won't
  8. if the setActive(YES) failed, it will return "Session Activation failed" error. No more information indicate why it failed.

Could you please review the following processes for any potential issues?

  1. We are aware of a possible race condition when configuring the AudioSession in step 5 on another thread, which we plan to address. This could occur if the user answers the call quickly in step 6, potentially causing a conflict between the CallKit active audio session and step 5.
  2. However, based on user logs, we've observed that most users don't answer calls immediately. This suggests the race condition may not be the root cause, unless CallKit performs additional audio session processes after step 4. Could you confirm this?
  3. We've noticed from some user logs that the issue may arise when the app is in the background and awakened by an incoming call push notification.
  4. In some instances, once the issue occurs, it affects all subsequent incoming calls.
  5. Despite an increase in occurrences, the issue still has a low incidence rate of 0.2%. This leads us to suspect that the problem may be caused by a race condition somewhere in the process.

Your insights on these points would be greatly appreciated. Thank you very much for your assistance!

Hi @DTS Engineer,

Our new metrics indicate that the one-way issue caused by "audio session not activated" accounts for more than 50% of cases. Given this significant percentage, could you please revisit and reexamine this issue?

Thank you

Hi @DTS Engineer

Now we can reproduce this issue:

  1. When the issue occurs, incoming calls always have no audio (both ways). The "setActive(YES) for workaround" in our app CANNOT fix the issue.
  2. For outbound calls, there is no audio (both ways) at the beginning. The "setActive(YES) for workaround" in our app CAN fix the issue.
  3. The issue always occurs for subsequent calls, unless the app is restarted.
  4. "setActive(YES) for workaround" means our app will force AVAudioSession to setActive when it detects that CallKit did not activate the AVAudioSession.

The bug is likely Apple-related. Could you please investigate the console logs? Thank you again for your assistance.

Please find the console logs in https://drive.google.com/drive/folders/1Uo7BcS5nzmRPmFaKg7-5_X3s-Tk9cwvt?usp=drive_link

incoming call example:

file: console-incoming.log

18:44:22 start ring & report incoming call

default 18:44:22.512414+0800 Glip Provider <private> was asked to report a new incoming call with UUID: <private> update: <private>

18:44:22 audiomxd start report errors, no audio both ways

error 18:44:22.533627+0800 audiomxd AVAudioSessionXPCServer.mm:404 -[AVAudioSessionRemoteXPCClient createProxySession:reply:] failed due to session lookup failure for SessionID 0x0

18:44:28 our app try to active audio session for workaround, doesn't work

default 18:44:28.998028+0800 audiomxd AudioSessionServerImp.mm:957 { "action":"activate", "session":{"ID":"0x6b102","name":"Glip(1554)"}, "details":{"->":"entry"} }

outgoing call example:

file: console-outgoing.log

19:13:27 make call

default 19:13:27.738603+0800 callservicesd Dialing new call due to requested start call action: <private>

19:13:27 start report errors, same as incoming call, no audio both ways

error 19:13:27.744302+0800 audiomxd AVAudioSessionXPCServer.mm:404 -[AVAudioSessionRemoteXPCClient createProxySession:reply:] failed due to session lookup failure for SessionID 0x0

19:13:33 our app try to active audio session for workaround, audio recovery

default 19:13:33.423110+0800 audiomxd AudioSessionServerImp.mm:1056 { "action":"activate", "session":{"ID":"0x6b102","name":"Glip(1554)"}, "details":{"->":"exit"} }

Hi @DTS Engineer, We’re able to reproduce this issue on a specific iOS device. Could you please take a look at the comment from @rc-yorick_zheng?

Could you please review the following processes for any potential issues?

Yes. It's very simple. Your app should NEVER do this:

try to setActive(YES) for workaround

The CallKit audio session is NOT a standard audio session. It's a restricted audio session configuration that:

  • Has different, higher, session priority than any other audio session.

  • Is allowed to activate in the background IF that activation comes from the properly authorized daemon.

That means:

sometimes it won't

...in the standard case, activation will fail because you're not the proper authorization. However, that activation attempt can disrupt the system and interfere with other activations.

However...

sometimes it will succeed,

These APIs are inherently race condition prone, which means that activation could succeed because, for example:

  • Your app was entering the foreground and was able to activate a PlayAndRecord.

  • Timing issues inside the audio system meant that it allowed activation under circumstances it wouldn't.

However, no matter what, that activation will not flow properly within CallKit and it will cause other issues. Making this as explicit as possible:

The "setActive(YES) for workaround" in our app CAN fix the issue.

No, it can't. It's possible that it may mask the issue in some cases; however, it's also very likely to introduce failures somewhere else.

In any case, in terms of what the real problem is, the key issue is the "SessionID 0x0" here:

error 18:44:22.533627+0800 audiomxd AVAudioSessionXPCServer.mm:404 -[AVAudioSessionRemoteXPCClient createProxySession:reply:] failed due to session lookup failure for SessionID 0x0

That session ID is the global identifier callservicesd uses to "find" your app’s audio session so that it can manage and activate it. It's not explicitly set by your app because it's actually included in the configuration payload you configure (and modify) CXProvider with. I'm not sure how it became NULL, but if I had to guess, it was either a side effect of an out-of-order process initialization (creating CXProvider prior to main) or a side effect of a system-wide disruption that wasn't handled properly.

Related to that point, please note that I'd much prefer a full sysdiagnose log over a console log snippet, as it's possible I might have been able to say more about the underlying cause if I'd had the full log so I could see where the configuration was last set.

In any case, I'd expect that setting the configuration yourself would clear the issue, unless it's caused by a broader system-wide disruption that you cannot control. Note that the configuration itself doesn't have to change; you can just use your existing configuration.

The bug is likely Apple-related.

I'm not sure if the bug itself is our (it depends on what created the specific situation); however, we could do a better job of identifying the situation and/or self-correcting. Please file a bug on that point that includes full sysdiagnose data, then post the number back here.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer

Thank you so much for checking this.

Regarding "try to setActive(YES) for workaround", this only happen 5 seconds after our app detect Audio Session is not activated call started.

We agree the major the key issue is the "SessionID 0x0". I have created a feedback for this issue, and submit sysdiagnose/console logs in it. However, the issue happen at Apr 4, the sysdiagnose was generated at Apr 7. So We are not sure if it could contain the key data at Apr 4. Please help to check it.

The issue happen intermediately, and it's hard to reproduce. We can update the feedback again once it can be reproduced again.

feedback ticket:

FB19429215 (CallKit does not activate audio session with higher probability after upgrading to iOS 18.4.1)

Regarding "try to setActive(YES) for workaround", this only happens 5 seconds after our app detects Audio Session is not activated call started.

That doesn't change my answer. Directly activating the audio session will never be reliable and can disrupt CallKit's own ability to manage your audio session. It happens to not be the cause of the problem you're currently looking at, but it can and will cause problems that look EXACTLY the same.

Putting this another way, the problem with using setActive ISN'T that it "doesn't work", it's that it can SORT of work in a way that can mask and create other issues, both now and in the future.

We agree the major key issue is the "SessionID 0x0". I have created a feedback for this issue, and submitted sysdiagnose/console logs in it. However, the issue happened on Apr 4, the sysdiagnose was generated on Apr 7. So we are not sure if it could contain the key data on Apr 4.

I'll try and take a look later today; however, I've seen enough already to raise the issue with the team and start discussing possible fixes.

In any case, shifting to the workaround front, I suspect the issue is tied to app startup in some way, so your app is starting up in a "broken" state and staying that way (until force quit and relaunched). If you have any case where you know that the app was able to process calls and then stopped working, then that's an edge that would be worth investigating further (particularly if you're able to capture a sysdiagnose).

In terms of how your app can avoid/prevent this, here are my suggestions:

  1. PushKit and CallKit should both be configured in applicationDidFinishLaunching. The goal here is that you want CallKit to be fully configured before your PushKit delegate is called to deliver a VoIP push.

  2. I would add an additional call to set the configuration before you call "reportNewIncomingCall" on the first push you receive, as this should ensure that callservicesd has a valid session ID. Note the configuration doesn't need to change, so setting the configuration to the existing value will work fine.

  3. As a "backstop", you can try adding the same configuration "set" call into the code you're using to repair the audio failure. I don't think this will fix the current call, but it should ensure that the audio works going forward (ensure that you “self-repair").

Some additional comments on the choices here:

  • In my recommendations above, I've generally minimized the number of set configuration calls; however, in practice, I'm not sure that really matters all that much. It's possible/likely that there are already VoIP apps that are doing this for every call anyway, either for custom ringtones and/or iconTemplateImageData. This is also true of other call locations like, for example, before fulfilling the answer call action.

  • My #3 is basically a "hedge" against timing issues with #2. My intuition is that #2 will probably solve the issue, but if I'm wrong, then #3 should ensure that the issue is corrected, and the next call will work.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer

We attempted to capture a fresh sysdiagnose, but we were unable to reproduce the issue in our lab. We will continue our efforts to replicate the problem.

  • PushKit and CallKit should both be configured in applicationDidFinishLaunching. The goal here is that you want CallKit to be fully configured before your PushKit delegate is called to deliver a VoIP push.

  • I would add an additional call to set the configuration before you call "reportNewIncomingCall" on the first push you receive, as this should ensure that callservicesd has a valid session ID. Note the configuration doesn't need to change, so setting the configuration to the existing value will work fine.

I believe our app shall have done this correctly, as both incoming and outgoing calls functioned properly before the problem occurred. Our application did not restart or reinitialize during this time. The issue does not happen on the first call(push).

Regarding "backstop", Are you referring to the reconfiguration of the audio session? After the issue occurs, our application does attempt to reconfigure the audio session in subsequent calls. However, this approach has not been successful in resolving the problem.

I believe our app shall have done this correctly, as both incoming and outgoing calls functioned properly before the problem occurred. Our application did not restart or reinitialize during this time.

Are you absolutely certain of this?

If so, then do you have any idea/guess as to how much time was between the last working call and the first failed call?

My best guess on what caused this was that it was tied to odd app initialization conditions. Thinking about it more, I might see another way it could happen, but I think that would require:

  • Your app to have done a setConfiguration at EXACTLY the wrong moment.

  • Immediately suspend after that point.

  • Stay suspended for a significant period of time (hours?).

Regarding "backstop", Are you referring to the reconfiguration of the audio session?

No, or at least not exactly. Let me jump back to what I said here:

  1. I would add an additional call to set the configuration before you call "reportNewIncomingCall" on the first push you receive, as this should ensure that callservicesd has a valid session ID. Note the configuration doesn't need to change, so setting the configuration to the existing value will work fine.

On its own, I think that should basically prevent the bug from ever occurring. My basic assumption here is that whatever broke the audio session happened before the actual call report arrived, so the code above means that callservicesd should have a "working" audio reference to your app at the point it actually tries to activate your audio session. More to the point, if your audio session is actively broken at this point, then there's no reason to believe other audio APIs will work.

However, there are enough unknowns here that the "back up" here is that IF you detect that you're in the failure case, then I would:

  • Try your own audio session activation code. I'm not confident it will work, but you might get lucky sometimes.

  • ALSO set the configuration again. Again, I'm not confident that this will fix a failing call, but it should make it less likely that the "next" call will fail.

Again, the two suggestions above are both "backups" to my original #2. As I said:

"My intuition is that #2 will probably solve the issue, but if I'm wrong, then #3 should ensure that the issue is corrected, and the next call will work."

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Are you absolutely certain of this?

If so, then do you have any idea/guess as to how much time was between the last working call and the first failed call?

36 minutes, base on our log, the call at 16:58 have no problem. and the app went into background from 17:00 - 17:36(screen lock), until an incoming call push woke up the app. The issue began to occur on this call and subsequent calls.

"My intuition is that #2 will probably solve the issue, but if I'm wrong, then #3 should ensure that the issue is corrected, and the next call will work."

So, you recommended we set the configuration before CallKit is first used after the app starts? Will this overwrite the configuration of CallKit?

We may give this a try, but it's difficult to determine if it will work or not since it's hard to reproduce. We might need to implement this in our production environment and check the metrics to assess its effectiveness. I will discuss with team.

36 minutes, base on our log, the call at 16:58 have no problem. and the app went into background from 17:00 - 17:36(screen lock), until an incoming call push woke up the app.

OK. That time scale does fit my other theory, which makes my optimistic about the workaround I’m suggesting.

So, you recommended we set the configuration before CallKit is first used after the app starts?

At this point, my best advice would be to set the configuration before "every" call. There are probably cases where it would be reasonable to "skip" that, however:

Will this overwrite the configuration of CallKit?

Keep in mind that the "configuration" here is primarily just a set of settings that control a few key details of how CallKit handles your call. Many apps already set the configuration immediately before reporting calls—for example, to customize the ringtone based on the incoming caller or to prevent that particular call from listing in recents.

That leads to here:

We may give this a try, but it's difficult to determine if it will work or not since it's hard to reproduce. We might need to implement this in our production environment and check the metrics to assess its effectiveness. I will discuss it with the team.

Yes, that's what I'd expect. However, one thing I'd emphasize here is that the change I'm suggesting should be quite safe/nondisruptive. Even if it doesn't fix the bug (which I think it will), I don't think it will actually hurt/harm your app.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer

Just to provide an update: Our metrics show that iOS 18.6 is still maintaining a high one-way ratio.

BTW, since you've raised this issue to your team, is it possible to share the plan or indicate in which version the issue is expected to be resolved?

CallKit does not activate audio session with higher probability after upgrading to iOS 18.4.1
 
 
Q