Skip to content

Commit f8f9dc1

Browse files
committed
Merge branch 'm125_release' into livekit-prefixed-m125
2 parents 67cf254 + c852b0e commit f8f9dc1

File tree

1 file changed

+140
-24
lines changed

1 file changed

+140
-24
lines changed

sdk/objc/components/capturer/RTCCameraVideoCapturer.m

Lines changed: 140 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@
99
*/
1010

1111
#import <Foundation/Foundation.h>
12+
#import <os/lock.h>
1213

1314
#import "RTCCameraVideoCapturer.h"
1415
#import "base/RTCLogging.h"
1516
#import "base/RTCVideoFrameBuffer.h"
1617
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
1718

18-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
19+
// AVCaptureMultiCamSession iOS 13.0+, iPadOS 13.0+, Mac Catalyst 14.0+, tvOS 17.0+
20+
#define TARGET_MULTICAM_CAPABLE (TARGET_OS_IPHONE && !TARGET_OS_VISION)
21+
22+
// iOS 2.0+, iPadOS 2.0+, Mac Catalyst 13.0+
23+
#define TARGET_WATCH_DEVICE_ROTATION \
24+
(TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION)
25+
26+
#if TARGET_WATCH_DEVICE_ROTATION
1927
#import "helpers/UIDevice+RTCDevice.h"
2028
#endif
2129

@@ -41,12 +49,24 @@ @implementation RTC_OBJC_TYPE (RTCCameraVideoCapturer) {
4149
FourCharCode _preferredOutputPixelFormat;
4250
FourCharCode _outputPixelFormat;
4351
RTCVideoRotation _rotation;
44-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
52+
53+
#if TARGET_WATCH_DEVICE_ROTATION
4554
UIInterfaceOrientation _orientation;
4655
BOOL _generatingOrientationNotifications;
4756
#endif
57+
58+
#if TARGET_MULTICAM_CAPABLE
59+
AVCaptureConnection *_captureConnection;
60+
#endif
4861
}
4962

63+
#if TARGET_MULTICAM_CAPABLE
64+
// Shared multi-camera session across capturers.
65+
static AVCaptureMultiCamSession *_sharedMultiCamSession = nil;
66+
static os_unfair_lock _sharedMultiCamSessionLock = OS_UNFAIR_LOCK_INIT;
67+
static NSUInteger _sharedMultiCamSessionCount = 0;
68+
#endif
69+
5070
@synthesize frameQueue = _frameQueue;
5171
@synthesize captureSession = _captureSession;
5272
@synthesize currentDevice = _currentDevice;
@@ -55,14 +75,13 @@ @implementation RTC_OBJC_TYPE (RTCCameraVideoCapturer) {
5575
@synthesize willBeRunning = _willBeRunning;
5676

5777
- (instancetype)init {
58-
return [self initWithDelegate:nil captureSession:[[AVCaptureSession alloc] init]];
78+
return [self initWithDelegate:nil captureSession:[self createCaptureSession]];
5979
}
6080

6181
- (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelegate)>)delegate {
62-
return [self initWithDelegate:delegate captureSession:[[AVCaptureSession alloc] init]];
82+
return [self initWithDelegate:delegate captureSession:[self createCaptureSession]];
6383
}
6484

65-
// This initializer is used for testing.
6685
- (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelegate)>)delegate
6786
captureSession:(AVCaptureSession *)captureSession {
6887
if (self = [super initWithDelegate:delegate]) {
@@ -73,8 +92,10 @@ - (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelega
7392
if (![self setupCaptureSession:captureSession]) {
7493
return nil;
7594
}
95+
7696
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
77-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
97+
98+
#if TARGET_WATCH_DEVICE_ROTATION
7899
_orientation = UIInterfaceOrientationPortrait;
79100
_rotation = RTCVideoRotation_90;
80101
[center addObserver:self
@@ -94,6 +115,7 @@ - (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelega
94115
name:UIApplicationDidBecomeActiveNotification
95116
object:[UIApplication sharedApplication]];
96117
#endif
118+
97119
[center addObserver:self
98120
selector:@selector(handleCaptureSessionRuntimeError:)
99121
name:AVCaptureSessionRuntimeErrorNotification
@@ -107,6 +129,7 @@ - (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelega
107129
name:AVCaptureSessionDidStopRunningNotification
108130
object:_captureSession];
109131
}
132+
110133
return self;
111134
}
112135

@@ -119,8 +142,8 @@ - (void)dealloc {
119142

120143
+ (NSArray<AVCaptureDevice *> *)captureDevices {
121144
#if TARGET_OS_VISION
122-
// Simply return an empty array.
123-
return [NSArray array];
145+
AVCaptureDevice *device = AVCaptureDevice.systemPreferredCamera;
146+
return device ? @[ device ] : @[];
124147
#else
125148
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
126149
discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
@@ -182,7 +205,7 @@ - (void)startCaptureWithDevice:(AVCaptureDevice *)device
182205
block:^{
183206
RTCLogInfo("startCaptureWithDevice %@ @ %ld fps", format, (long)fps);
184207

185-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
208+
#if TARGET_WATCH_DEVICE_ROTATION
186209
dispatch_async(dispatch_get_main_queue(), ^{
187210
if (!self->_generatingOrientationNotifications) {
188211
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
@@ -211,8 +234,8 @@ - (void)startCaptureWithDevice:(AVCaptureDevice *)device
211234
[self updateZoomFactor];
212235
[self.currentDevice unlockForConfiguration];
213236

214-
[self.captureSession startRunning];
215-
self.isRunning = YES;
237+
[self startRunning];
238+
216239
if (completionHandler) {
217240
completionHandler(nil);
218241
}
@@ -225,21 +248,31 @@ - (void)stopCaptureWithCompletionHandler:(nullable void (^)(void))completionHand
225248
dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
226249
block:^{
227250
RTCLogInfo("Stop");
228-
self.currentDevice = nil;
251+
252+
#if TARGET_MULTICAM_CAPABLE
253+
[self.captureSession removeConnection:self->_captureConnection];
254+
self->_captureConnection = nil;
255+
#endif
256+
229257
for (AVCaptureDeviceInput *oldInput in [self.captureSession.inputs copy]) {
230-
[self.captureSession removeInput:oldInput];
258+
// Remove any old input with same device.
259+
if ([self->_currentDevice isEqual:oldInput.device]) {
260+
[self.captureSession removeInput:oldInput];
261+
}
231262
}
232-
[self.captureSession stopRunning];
263+
self.currentDevice = nil;
264+
265+
[self stopRunning];
233266

234-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
267+
#if TARGET_WATCH_DEVICE_ROTATION
235268
dispatch_async(dispatch_get_main_queue(), ^{
236269
if (self->_generatingOrientationNotifications) {
237270
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
238271
self->_generatingOrientationNotifications = NO;
239272
}
240273
});
241274
#endif
242-
self.isRunning = NO;
275+
243276
if (completionHandler) {
244277
completionHandler();
245278
}
@@ -248,7 +281,7 @@ - (void)stopCaptureWithCompletionHandler:(nullable void (^)(void))completionHand
248281

249282
#pragma mark iOS notifications
250283

251-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
284+
#if TARGET_WATCH_DEVICE_ROTATION
252285
- (void)deviceOrientationDidChange:(NSNotification *)notification {
253286
[self updateOrientation];
254287
}
@@ -271,7 +304,7 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput
271304
return;
272305
}
273306

274-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
307+
#if TARGET_WATCH_DEVICE_ROTATION
275308
// Default to portrait orientation on iPhone.
276309
BOOL usingFrontCamera = NO;
277310
// Check the image's EXIF for the camera the image came from as the image could have been
@@ -433,10 +466,70 @@ - (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
433466
}];
434467
}
435468

436-
#endif // TARGET_OS_IPHONE
469+
#endif
437470

438471
#pragma mark - Private
439472

473+
- (AVCaptureSession *)createCaptureSession {
474+
#if TARGET_MULTICAM_CAPABLE
475+
if (AVCaptureMultiCamSession.isMultiCamSupported) {
476+
// AVCaptureMultiCamSession exists and device supports multi-cam.
477+
if (_sharedMultiCamSession == nil) {
478+
_sharedMultiCamSession = [[AVCaptureMultiCamSession alloc] init];
479+
}
480+
return _sharedMultiCamSession;
481+
} else {
482+
// AVCaptureMultiCamSession exists but device doesn't support multi-cam.
483+
return [[AVCaptureSession alloc] init];
484+
}
485+
#else
486+
// AVCaptureMultiCamSession doesn't exist with this platform, use AVCaptureSession.
487+
return [[AVCaptureSession alloc] init];
488+
#endif
489+
}
490+
491+
- (BOOL)isUsingSelfCreatedMultiCamSession {
492+
#if TARGET_MULTICAM_CAPABLE
493+
return _sharedMultiCamSession != nil && _sharedMultiCamSession == _captureSession;
494+
#else
495+
return NO;
496+
#endif
497+
}
498+
499+
- (void)startRunning {
500+
BOOL shouldStartRunning = YES;
501+
#if TARGET_MULTICAM_CAPABLE
502+
if ([self isUsingSelfCreatedMultiCamSession]) {
503+
os_unfair_lock_lock(&_sharedMultiCamSessionLock);
504+
shouldStartRunning = _sharedMultiCamSessionCount == 0;
505+
_sharedMultiCamSessionCount += 1;
506+
os_unfair_lock_unlock(&_sharedMultiCamSessionLock);
507+
}
508+
#endif
509+
if (shouldStartRunning) {
510+
[_captureSession startRunning];
511+
}
512+
self.isRunning = YES;
513+
}
514+
515+
- (void)stopRunning {
516+
BOOL shouldStopRunning = YES;
517+
#if TARGET_MULTICAM_CAPABLE
518+
if ([self isUsingSelfCreatedMultiCamSession]) {
519+
os_unfair_lock_lock(&_sharedMultiCamSessionLock);
520+
if (_sharedMultiCamSessionCount > 0) {
521+
_sharedMultiCamSessionCount -= 1;
522+
shouldStopRunning = _sharedMultiCamSessionCount == 0;
523+
}
524+
os_unfair_lock_unlock(&_sharedMultiCamSessionLock);
525+
}
526+
#endif
527+
if (shouldStopRunning) {
528+
[_captureSession stopRunning];
529+
}
530+
self.isRunning = NO;
531+
}
532+
440533
- (dispatch_queue_t)frameQueue {
441534
if (!_frameQueue) {
442535
_frameQueue = RTCDispatchQueueCreateWithTarget(
@@ -459,7 +552,12 @@ - (BOOL)setupCaptureSession:(AVCaptureSession *)captureSession {
459552
RTCLogError(@"Video data output unsupported.");
460553
return NO;
461554
}
555+
556+
#if TARGET_MULTICAM_CAPABLE
557+
[_captureSession addOutputWithNoConnections:_videoDataOutput];
558+
#else
462559
[_captureSession addOutput:_videoDataOutput];
560+
#endif
463561

464562
return YES;
465563
}
@@ -544,22 +642,40 @@ - (void)reconfigureCaptureSessionInput {
544642
}
545643
[_captureSession beginConfiguration];
546644
for (AVCaptureDeviceInput *oldInput in [_captureSession.inputs copy]) {
547-
[_captureSession removeInput:oldInput];
645+
// Remove any old input with same device.
646+
if ([_currentDevice isEqual:oldInput.device]) {
647+
[_captureSession removeInput:oldInput];
648+
}
548649
}
650+
549651
if ([_captureSession canAddInput:input]) {
652+
#if TARGET_MULTICAM_CAPABLE
653+
[_captureSession addInputWithNoConnections:input];
654+
655+
AVCaptureInputPort *videoPort = input.ports.firstObject;
656+
_captureConnection = [AVCaptureConnection connectionWithInputPorts:@[ videoPort ]
657+
output:_videoDataOutput];
658+
659+
[_captureSession addConnection:_captureConnection];
660+
#else
550661
[_captureSession addInput:input];
662+
#endif
551663
} else {
552664
RTCLogError(@"Cannot add camera as an input to the session.");
553665
}
666+
554667
[_captureSession commitConfiguration];
555668
}
556669

557-
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_VISION
670+
#if TARGET_WATCH_DEVICE_ROTATION
558671
- (void)updateOrientation {
559672
NSAssert([RTC_OBJC_TYPE(RTCDispatcher) isOnQueueForType:RTCDispatcherTypeMain],
560-
@"statusBarOrientation must be called on the main queue.");
561-
// statusBarOrientation must be called on the main queue
562-
UIInterfaceOrientation newOrientation = [UIApplication sharedApplication].statusBarOrientation;
673+
@"Retrieving device orientation must be called on the main queue.");
674+
675+
// Must be called on the main queue.
676+
UIWindowScene *windowScene =
677+
(UIWindowScene *)[UIApplication sharedApplication].connectedScenes.anyObject;
678+
UIInterfaceOrientation newOrientation = windowScene.interfaceOrientation;
563679

564680
[RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
565681
block:^{

0 commit comments

Comments
 (0)