Surface audio unit errors.

With this change, we catch audio unit start errors and pipe them to the
audio session. The audio session notifies its delegate, which can then
take appropriate action based on the error code.
The signal follows the same path as the playout glitch detection.

Bug: webrtc:13119
Change-Id: I8c9f9d2a1e3457447d0ce61ad197f7e1c6392837
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230240
Commit-Queue: Peter Hanspers <peterhanspers@webrtc.org>
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Reviewed-by: Xavier Lepaul‎ <xalep@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34862}
diff --git a/sdk/objc/components/audio/RTCAudioSession+Private.h b/sdk/objc/components/audio/RTCAudioSession+Private.h
index 8ee4fdd..4f5107f 100644
--- a/sdk/objc/components/audio/RTCAudioSession+Private.h
+++ b/sdk/objc/components/audio/RTCAudioSession+Private.h
@@ -73,6 +73,12 @@
 /** Returns a configuration error with the given description. */
 - (NSError *)configurationErrorWithDescription:(NSString *)description;
 
+/** Notifies the reciever that a playout glitch was detected. */
+- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches;
+
+/** Notifies the reciever that there was an error when starting an audio unit. */
+- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error;
+
 // Properties and methods for tests.
 - (void)notifyDidBeginInterruption;
 - (void)notifyDidEndInterruptionWithShouldResumeSession:(BOOL)shouldResumeSession;
@@ -83,7 +89,6 @@
 - (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord;
 - (void)notifyDidStartPlayOrRecord;
 - (void)notifyDidStopPlayOrRecord;
-- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches;
 
 @end
 
diff --git a/sdk/objc/components/audio/RTCAudioSession.h b/sdk/objc/components/audio/RTCAudioSession.h
index 5881155..3b83b27 100644
--- a/sdk/objc/components/audio/RTCAudioSession.h
+++ b/sdk/objc/components/audio/RTCAudioSession.h
@@ -99,6 +99,9 @@
     failedToSetActive:(BOOL)active
                 error:(NSError *)error;
 
+- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
+    audioUnitStartFailedWithError:(NSError *)error;
+
 @end
 
 /** This is a protocol used to inform RTCAudioSession when the audio session
diff --git a/sdk/objc/components/audio/RTCAudioSession.mm b/sdk/objc/components/audio/RTCAudioSession.mm
index 057f62c..5031e15 100644
--- a/sdk/objc/components/audio/RTCAudioSession.mm
+++ b/sdk/objc/components/audio/RTCAudioSession.mm
@@ -794,6 +794,18 @@
   }
 }
 
+- (void)notifyAudioUnitStartFailedWithError:(OSStatus)error {
+  for (auto delegate : self.delegates) {
+    SEL sel = @selector(audioSession:audioUnitStartFailedWithError:);
+    if ([delegate respondsToSelector:sel]) {
+      [delegate audioSession:self
+          audioUnitStartFailedWithError:[NSError errorWithDomain:kRTCAudioSessionErrorDomain
+                                                            code:error
+                                                        userInfo:nil]];
+    }
+  }
+}
+
 - (void)notifyDidBeginInterruption {
   for (auto delegate : self.delegates) {
     SEL sel = @selector(audioSessionDidBeginInterruption:);
diff --git a/sdk/objc/native/src/audio/audio_device_ios.mm b/sdk/objc/native/src/audio/audio_device_ios.mm
index e3020ec..3ec7d0b 100644
--- a/sdk/objc/native/src/audio/audio_device_ios.mm
+++ b/sdk/objc/native/src/audio/audio_device_ios.mm
@@ -235,8 +235,11 @@
     fine_audio_buffer_->ResetPlayout();
   }
   if (!recording_ && audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
-    if (!audio_unit_->Start()) {
-      RTCLogError(@"StartPlayout failed to start audio unit.");
+    OSStatus result = audio_unit_->Start();
+    if (result != noErr) {
+      RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
+      [session notifyAudioUnitStartFailedWithError:result];
+      RTCLogError(@"StartPlayout failed to start audio unit, reason %d", result);
       return -1;
     }
     RTC_LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
@@ -288,8 +291,11 @@
     fine_audio_buffer_->ResetRecord();
   }
   if (!playing_ && audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
-    if (!audio_unit_->Start()) {
-      RTCLogError(@"StartRecording failed to start audio unit.");
+    OSStatus result = audio_unit_->Start();
+    if (result != noErr) {
+      RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
+      [session notifyAudioUnitStartFailedWithError:result];
+      RTCLogError(@"StartRecording failed to start audio unit, reason %d", result);
       return -1;
     }
     RTC_LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
@@ -620,9 +626,16 @@
   }
 
   // Restart the audio unit if it was already running.
-  if (restart_audio_unit && !audio_unit_->Start()) {
-    RTCLogError(@"Failed to start audio unit with sample rate: %f", session_sample_rate);
-    return;
+  if (restart_audio_unit) {
+    OSStatus result = audio_unit_->Start();
+    if (result != noErr) {
+      RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
+      [session notifyAudioUnitStartFailedWithError:result];
+      RTCLogError(@"Failed to start audio unit with sample rate: %f, reason %d",
+                  session_sample_rate,
+                  result);
+      return;
+    }
   }
   RTCLog(@"Successfully handled sample rate change.");
 }
@@ -801,8 +814,10 @@
     // Log session settings before trying to start audio streaming.
     RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
     RTCLog(@"%@", session);
-    if (!audio_unit_->Start()) {
-      RTCLogError(@"Failed to start audio unit.");
+    OSStatus result = audio_unit_->Start();
+    if (result != noErr) {
+      [session notifyAudioUnitStartFailedWithError:result];
+      RTCLogError(@"Failed to start audio unit, reason %d", result);
       return;
     }
   }
diff --git a/sdk/objc/native/src/audio/voice_processing_audio_unit.h b/sdk/objc/native/src/audio/voice_processing_audio_unit.h
index 72e29c0..ed9dd98 100644
--- a/sdk/objc/native/src/audio/voice_processing_audio_unit.h
+++ b/sdk/objc/native/src/audio/voice_processing_audio_unit.h
@@ -78,7 +78,7 @@
   bool Initialize(Float64 sample_rate);
 
   // Starts the underlying audio unit.
-  bool Start();
+  OSStatus Start();
 
   // Stops the underlying audio unit.
   bool Stop();
diff --git a/sdk/objc/native/src/audio/voice_processing_audio_unit.mm b/sdk/objc/native/src/audio/voice_processing_audio_unit.mm
index 2325b2e..661022f 100644
--- a/sdk/objc/native/src/audio/voice_processing_audio_unit.mm
+++ b/sdk/objc/native/src/audio/voice_processing_audio_unit.mm
@@ -332,19 +332,19 @@
   return true;
 }
 
-bool VoiceProcessingAudioUnit::Start() {
+OSStatus VoiceProcessingAudioUnit::Start() {
   RTC_DCHECK_GE(state_, kUninitialized);
   RTCLog(@"Starting audio unit.");
 
   OSStatus result = AudioOutputUnitStart(vpio_unit_);
   if (result != noErr) {
     RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result);
-    return false;
+    return result;
   } else {
     RTCLog(@"Started audio unit");
   }
   state_ = kStarted;
-  return true;
+  return noErr;
 }
 
 bool VoiceProcessingAudioUnit::Stop() {