14

I'm trying to redirect audio to speakers in the AppRTC iOS example.

I tried:

AVAudioSession* session = [AVAudioSession sharedInstance];

//error handling
BOOL success;
NSError* error;

//set the audioSession category. 
//Needs to be Record or PlayAndRecord to use audioRouteOverride:  

success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                         error:&error];

if (!success)  NSLog(@"AVAudioSession error setting category:%@",error);

//set the audioSession override
success = [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
                                      error:&error];
if (!success)  NSLog(@"AVAudioSession error overrideOutputAudioPort:%@",error);

//activate the audio session
success = [session setActive:YES error:&error];
if (!success) NSLog(@"AVAudioSession error activating: %@",error);
else NSLog(@"audioSession active");

There are no errors, but it doesn't work. How can I fix this?

Alon Gubkin
  • 56,458
  • 54
  • 195
  • 288

6 Answers6

14

I solved it by the solution. Just listen AVAudioSessionRouteChangeNotification

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didSessionRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];

And using the didSessionRouteChange selector as below:

- (void)didSessionRouteChange:(NSNotification *)notification
{
  NSDictionary *interuptionDict = notification.userInfo;
  NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

  switch (routeChangeReason) {
      case AVAudioSessionRouteChangeReasonCategoryChange: {
          // Set speaker as default route
          NSError* error;
          [[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
      }
      break;

    default:
      break;
  }
}
phuongle
  • 1,166
  • 11
  • 17
  • Finally a solution that worked for me. Many thanks. – Saran Mar 28 '19 at 06:52
  • 1
    This solution is dangerous. This notification will get called each time someone changes the route, in other words, it could lead to potential issues later. – Miki Aug 19 '19 at 12:11
5

Still seems to be an issue, phuongle answer worked for me. Swift 4 version:

NotificationCenter.default.addObserver(forName: .AVAudioSessionRouteChange, object: nil, queue: nil, using: routeChange)    

private func routeChange(_ n: Notification) {
    guard let info = n.userInfo,
        let value = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSessionRouteChangeReason(rawValue: value) else { return }
    switch reason {
    case .categoryChange: try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
    default: break
    }
}
Joe Maher
  • 5,354
  • 5
  • 28
  • 44
4

For anyone who came here, searching for a solution in Swift that also accounts for changes back from (BT-)earphones. The below sample (Swift 5) does that.
Adopted in part from @Teivaz

@objc func handleRouteChange(notification: Notification) {
    guard let info = notification.userInfo,
        let value = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSession.RouteChangeReason(rawValue: value) else { return }

    switch reason {
    case .categoryChange:
        try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
    case .oldDeviceUnavailable:
        try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
    default:
        l.debug("other")
    }
}
Martin
  • 1,112
  • 1
  • 11
  • 31
  • 1
    This really is the only solution that worked for me from all that I tried. The problem seems to be that the category changes even after the session has been started and so overrides the initial configuration – cseh_17 Sep 26 '22 at 10:48
2

I found solution in the end.

Reason was that you need to set AVAudioSession category to AVAudioSessionCategoryPlayback. But for some reason after establishing webRTC call it was set back to AVAudioSessionCategoryPlayAndRecord. In the end I decided to add observer for AVAudioSessionRouteChangeNotification and switch to AVAudioSessionCategoryPlayback each time I detected unwanted category change. A bit of hack solution but worked in the end. You can check it here.

Josip B.
  • 2,434
  • 1
  • 25
  • 30
1

phuongle's answer is correct. Though when you enable override this will actually override audio output even when user plugs in headphones. There's not much sense in playing audio through loudspeaker when user is using headphones. For this purpose use following code:

- (void)didSessionRouteChange:(NSNotification *)notification
{
    NSDictionary *interuptionDict = notification.userInfo;
    const NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

    if (routeChangeReason == AVAudioSessionRouteChangeReasonRouteConfigurationChange) {
        [self enableLoudspeaker];
    }
}

- (void)enableLoudspeaker {
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    AVAudioSessionCategoryOptions options = audioSession.categoryOptions;
    if (options & AVAudioSessionCategoryOptionDefaultToSpeaker) return;
    options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    [audioSession setActive:YES error:nil];
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:nil];
}
Teivaz
  • 5,462
  • 4
  • 37
  • 75
  • "AVAudioSessionRouteChangeReasonCategoryChange" instead of "AVAudioSessionRouteChangeReasonRouteConfigurationChange" fixed my issue. Thanks – Sana Dec 20 '19 at 18:40
1

I have been facing this issue for a while and I find a solution for this. The problem was caused when we set AVAudioSessionCategory before the WebRTC completing its configurations. So set AVAudioSessionCategory after you start the local video capture.

let rtcAudioSession = RTCAudioSession.sharedInstance()
let capturer = self.videoCapturer as? RTCCameraVideoCapturer

// set your captureDevice, frameRate
// ......

capturer.startCapture(with: captureDevice, format: format, fps: frameRate) { error in
     if error != nil {
       debugPrint("Capture error : ", error?.localizedDescription as Any)
     }
    
     // set local video renderer
     self.localVideoTrack?.add(renderer)
            
     // set audio configuration
     rtcAudioSession.lockForConfiguration()
     try? self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.defaultToSpeaker, .allowBluetoothA2DP, .allowBluetooth])
     try? self.rtcAudioSession.setMode(AVAudioSession.Mode.videoChat.rawValue)
     try? self.rtcAudioSession.overrideOutputAudioPort(.speaker)
     try? self.rtcAudioSession.setActive(true)
     rtcAudioSession.unlockForConfiguration()
}
Sreekuttan
  • 1,579
  • 13
  • 19