blob: 7d32ec69742be4ac67b96c2c7d1df35ddc12b45d [file] [log] [blame]
tkchin0ce3bf92016-03-12 16:52:04 -08001/*
2 * Copyright 2016 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 <Foundation/Foundation.h>
jttehf84c1d62017-04-21 13:56:39 -070012#import <OCMock/OCMock.h>
tkchin0ce3bf92016-03-12 16:52:04 -080013
Joe Chen0b3a6e32019-12-26 23:01:42 -080014#include <vector>
15
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +090016#include "rtc_base/event.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020017#include "rtc_base/gunit.h"
tkchin0ce3bf92016-03-12 16:52:04 -080018
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020019#import "components/audio/RTCAudioSession+Private.h"
denicija59ee91b2017-06-05 05:48:47 -070020
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020021#import "components/audio/RTCAudioSession.h"
22#import "components/audio/RTCAudioSessionConfiguration.h"
tkchine54467f2016-03-15 16:54:03 -070023
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020024@interface RTC_OBJC_TYPE (RTCAudioSession)
25(UnitTesting)
Peter Hanspers47217362017-10-05 11:39:15 +020026
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020027 @property(nonatomic,
28 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080029
Peter Hanspers47217362017-10-05 11:39:15 +020030- (instancetype)initWithAudioSession:(id)audioSession;
31
32@end
33
34@interface MockAVAudioSession : NSObject
35
36@property (nonatomic, readwrite, assign) float outputVolume;
37
38@end
39
40@implementation MockAVAudioSession
41@synthesize outputVolume = _outputVolume;
42@end
43
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020044@interface RTCAudioSessionTestDelegate : NSObject <RTC_OBJC_TYPE (RTCAudioSessionDelegate)>
jtteh13ae11a2017-05-25 17:52:20 -070045
46@property (nonatomic, readonly) float outputVolume;
47
tkchine54467f2016-03-15 16:54:03 -070048@end
49
50@implementation RTCAudioSessionTestDelegate
51
jtteh13ae11a2017-05-25 17:52:20 -070052@synthesize outputVolume = _outputVolume;
53
54- (instancetype)init {
55 if (self = [super init]) {
56 _outputVolume = -1;
57 }
58 return self;
59}
60
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020061- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070062}
63
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020064- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session
tkchine54467f2016-03-15 16:54:03 -070065 shouldResumeSession:(BOOL)shouldResumeSession {
66}
67
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020068- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session
69 reason:(AVAudioSessionRouteChangeReason)reason
70 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -070071}
72
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020073- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070074}
75
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020076- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070077}
78
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020079- (void)audioSessionShouldConfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070080}
81
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020082- (void)audioSessionShouldUnconfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070083}
84
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020085- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
jtteh13ae11a2017-05-25 17:52:20 -070086 didChangeOutputVolume:(float)outputVolume {
87 _outputVolume = outputVolume;
88}
89
tkchine54467f2016-03-15 16:54:03 -070090@end
91
tkchinefdd9302016-04-11 12:00:59 -070092// A delegate that adds itself to the audio session on init and removes itself
93// in its dealloc.
94@interface RTCTestRemoveOnDeallocDelegate : RTCAudioSessionTestDelegate
95@end
96
97@implementation RTCTestRemoveOnDeallocDelegate
98
99- (instancetype)init {
100 if (self = [super init]) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200101 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700102 [session addDelegate:self];
103 }
104 return self;
105}
106
107- (void)dealloc {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200108 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700109 [session removeDelegate:self];
110}
111
112@end
113
tkchin0ce3bf92016-03-12 16:52:04 -0800114
115@interface RTCAudioSessionTest : NSObject
116
tkchin0ce3bf92016-03-12 16:52:04 -0800117@end
118
119@implementation RTCAudioSessionTest
120
tkchine54467f2016-03-15 16:54:03 -0700121- (void)testAddAndRemoveDelegates {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200122 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700123 NSMutableArray *delegates = [NSMutableArray array];
124 const size_t count = 5;
125 for (size_t i = 0; i < count; ++i) {
126 RTCAudioSessionTestDelegate *delegate =
127 [[RTCAudioSessionTestDelegate alloc] init];
128 [session addDelegate:delegate];
129 [delegates addObject:delegate];
130 EXPECT_EQ(i + 1, session.delegates.size());
131 }
132 [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj,
133 NSUInteger idx,
134 BOOL *stop) {
135 [session removeDelegate:obj];
136 }];
137 EXPECT_EQ(0u, session.delegates.size());
138}
139
140- (void)testPushDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200141 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700142 NSMutableArray *delegates = [NSMutableArray array];
143 const size_t count = 2;
144 for (size_t i = 0; i < count; ++i) {
145 RTCAudioSessionTestDelegate *delegate =
146 [[RTCAudioSessionTestDelegate alloc] init];
147 [session addDelegate:delegate];
148 [delegates addObject:delegate];
149 }
150 // Test that it gets added to the front of the list.
151 RTCAudioSessionTestDelegate *pushedDelegate =
152 [[RTCAudioSessionTestDelegate alloc] init];
153 [session pushDelegate:pushedDelegate];
154 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
155
156 // Test that it stays at the front of the list.
157 for (size_t i = 0; i < count; ++i) {
158 RTCAudioSessionTestDelegate *delegate =
159 [[RTCAudioSessionTestDelegate alloc] init];
160 [session addDelegate:delegate];
161 [delegates addObject:delegate];
162 }
163 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
164
165 // Test that the next one goes to the front too.
166 pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init];
167 [session pushDelegate:pushedDelegate];
168 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
169}
170
171// Tests that delegates added to the audio session properly zero out. This is
172// checking an implementation detail (that vectors of __weak work as expected).
173- (void)testZeroingWeakDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200174 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700175 @autoreleasepool {
176 // Add a delegate to the session. There should be one delegate at this
177 // point.
178 RTCAudioSessionTestDelegate *delegate =
179 [[RTCAudioSessionTestDelegate alloc] init];
180 [session addDelegate:delegate];
181 EXPECT_EQ(1u, session.delegates.size());
182 EXPECT_TRUE(session.delegates[0]);
183 }
184 // The previously created delegate should've de-alloced, leaving a nil ptr.
185 EXPECT_FALSE(session.delegates[0]);
186 RTCAudioSessionTestDelegate *delegate =
187 [[RTCAudioSessionTestDelegate alloc] init];
188 [session addDelegate:delegate];
189 // On adding a new delegate, nil ptrs should've been cleared.
190 EXPECT_EQ(1u, session.delegates.size());
191 EXPECT_TRUE(session.delegates[0]);
192}
193
tkchinefdd9302016-04-11 12:00:59 -0700194// Tests that we don't crash when removing delegates in dealloc.
195// Added as a regression test.
196- (void)testRemoveDelegateOnDealloc {
197 @autoreleasepool {
198 RTCTestRemoveOnDeallocDelegate *delegate =
199 [[RTCTestRemoveOnDeallocDelegate alloc] init];
200 EXPECT_TRUE(delegate);
201 }
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200202 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700203 EXPECT_EQ(0u, session.delegates.size());
204}
205
jtteh3c9a6c02017-04-18 09:09:35 -0700206- (void)testAudioSessionActivation {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200207 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
jtteh3c9a6c02017-04-18 09:09:35 -0700208 EXPECT_EQ(0, audioSession.activationCount);
209 [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]];
210 EXPECT_EQ(1, audioSession.activationCount);
211 [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
212 EXPECT_EQ(0, audioSession.activationCount);
213}
214
jttehf84c1d62017-04-21 13:56:39 -0700215// Hack - fixes OCMVerify link error
216// Link error is: Undefined symbols for architecture i386:
217// "OCMMakeLocation(objc_object*, char const*, int)", referenced from:
218// -[RTCAudioSessionTest testConfigureWebRTCSession] in RTCAudioSessionTest.o
219// ld: symbol(s) not found for architecture i386
220// REASON: https://github.com/erikdoe/ocmock/issues/238
221OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line){
222 return [OCMLocation locationWithTestCase:testCase
223 file:[NSString stringWithUTF8String:fileCString]
224 line:line];
225}
226
227- (void)testConfigureWebRTCSession {
228 NSError *error = nil;
229
230 void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
231 __autoreleasing NSError **retError;
232 [invocation getArgument:&retError atIndex:4];
233 *retError = [NSError errorWithDomain:@"AVAudioSession"
Björn Terelius02768ae2021-07-02 16:19:32 +0200234 code:AVAudioSessionErrorCodeCannotInterruptOthers
jttehf84c1d62017-04-21 13:56:39 -0700235 userInfo:nil];
236 BOOL failure = NO;
237 [invocation setReturnValue:&failure];
238 };
239
240 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]);
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100241 OCMStub([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
242 withOptions:0
243 error:([OCMArg anyObjectRef])])
244 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700245
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200246 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]);
jttehf84c1d62017-04-21 13:56:39 -0700247 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession);
248
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200249 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession;
jttehf84c1d62017-04-21 13:56:39 -0700250 EXPECT_EQ(0, audioSession.activationCount);
251 [audioSession lockForConfiguration];
jttehf84c1d62017-04-21 13:56:39 -0700252 // configureWebRTCSession is forced to fail in the above mock interface,
253 // so activationCount should remain 0
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100254 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
255 withOptions:0
256 error:([OCMArg anyObjectRef])])
257 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700258 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession);
259 EXPECT_FALSE([audioSession configureWebRTCSession:&error]);
260 EXPECT_EQ(0, audioSession.activationCount);
261
262 id session = audioSession.session;
263 EXPECT_EQ(session, mockAVAudioSession);
264 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]);
265 [audioSession unlockForConfiguration];
266
267 OCMVerify([mockAudioSession session]);
268 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]);
269 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]);
270
271 [mockAVAudioSession stopMocking];
272 [mockAudioSession stopMocking];
273}
274
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900275- (void)testConfigureWebRTCSessionWithoutLocking {
276 NSError *error = nil;
277
278 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]);
279 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]);
280 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession);
281
282 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession;
283
284 std::unique_ptr<rtc::Thread> thread = rtc::Thread::Create();
285 EXPECT_TRUE(thread);
286 EXPECT_TRUE(thread->Start());
287
288 rtc::Event waitLock;
289 rtc::Event waitCleanup;
290 constexpr int timeoutMs = 5000;
Henrik Boström2deee4b2022-01-20 11:58:05 +0100291 thread->PostTask([audioSession, &waitLock, &waitCleanup] {
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900292 [audioSession lockForConfiguration];
293 waitLock.Set();
294 waitCleanup.Wait(timeoutMs);
295 [audioSession unlockForConfiguration];
296 });
297
298 waitLock.Wait(timeoutMs);
299 [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:&error];
300 EXPECT_TRUE(error != nil);
301 EXPECT_EQ(error.domain, kRTCAudioSessionErrorDomain);
302 EXPECT_EQ(error.code, kRTCAudioSessionErrorLockRequired);
303 waitCleanup.Set();
304 thread->Stop();
305
306 [mockAVAudioSession stopMocking];
307 [mockAudioSession stopMocking];
308}
309
jtteh13ae11a2017-05-25 17:52:20 -0700310- (void)testAudioVolumeDidNotify {
Peter Hanspers47217362017-10-05 11:39:15 +0200311 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init];
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200312 RTC_OBJC_TYPE(RTCAudioSession) *session =
313 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession];
jtteh13ae11a2017-05-25 17:52:20 -0700314 RTCAudioSessionTestDelegate *delegate =
315 [[RTCAudioSessionTestDelegate alloc] init];
316 [session addDelegate:delegate];
317
Peter Hanspers47217362017-10-05 11:39:15 +0200318 float expectedVolume = 0.75;
319 mockAVAudioSession.outputVolume = expectedVolume;
jtteh13ae11a2017-05-25 17:52:20 -0700320
Peter Hanspers47217362017-10-05 11:39:15 +0200321 EXPECT_EQ(expectedVolume, delegate.outputVolume);
jtteh13ae11a2017-05-25 17:52:20 -0700322}
323
tkchin0ce3bf92016-03-12 16:52:04 -0800324@end
325
tkchine54467f2016-03-15 16:54:03 -0700326namespace webrtc {
327
328class AudioSessionTest : public ::testing::Test {
329 protected:
Mirko Bonadei17aff352018-07-26 12:20:40 +0200330 void TearDown() override {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200331 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
332 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) {
tkchine54467f2016-03-15 16:54:03 -0700333 [session removeDelegate:delegate];
334 }
335 }
336};
337
tkchine54467f2016-03-15 16:54:03 -0700338TEST_F(AudioSessionTest, AddAndRemoveDelegates) {
339 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
340 [test testAddAndRemoveDelegates];
341}
342
343TEST_F(AudioSessionTest, PushDelegate) {
344 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
345 [test testPushDelegate];
346}
347
348TEST_F(AudioSessionTest, ZeroingWeakDelegate) {
349 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
350 [test testZeroingWeakDelegate];
351}
352
tkchinefdd9302016-04-11 12:00:59 -0700353TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) {
354 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
355 [test testRemoveDelegateOnDealloc];
356}
357
jtteh3c9a6c02017-04-18 09:09:35 -0700358TEST_F(AudioSessionTest, AudioSessionActivation) {
359 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
360 [test testAudioSessionActivation];
361}
362
jttehf84c1d62017-04-21 13:56:39 -0700363TEST_F(AudioSessionTest, ConfigureWebRTCSession) {
364 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
365 [test testConfigureWebRTCSession];
366}
jtteh3c9a6c02017-04-18 09:09:35 -0700367
Byoungchan Lee0a54e7a2021-09-06 22:32:52 +0900368TEST_F(AudioSessionTest, ConfigureWebRTCSessionWithoutLocking) {
369 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
370 [test testConfigureWebRTCSessionWithoutLocking];
371}
372
jtteh13ae11a2017-05-25 17:52:20 -0700373TEST_F(AudioSessionTest, AudioVolumeDidNotify) {
374 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
375 [test testAudioVolumeDidNotify];
376}
377
tkchine54467f2016-03-15 16:54:03 -0700378} // namespace webrtc