1*d9f75844SAndroid Build Coastguard Worker/* 2*d9f75844SAndroid Build Coastguard Worker * Copyright 2018 The WebRTC project authors. All Rights Reserved. 3*d9f75844SAndroid Build Coastguard Worker * 4*d9f75844SAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license 5*d9f75844SAndroid Build Coastguard Worker * that can be found in the LICENSE file in the root of the source 6*d9f75844SAndroid Build Coastguard Worker * tree. An additional intellectual property rights grant can be found 7*d9f75844SAndroid Build Coastguard Worker * in the file PATENTS. All contributing project authors may 8*d9f75844SAndroid Build Coastguard Worker * be found in the AUTHORS file in the root of the source tree. 9*d9f75844SAndroid Build Coastguard Worker */ 10*d9f75844SAndroid Build Coastguard Worker 11*d9f75844SAndroid Build Coastguard Worker#import <XCTest/XCTest.h> 12*d9f75844SAndroid Build Coastguard Worker 13*d9f75844SAndroid Build Coastguard Worker#include "api/task_queue/default_task_queue_factory.h" 14*d9f75844SAndroid Build Coastguard Worker 15*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/components/audio/RTCAudioSession+Private.h" 16*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/native/api/audio_device_module.h" 17*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/native/src/audio/audio_device_ios.h" 18*d9f75844SAndroid Build Coastguard Worker 19*d9f75844SAndroid Build Coastguard Worker@interface RTCAudioDeviceTests : XCTestCase { 20*d9f75844SAndroid Build Coastguard Worker rtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule; 21*d9f75844SAndroid Build Coastguard Worker std::unique_ptr<webrtc::ios_adm::AudioDeviceIOS> _audio_device; 22*d9f75844SAndroid Build Coastguard Worker} 23*d9f75844SAndroid Build Coastguard Worker 24*d9f75844SAndroid Build Coastguard Worker@property(nonatomic) RTC_OBJC_TYPE(RTCAudioSession) * audioSession; 25*d9f75844SAndroid Build Coastguard Worker 26*d9f75844SAndroid Build Coastguard Worker@end 27*d9f75844SAndroid Build Coastguard Worker 28*d9f75844SAndroid Build Coastguard Worker@implementation RTCAudioDeviceTests 29*d9f75844SAndroid Build Coastguard Worker 30*d9f75844SAndroid Build Coastguard Worker@synthesize audioSession = _audioSession; 31*d9f75844SAndroid Build Coastguard Worker 32*d9f75844SAndroid Build Coastguard Worker- (void)setUp { 33*d9f75844SAndroid Build Coastguard Worker [super setUp]; 34*d9f75844SAndroid Build Coastguard Worker 35*d9f75844SAndroid Build Coastguard Worker _audioDeviceModule = webrtc::CreateAudioDeviceModule(); 36*d9f75844SAndroid Build Coastguard Worker _audio_device.reset(new webrtc::ios_adm::AudioDeviceIOS(/*bypass_voice_processing=*/false)); 37*d9f75844SAndroid Build Coastguard Worker self.audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 38*d9f75844SAndroid Build Coastguard Worker 39*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 40*d9f75844SAndroid Build Coastguard Worker [self.audioSession lockForConfiguration]; 41*d9f75844SAndroid Build Coastguard Worker [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:&error]; 42*d9f75844SAndroid Build Coastguard Worker XCTAssertNil(error); 43*d9f75844SAndroid Build Coastguard Worker 44*d9f75844SAndroid Build Coastguard Worker [self.audioSession setMode:AVAudioSessionModeVoiceChat error:&error]; 45*d9f75844SAndroid Build Coastguard Worker XCTAssertNil(error); 46*d9f75844SAndroid Build Coastguard Worker 47*d9f75844SAndroid Build Coastguard Worker [self.audioSession setActive:YES error:&error]; 48*d9f75844SAndroid Build Coastguard Worker XCTAssertNil(error); 49*d9f75844SAndroid Build Coastguard Worker 50*d9f75844SAndroid Build Coastguard Worker [self.audioSession unlockForConfiguration]; 51*d9f75844SAndroid Build Coastguard Worker} 52*d9f75844SAndroid Build Coastguard Worker 53*d9f75844SAndroid Build Coastguard Worker- (void)tearDown { 54*d9f75844SAndroid Build Coastguard Worker _audio_device->Terminate(); 55*d9f75844SAndroid Build Coastguard Worker _audio_device.reset(nullptr); 56*d9f75844SAndroid Build Coastguard Worker _audioDeviceModule = nullptr; 57*d9f75844SAndroid Build Coastguard Worker [self.audioSession notifyDidEndInterruptionWithShouldResumeSession:NO]; 58*d9f75844SAndroid Build Coastguard Worker 59*d9f75844SAndroid Build Coastguard Worker [super tearDown]; 60*d9f75844SAndroid Build Coastguard Worker} 61*d9f75844SAndroid Build Coastguard Worker 62*d9f75844SAndroid Build Coastguard Worker// Verifies that the AudioDeviceIOS is_interrupted_ flag is reset correctly 63*d9f75844SAndroid Build Coastguard Worker// after an iOS AVAudioSessionInterruptionTypeEnded notification event. 64*d9f75844SAndroid Build Coastguard Worker// AudioDeviceIOS listens to RTC_OBJC_TYPE(RTCAudioSession) interrupted notifications by: 65*d9f75844SAndroid Build Coastguard Worker// - In AudioDeviceIOS.InitPlayOrRecord registers its audio_session_observer_ 66*d9f75844SAndroid Build Coastguard Worker// callback with RTC_OBJC_TYPE(RTCAudioSession)'s delegate list. 67*d9f75844SAndroid Build Coastguard Worker// - When RTC_OBJC_TYPE(RTCAudioSession) receives an iOS audio interrupted notification, it 68*d9f75844SAndroid Build Coastguard Worker// passes the notification to callbacks in its delegate list which sets 69*d9f75844SAndroid Build Coastguard Worker// AudioDeviceIOS's is_interrupted_ flag to true. 70*d9f75844SAndroid Build Coastguard Worker// - When AudioDeviceIOS.ShutdownPlayOrRecord is called, its 71*d9f75844SAndroid Build Coastguard Worker// audio_session_observer_ callback is removed from RTCAudioSessions's 72*d9f75844SAndroid Build Coastguard Worker// delegate list. 73*d9f75844SAndroid Build Coastguard Worker// So if RTC_OBJC_TYPE(RTCAudioSession) receives an iOS end audio interruption notification, 74*d9f75844SAndroid Build Coastguard Worker// AudioDeviceIOS is not notified as its callback is not in RTC_OBJC_TYPE(RTCAudioSession)'s 75*d9f75844SAndroid Build Coastguard Worker// delegate list. This causes AudioDeviceIOS's is_interrupted_ flag to be in 76*d9f75844SAndroid Build Coastguard Worker// the wrong (true) state and the audio session will ignore audio changes. 77*d9f75844SAndroid Build Coastguard Worker// As RTC_OBJC_TYPE(RTCAudioSession) keeps its own interrupted state, the fix is to initialize 78*d9f75844SAndroid Build Coastguard Worker// AudioDeviceIOS's is_interrupted_ flag to RTC_OBJC_TYPE(RTCAudioSession)'s isInterrupted 79*d9f75844SAndroid Build Coastguard Worker// flag in AudioDeviceIOS.InitPlayOrRecord. 80*d9f75844SAndroid Build Coastguard Worker- (void)testInterruptedAudioSession { 81*d9f75844SAndroid Build Coastguard Worker XCTAssertTrue(self.audioSession.isActive); 82*d9f75844SAndroid Build Coastguard Worker XCTAssertTrue([self.audioSession.category isEqual:AVAudioSessionCategoryPlayAndRecord] || 83*d9f75844SAndroid Build Coastguard Worker [self.audioSession.category isEqual:AVAudioSessionCategoryPlayback]); 84*d9f75844SAndroid Build Coastguard Worker XCTAssertEqual(AVAudioSessionModeVoiceChat, self.audioSession.mode); 85*d9f75844SAndroid Build Coastguard Worker 86*d9f75844SAndroid Build Coastguard Worker std::unique_ptr<webrtc::TaskQueueFactory> task_queue_factory = 87*d9f75844SAndroid Build Coastguard Worker webrtc::CreateDefaultTaskQueueFactory(); 88*d9f75844SAndroid Build Coastguard Worker std::unique_ptr<webrtc::AudioDeviceBuffer> audio_buffer; 89*d9f75844SAndroid Build Coastguard Worker audio_buffer.reset(new webrtc::AudioDeviceBuffer(task_queue_factory.get())); 90*d9f75844SAndroid Build Coastguard Worker _audio_device->AttachAudioBuffer(audio_buffer.get()); 91*d9f75844SAndroid Build Coastguard Worker XCTAssertEqual(webrtc::AudioDeviceGeneric::InitStatus::OK, _audio_device->Init()); 92*d9f75844SAndroid Build Coastguard Worker XCTAssertEqual(0, _audio_device->InitPlayout()); 93*d9f75844SAndroid Build Coastguard Worker XCTAssertEqual(0, _audio_device->StartPlayout()); 94*d9f75844SAndroid Build Coastguard Worker 95*d9f75844SAndroid Build Coastguard Worker // Force interruption. 96*d9f75844SAndroid Build Coastguard Worker [self.audioSession notifyDidBeginInterruption]; 97*d9f75844SAndroid Build Coastguard Worker 98*d9f75844SAndroid Build Coastguard Worker // Wait for notification to propagate. 99*d9f75844SAndroid Build Coastguard Worker rtc::ThreadManager::ProcessAllMessageQueuesForTesting(); 100*d9f75844SAndroid Build Coastguard Worker XCTAssertTrue(_audio_device->IsInterrupted()); 101*d9f75844SAndroid Build Coastguard Worker 102*d9f75844SAndroid Build Coastguard Worker // Force it for testing. 103*d9f75844SAndroid Build Coastguard Worker _audio_device->StopPlayout(); 104*d9f75844SAndroid Build Coastguard Worker 105*d9f75844SAndroid Build Coastguard Worker [self.audioSession notifyDidEndInterruptionWithShouldResumeSession:YES]; 106*d9f75844SAndroid Build Coastguard Worker // Wait for notification to propagate. 107*d9f75844SAndroid Build Coastguard Worker rtc::ThreadManager::ProcessAllMessageQueuesForTesting(); 108*d9f75844SAndroid Build Coastguard Worker XCTAssertTrue(_audio_device->IsInterrupted()); 109*d9f75844SAndroid Build Coastguard Worker 110*d9f75844SAndroid Build Coastguard Worker _audio_device->Init(); 111*d9f75844SAndroid Build Coastguard Worker _audio_device->InitPlayout(); 112*d9f75844SAndroid Build Coastguard Worker XCTAssertFalse(_audio_device->IsInterrupted()); 113*d9f75844SAndroid Build Coastguard Worker} 114*d9f75844SAndroid Build Coastguard Worker 115*d9f75844SAndroid Build Coastguard Worker@end 116