|
41 | 41 | @interface RTC_OBJC_TYPE (RTCAudioSession) |
42 | 42 | () @property(nonatomic, readonly) |
43 | 43 | std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates; |
| 44 | + |
| 45 | +- (void)restoreAudioSessionAfterMediaServicesReset; |
44 | 46 | @end |
45 | 47 |
|
46 | 48 | // This class needs to be thread-safe because it is accessed from many threads. |
@@ -595,7 +597,7 @@ - (void)handleMediaServicesWereLost:(NSNotification *)notification { |
595 | 597 |
|
596 | 598 | - (void)handleMediaServicesWereReset:(NSNotification *)notification { |
597 | 599 | RTCLog(@"Media services were reset."); |
598 | | - [self updateAudioSessionAfterEvent]; |
| 600 | + [self restoreAudioSessionAfterMediaServicesReset]; |
599 | 601 | [self notifyMediaServicesWereReset]; |
600 | 602 | } |
601 | 603 |
|
@@ -676,7 +678,18 @@ - (int)incrementActivationCount { |
676 | 678 |
|
677 | 679 | - (NSInteger)decrementActivationCount { |
678 | 680 | 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; |
680 | 693 | } |
681 | 694 |
|
682 | 695 | - (int)webRTCSessionCount { |
@@ -812,21 +825,90 @@ - (NSError *)configurationErrorWithDescription:(NSString *)description { |
812 | 825 | } |
813 | 826 |
|
814 | 827 | - (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; |
819 | 861 | 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: %@", |
828 | 868 | error.localizedDescription); |
829 | 869 | } |
| 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]; |
830 | 912 | } |
831 | 913 |
|
832 | 914 | - (void)updateCanPlayOrRecord { |
|
0 commit comments