Skip to content

Commit 522a729

Browse files
committed
[Fix] Recover iOS audio after media-services reset
1 parent cd59311 commit 522a729

6 files changed

Lines changed: 340 additions & 15 deletions

File tree

modules/audio_device/audio_engine_device.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
360360
void OnInterruptionBegin() override;
361361
void OnInterruptionEnd(bool should_resume) override;
362362
void OnValidRouteChange() override;
363+
void OnMediaServicesReset() override;
363364
void OnCanPlayOrRecordChange(bool can_play_or_record) override;
364365
void OnChangedOutputVolume() override;
365366

modules/audio_device/audio_engine_device.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,16 @@
651651
}));
652652
}
653653

654+
void AudioEngineDevice::OnMediaServicesReset() {
655+
LOGI() << "OnMediaServicesReset";
656+
RTC_DCHECK(thread_);
657+
658+
// A media-services reset invalidates AVAudioEngine and its nodes even though
659+
// WebRTC still considers playout and recording started. ReconfigureEngine
660+
// rebuilds the graph and restores their prior state.
661+
ReconfigureEngine();
662+
}
663+
654664
void AudioEngineDevice::OnCanPlayOrRecordChange(bool can_play_or_record) {
655665
LOGI() << "OnCanPlayOrRecordChange";
656666
RTC_DCHECK(thread_);
@@ -1586,6 +1596,8 @@
15861596
thread_->PostTask(SafeTask(safety_, [this] {
15871597
RTC_DCHECK_RUN_ON(thread_);
15881598

1599+
// Snapshot on the device thread so stop and availability updates are
1600+
// ordered before or after the complete recovery transition.
15891601
EngineState current_state = this->engine_state_;
15901602

15911603
// Re-configure is only for device mode

sdk/objc/components/audio/RTCAudioSession.mm

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
@interface RTC_OBJC_TYPE (RTCAudioSession)
4242
() @property(nonatomic, readonly)
4343
std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
44+
45+
- (void)restoreAudioSessionAfterMediaServicesReset;
4446
@end
4547

4648
// This class needs to be thread-safe because it is accessed from many threads.
@@ -595,7 +597,7 @@ - (void)handleMediaServicesWereLost:(NSNotification *)notification {
595597

596598
- (void)handleMediaServicesWereReset:(NSNotification *)notification {
597599
RTCLog(@"Media services were reset.");
598-
[self updateAudioSessionAfterEvent];
600+
[self restoreAudioSessionAfterMediaServicesReset];
599601
[self notifyMediaServicesWereReset];
600602
}
601603

@@ -676,7 +678,18 @@ - (int)incrementActivationCount {
676678

677679
- (NSInteger)decrementActivationCount {
678680
RTCLog(@"Decrementing activation count.");
679-
return _activationCount.fetch_sub(1) - 1;
681+
// An unmatched deactivation used to make the count negative. A later valid
682+
// activation then still looked inactive during media-services recovery.
683+
int activationCount = _activationCount.load();
684+
while (activationCount > 0) {
685+
if (_activationCount.compare_exchange_weak(activationCount,
686+
activationCount - 1)) {
687+
return activationCount - 1;
688+
}
689+
}
690+
691+
RTCLogWarning(@"Ignoring unbalanced audio session deactivation.");
692+
return 0;
680693
}
681694

682695
- (int)webRTCSessionCount {
@@ -812,21 +825,90 @@ - (NSError *)configurationErrorWithDescription:(NSString *)description {
812825
}
813826

814827
- (void)updateAudioSessionAfterEvent {
815-
BOOL shouldActivate = self.activationCount > 0;
816-
AVAudioSessionSetActiveOptions options = shouldActivate ?
817-
0 :
818-
AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
828+
BOOL shouldActivate = NO;
829+
do {
830+
shouldActivate = self.activationCount > 0;
831+
AVAudioSessionSetActiveOptions options = shouldActivate ?
832+
0 :
833+
AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
834+
NSError *error = nil;
835+
if ([self.session setActive:shouldActivate
836+
withOptions:options
837+
error:&error]) {
838+
self.isActive = shouldActivate;
839+
RTCLog(@"Did set session active to %d", shouldActivate);
840+
} else {
841+
RTCLogError(@"Failed to set session active to %d. Error:%@",
842+
shouldActivate,
843+
error.localizedDescription);
844+
return;
845+
}
846+
847+
// An external activation or deactivation can arrive while setActive is in
848+
// progress. Reconcile once more when ownership changed during the call.
849+
} while ((self.activationCount > 0) != shouldActivate);
850+
}
851+
852+
- (void)restoreAudioSessionAfterMediaServicesReset {
853+
RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *configuration =
854+
[RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration];
855+
[self lockForConfiguration];
856+
857+
// The restarted media server no longer owns the previous session state even
858+
// when AVAudioSession still reports the cached values. Reapply each value
859+
// without comparing it to the getters first.
860+
self.isActive = NO;
819861
NSError *error = nil;
820-
if ([self.session setActive:shouldActivate
821-
withOptions:options
822-
error:&error]) {
823-
self.isActive = shouldActivate;
824-
RTCLogError(@"Did set session active to %d", shouldActivate);
825-
} else {
826-
RTCLogError(@"Failed to set session active to %d. Error:%@",
827-
shouldActivate,
862+
if (![self setCategory:configuration.category
863+
mode:configuration.mode
864+
options:configuration.categoryOptions
865+
error:&error]) {
866+
RTCLogError(@"Failed to restore category and mode after media-services "
867+
@"reset: %@",
828868
error.localizedDescription);
829869
}
870+
871+
error = nil;
872+
if (![self setPreferredSampleRate:configuration.sampleRate error:&error]) {
873+
RTCLogError(@"Failed to restore preferred sample rate after media-services "
874+
@"reset: %@",
875+
error.localizedDescription);
876+
}
877+
878+
error = nil;
879+
if (![self setPreferredIOBufferDuration:configuration.ioBufferDuration
880+
error:&error]) {
881+
RTCLogError(@"Failed to restore preferred I/O buffer duration after "
882+
@"media-services reset: %@",
883+
error.localizedDescription);
884+
}
885+
886+
// Restore the physical session without changing the activation ownership
887+
// count. A deactivation racing the reset therefore still wins normally.
888+
[self updateAudioSessionAfterEvent];
889+
890+
if (self.isActive &&
891+
[configuration.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
892+
error = nil;
893+
if (![self setPreferredInputNumberOfChannels:configuration
894+
.inputNumberOfChannels
895+
error:&error]) {
896+
RTCLogError(@"Failed to restore preferred input channels after "
897+
@"media-services reset: %@",
898+
error.localizedDescription);
899+
}
900+
901+
error = nil;
902+
if (![self setPreferredOutputNumberOfChannels:configuration
903+
.outputNumberOfChannels
904+
error:&error]) {
905+
RTCLogError(@"Failed to restore preferred output channels after "
906+
@"media-services reset: %@",
907+
error.localizedDescription);
908+
}
909+
}
910+
911+
[self unlockForConfiguration];
830912
}
831913

832914
- (void)updateCanPlayOrRecord {

sdk/objc/components/audio/RTCNativeAudioSessionDelegateAdapter.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ - (void)audioSessionMediaServerTerminated:
7272
}
7373

7474
- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
75+
// iOS sends this after its media-services process restarts. Existing audio
76+
// objects may remain alive while capture and playout stay silent until the
77+
// graph is rebuilt.
78+
_observer->OnMediaServicesReset();
7579
}
7680

7781
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)session

sdk/objc/native/src/audio/audio_session_observer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class AudioSessionObserver {
2727
// Called when audio route changes.
2828
virtual void OnValidRouteChange() = 0;
2929

30+
// Called after the media services process restarts.
31+
// Implementations can rebuild audio objects invalidated by the reset.
32+
virtual void OnMediaServicesReset() {}
33+
3034
// Called when the ability to play or record changes.
3135
virtual void OnCanPlayOrRecordChange(bool can_play_or_record) = 0;
3236

0 commit comments

Comments
 (0)