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