xref: /aosp_15_r20/external/webrtc/sdk/objc/components/capturer/RTCCameraVideoCapturer.m (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker/*
2*d9f75844SAndroid Build Coastguard Worker *  Copyright 2017 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 <Foundation/Foundation.h>
12*d9f75844SAndroid Build Coastguard Worker
13*d9f75844SAndroid Build Coastguard Worker#import "RTCCameraVideoCapturer.h"
14*d9f75844SAndroid Build Coastguard Worker#import "base/RTCLogging.h"
15*d9f75844SAndroid Build Coastguard Worker#import "base/RTCVideoFrameBuffer.h"
16*d9f75844SAndroid Build Coastguard Worker#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
17*d9f75844SAndroid Build Coastguard Worker
18*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
19*d9f75844SAndroid Build Coastguard Worker#import "helpers/UIDevice+RTCDevice.h"
20*d9f75844SAndroid Build Coastguard Worker#endif
21*d9f75844SAndroid Build Coastguard Worker
22*d9f75844SAndroid Build Coastguard Worker#import "helpers/AVCaptureSession+DevicePosition.h"
23*d9f75844SAndroid Build Coastguard Worker#import "helpers/RTCDispatcher+Private.h"
24*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/system/gcd_helpers.h"
25*d9f75844SAndroid Build Coastguard Worker
26*d9f75844SAndroid Build Coastguard Workerconst int64_t kNanosecondsPerSecond = 1000000000;
27*d9f75844SAndroid Build Coastguard Worker
28*d9f75844SAndroid Build Coastguard Worker@interface RTC_OBJC_TYPE (RTCCameraVideoCapturer)
29*d9f75844SAndroid Build Coastguard Worker()<AVCaptureVideoDataOutputSampleBufferDelegate> @property(nonatomic,
30*d9f75844SAndroid Build Coastguard Worker                                                           readonly) dispatch_queue_t frameQueue;
31*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) AVCaptureDevice *currentDevice;
32*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, assign) BOOL hasRetriedOnFatalError;
33*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, assign) BOOL isRunning;
34*d9f75844SAndroid Build Coastguard Worker// Will the session be running once all asynchronous operations have been completed?
35*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, assign) BOOL willBeRunning;
36*d9f75844SAndroid Build Coastguard Worker@end
37*d9f75844SAndroid Build Coastguard Worker
38*d9f75844SAndroid Build Coastguard Worker@implementation RTC_OBJC_TYPE (RTCCameraVideoCapturer) {
39*d9f75844SAndroid Build Coastguard Worker  AVCaptureVideoDataOutput *_videoDataOutput;
40*d9f75844SAndroid Build Coastguard Worker  AVCaptureSession *_captureSession;
41*d9f75844SAndroid Build Coastguard Worker  FourCharCode _preferredOutputPixelFormat;
42*d9f75844SAndroid Build Coastguard Worker  FourCharCode _outputPixelFormat;
43*d9f75844SAndroid Build Coastguard Worker  RTCVideoRotation _rotation;
44*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
45*d9f75844SAndroid Build Coastguard Worker  UIDeviceOrientation _orientation;
46*d9f75844SAndroid Build Coastguard Worker  BOOL _generatingOrientationNotifications;
47*d9f75844SAndroid Build Coastguard Worker#endif
48*d9f75844SAndroid Build Coastguard Worker}
49*d9f75844SAndroid Build Coastguard Worker
50*d9f75844SAndroid Build Coastguard Worker@synthesize frameQueue = _frameQueue;
51*d9f75844SAndroid Build Coastguard Worker@synthesize captureSession = _captureSession;
52*d9f75844SAndroid Build Coastguard Worker@synthesize currentDevice = _currentDevice;
53*d9f75844SAndroid Build Coastguard Worker@synthesize hasRetriedOnFatalError = _hasRetriedOnFatalError;
54*d9f75844SAndroid Build Coastguard Worker@synthesize isRunning = _isRunning;
55*d9f75844SAndroid Build Coastguard Worker@synthesize willBeRunning = _willBeRunning;
56*d9f75844SAndroid Build Coastguard Worker
57*d9f75844SAndroid Build Coastguard Worker- (instancetype)init {
58*d9f75844SAndroid Build Coastguard Worker  return [self initWithDelegate:nil captureSession:[[AVCaptureSession alloc] init]];
59*d9f75844SAndroid Build Coastguard Worker}
60*d9f75844SAndroid Build Coastguard Worker
61*d9f75844SAndroid Build Coastguard Worker- (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelegate)>)delegate {
62*d9f75844SAndroid Build Coastguard Worker  return [self initWithDelegate:delegate captureSession:[[AVCaptureSession alloc] init]];
63*d9f75844SAndroid Build Coastguard Worker}
64*d9f75844SAndroid Build Coastguard Worker
65*d9f75844SAndroid Build Coastguard Worker// This initializer is used for testing.
66*d9f75844SAndroid Build Coastguard Worker- (instancetype)initWithDelegate:(__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelegate)>)delegate
67*d9f75844SAndroid Build Coastguard Worker                  captureSession:(AVCaptureSession *)captureSession {
68*d9f75844SAndroid Build Coastguard Worker  if (self = [super initWithDelegate:delegate]) {
69*d9f75844SAndroid Build Coastguard Worker    // Create the capture session and all relevant inputs and outputs. We need
70*d9f75844SAndroid Build Coastguard Worker    // to do this in init because the application may want the capture session
71*d9f75844SAndroid Build Coastguard Worker    // before we start the capturer for e.g. AVCapturePreviewLayer. All objects
72*d9f75844SAndroid Build Coastguard Worker    // created here are retained until dealloc and never recreated.
73*d9f75844SAndroid Build Coastguard Worker    if (![self setupCaptureSession:captureSession]) {
74*d9f75844SAndroid Build Coastguard Worker      return nil;
75*d9f75844SAndroid Build Coastguard Worker    }
76*d9f75844SAndroid Build Coastguard Worker    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
77*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
78*d9f75844SAndroid Build Coastguard Worker    _orientation = UIDeviceOrientationPortrait;
79*d9f75844SAndroid Build Coastguard Worker    _rotation = RTCVideoRotation_90;
80*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
81*d9f75844SAndroid Build Coastguard Worker               selector:@selector(deviceOrientationDidChange:)
82*d9f75844SAndroid Build Coastguard Worker                   name:UIDeviceOrientationDidChangeNotification
83*d9f75844SAndroid Build Coastguard Worker                 object:nil];
84*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
85*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleCaptureSessionInterruption:)
86*d9f75844SAndroid Build Coastguard Worker                   name:AVCaptureSessionWasInterruptedNotification
87*d9f75844SAndroid Build Coastguard Worker                 object:_captureSession];
88*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
89*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleCaptureSessionInterruptionEnded:)
90*d9f75844SAndroid Build Coastguard Worker                   name:AVCaptureSessionInterruptionEndedNotification
91*d9f75844SAndroid Build Coastguard Worker                 object:_captureSession];
92*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
93*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleApplicationDidBecomeActive:)
94*d9f75844SAndroid Build Coastguard Worker                   name:UIApplicationDidBecomeActiveNotification
95*d9f75844SAndroid Build Coastguard Worker                 object:[UIApplication sharedApplication]];
96*d9f75844SAndroid Build Coastguard Worker#endif
97*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
98*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleCaptureSessionRuntimeError:)
99*d9f75844SAndroid Build Coastguard Worker                   name:AVCaptureSessionRuntimeErrorNotification
100*d9f75844SAndroid Build Coastguard Worker                 object:_captureSession];
101*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
102*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleCaptureSessionDidStartRunning:)
103*d9f75844SAndroid Build Coastguard Worker                   name:AVCaptureSessionDidStartRunningNotification
104*d9f75844SAndroid Build Coastguard Worker                 object:_captureSession];
105*d9f75844SAndroid Build Coastguard Worker    [center addObserver:self
106*d9f75844SAndroid Build Coastguard Worker               selector:@selector(handleCaptureSessionDidStopRunning:)
107*d9f75844SAndroid Build Coastguard Worker                   name:AVCaptureSessionDidStopRunningNotification
108*d9f75844SAndroid Build Coastguard Worker                 object:_captureSession];
109*d9f75844SAndroid Build Coastguard Worker  }
110*d9f75844SAndroid Build Coastguard Worker  return self;
111*d9f75844SAndroid Build Coastguard Worker}
112*d9f75844SAndroid Build Coastguard Worker
113*d9f75844SAndroid Build Coastguard Worker- (void)dealloc {
114*d9f75844SAndroid Build Coastguard Worker  NSAssert(!_willBeRunning,
115*d9f75844SAndroid Build Coastguard Worker           @"Session was still running in RTC_OBJC_TYPE(RTCCameraVideoCapturer) dealloc. Forgot to "
116*d9f75844SAndroid Build Coastguard Worker           @"call stopCapture?");
117*d9f75844SAndroid Build Coastguard Worker  [[NSNotificationCenter defaultCenter] removeObserver:self];
118*d9f75844SAndroid Build Coastguard Worker}
119*d9f75844SAndroid Build Coastguard Worker
120*d9f75844SAndroid Build Coastguard Worker+ (NSArray<AVCaptureDevice *> *)captureDevices {
121*d9f75844SAndroid Build Coastguard Worker#if defined(WEBRTC_IOS) && defined(__IPHONE_10_0) && \
122*d9f75844SAndroid Build Coastguard Worker    __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0
123*d9f75844SAndroid Build Coastguard Worker  AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
124*d9f75844SAndroid Build Coastguard Worker      discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
125*d9f75844SAndroid Build Coastguard Worker                            mediaType:AVMediaTypeVideo
126*d9f75844SAndroid Build Coastguard Worker                             position:AVCaptureDevicePositionUnspecified];
127*d9f75844SAndroid Build Coastguard Worker  return session.devices;
128*d9f75844SAndroid Build Coastguard Worker#else
129*d9f75844SAndroid Build Coastguard Worker  return [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
130*d9f75844SAndroid Build Coastguard Worker#endif
131*d9f75844SAndroid Build Coastguard Worker}
132*d9f75844SAndroid Build Coastguard Worker
133*d9f75844SAndroid Build Coastguard Worker+ (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device {
134*d9f75844SAndroid Build Coastguard Worker  // Support opening the device in any format. We make sure it's converted to a format we
135*d9f75844SAndroid Build Coastguard Worker  // can handle, if needed, in the method `-setupVideoDataOutput`.
136*d9f75844SAndroid Build Coastguard Worker  return device.formats;
137*d9f75844SAndroid Build Coastguard Worker}
138*d9f75844SAndroid Build Coastguard Worker
139*d9f75844SAndroid Build Coastguard Worker- (FourCharCode)preferredOutputPixelFormat {
140*d9f75844SAndroid Build Coastguard Worker  return _preferredOutputPixelFormat;
141*d9f75844SAndroid Build Coastguard Worker}
142*d9f75844SAndroid Build Coastguard Worker
143*d9f75844SAndroid Build Coastguard Worker- (void)startCaptureWithDevice:(AVCaptureDevice *)device
144*d9f75844SAndroid Build Coastguard Worker                        format:(AVCaptureDeviceFormat *)format
145*d9f75844SAndroid Build Coastguard Worker                           fps:(NSInteger)fps {
146*d9f75844SAndroid Build Coastguard Worker  [self startCaptureWithDevice:device format:format fps:fps completionHandler:nil];
147*d9f75844SAndroid Build Coastguard Worker}
148*d9f75844SAndroid Build Coastguard Worker
149*d9f75844SAndroid Build Coastguard Worker- (void)stopCapture {
150*d9f75844SAndroid Build Coastguard Worker  [self stopCaptureWithCompletionHandler:nil];
151*d9f75844SAndroid Build Coastguard Worker}
152*d9f75844SAndroid Build Coastguard Worker
153*d9f75844SAndroid Build Coastguard Worker- (void)startCaptureWithDevice:(AVCaptureDevice *)device
154*d9f75844SAndroid Build Coastguard Worker                        format:(AVCaptureDeviceFormat *)format
155*d9f75844SAndroid Build Coastguard Worker                           fps:(NSInteger)fps
156*d9f75844SAndroid Build Coastguard Worker             completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler {
157*d9f75844SAndroid Build Coastguard Worker  _willBeRunning = YES;
158*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher)
159*d9f75844SAndroid Build Coastguard Worker      dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
160*d9f75844SAndroid Build Coastguard Worker                    block:^{
161*d9f75844SAndroid Build Coastguard Worker                      RTCLogInfo("startCaptureWithDevice %@ @ %ld fps", format, (long)fps);
162*d9f75844SAndroid Build Coastguard Worker
163*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
164*d9f75844SAndroid Build Coastguard Worker                      dispatch_async(dispatch_get_main_queue(), ^{
165*d9f75844SAndroid Build Coastguard Worker                        if (!self->_generatingOrientationNotifications) {
166*d9f75844SAndroid Build Coastguard Worker                          [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
167*d9f75844SAndroid Build Coastguard Worker                          self->_generatingOrientationNotifications = YES;
168*d9f75844SAndroid Build Coastguard Worker                        }
169*d9f75844SAndroid Build Coastguard Worker                      });
170*d9f75844SAndroid Build Coastguard Worker#endif
171*d9f75844SAndroid Build Coastguard Worker
172*d9f75844SAndroid Build Coastguard Worker                      self.currentDevice = device;
173*d9f75844SAndroid Build Coastguard Worker
174*d9f75844SAndroid Build Coastguard Worker                      NSError *error = nil;
175*d9f75844SAndroid Build Coastguard Worker                      if (![self.currentDevice lockForConfiguration:&error]) {
176*d9f75844SAndroid Build Coastguard Worker                        RTCLogError(@"Failed to lock device %@. Error: %@",
177*d9f75844SAndroid Build Coastguard Worker                                    self.currentDevice,
178*d9f75844SAndroid Build Coastguard Worker                                    error.userInfo);
179*d9f75844SAndroid Build Coastguard Worker                        if (completionHandler) {
180*d9f75844SAndroid Build Coastguard Worker                          completionHandler(error);
181*d9f75844SAndroid Build Coastguard Worker                        }
182*d9f75844SAndroid Build Coastguard Worker                        self.willBeRunning = NO;
183*d9f75844SAndroid Build Coastguard Worker                        return;
184*d9f75844SAndroid Build Coastguard Worker                      }
185*d9f75844SAndroid Build Coastguard Worker                      [self reconfigureCaptureSessionInput];
186*d9f75844SAndroid Build Coastguard Worker                      [self updateOrientation];
187*d9f75844SAndroid Build Coastguard Worker                      [self updateDeviceCaptureFormat:format fps:fps];
188*d9f75844SAndroid Build Coastguard Worker                      [self updateVideoDataOutputPixelFormat:format];
189*d9f75844SAndroid Build Coastguard Worker                      [self.captureSession startRunning];
190*d9f75844SAndroid Build Coastguard Worker                      [self.currentDevice unlockForConfiguration];
191*d9f75844SAndroid Build Coastguard Worker                      self.isRunning = YES;
192*d9f75844SAndroid Build Coastguard Worker                      if (completionHandler) {
193*d9f75844SAndroid Build Coastguard Worker                        completionHandler(nil);
194*d9f75844SAndroid Build Coastguard Worker                      }
195*d9f75844SAndroid Build Coastguard Worker                    }];
196*d9f75844SAndroid Build Coastguard Worker}
197*d9f75844SAndroid Build Coastguard Worker
198*d9f75844SAndroid Build Coastguard Worker- (void)stopCaptureWithCompletionHandler:(nullable void (^)(void))completionHandler {
199*d9f75844SAndroid Build Coastguard Worker  _willBeRunning = NO;
200*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher)
201*d9f75844SAndroid Build Coastguard Worker      dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
202*d9f75844SAndroid Build Coastguard Worker                    block:^{
203*d9f75844SAndroid Build Coastguard Worker                      RTCLogInfo("Stop");
204*d9f75844SAndroid Build Coastguard Worker                      self.currentDevice = nil;
205*d9f75844SAndroid Build Coastguard Worker                      for (AVCaptureDeviceInput *oldInput in [self.captureSession.inputs copy]) {
206*d9f75844SAndroid Build Coastguard Worker                        [self.captureSession removeInput:oldInput];
207*d9f75844SAndroid Build Coastguard Worker                      }
208*d9f75844SAndroid Build Coastguard Worker                      [self.captureSession stopRunning];
209*d9f75844SAndroid Build Coastguard Worker
210*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
211*d9f75844SAndroid Build Coastguard Worker                      dispatch_async(dispatch_get_main_queue(), ^{
212*d9f75844SAndroid Build Coastguard Worker                        if (self->_generatingOrientationNotifications) {
213*d9f75844SAndroid Build Coastguard Worker                          [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
214*d9f75844SAndroid Build Coastguard Worker                          self->_generatingOrientationNotifications = NO;
215*d9f75844SAndroid Build Coastguard Worker                        }
216*d9f75844SAndroid Build Coastguard Worker                      });
217*d9f75844SAndroid Build Coastguard Worker#endif
218*d9f75844SAndroid Build Coastguard Worker                      self.isRunning = NO;
219*d9f75844SAndroid Build Coastguard Worker                      if (completionHandler) {
220*d9f75844SAndroid Build Coastguard Worker                        completionHandler();
221*d9f75844SAndroid Build Coastguard Worker                      }
222*d9f75844SAndroid Build Coastguard Worker                    }];
223*d9f75844SAndroid Build Coastguard Worker}
224*d9f75844SAndroid Build Coastguard Worker
225*d9f75844SAndroid Build Coastguard Worker#pragma mark iOS notifications
226*d9f75844SAndroid Build Coastguard Worker
227*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
228*d9f75844SAndroid Build Coastguard Worker- (void)deviceOrientationDidChange:(NSNotification *)notification {
229*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
230*d9f75844SAndroid Build Coastguard Worker                                              block:^{
231*d9f75844SAndroid Build Coastguard Worker                                                [self updateOrientation];
232*d9f75844SAndroid Build Coastguard Worker                                              }];
233*d9f75844SAndroid Build Coastguard Worker}
234*d9f75844SAndroid Build Coastguard Worker#endif
235*d9f75844SAndroid Build Coastguard Worker
236*d9f75844SAndroid Build Coastguard Worker#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
237*d9f75844SAndroid Build Coastguard Worker
238*d9f75844SAndroid Build Coastguard Worker- (void)captureOutput:(AVCaptureOutput *)captureOutput
239*d9f75844SAndroid Build Coastguard Worker    didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
240*d9f75844SAndroid Build Coastguard Worker           fromConnection:(AVCaptureConnection *)connection {
241*d9f75844SAndroid Build Coastguard Worker  NSParameterAssert(captureOutput == _videoDataOutput);
242*d9f75844SAndroid Build Coastguard Worker
243*d9f75844SAndroid Build Coastguard Worker  if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
244*d9f75844SAndroid Build Coastguard Worker      !CMSampleBufferDataIsReady(sampleBuffer)) {
245*d9f75844SAndroid Build Coastguard Worker    return;
246*d9f75844SAndroid Build Coastguard Worker  }
247*d9f75844SAndroid Build Coastguard Worker
248*d9f75844SAndroid Build Coastguard Worker  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
249*d9f75844SAndroid Build Coastguard Worker  if (pixelBuffer == nil) {
250*d9f75844SAndroid Build Coastguard Worker    return;
251*d9f75844SAndroid Build Coastguard Worker  }
252*d9f75844SAndroid Build Coastguard Worker
253*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
254*d9f75844SAndroid Build Coastguard Worker  // Default to portrait orientation on iPhone.
255*d9f75844SAndroid Build Coastguard Worker  BOOL usingFrontCamera = NO;
256*d9f75844SAndroid Build Coastguard Worker  // Check the image's EXIF for the camera the image came from as the image could have been
257*d9f75844SAndroid Build Coastguard Worker  // delayed as we set alwaysDiscardsLateVideoFrames to NO.
258*d9f75844SAndroid Build Coastguard Worker  AVCaptureDevicePosition cameraPosition =
259*d9f75844SAndroid Build Coastguard Worker      [AVCaptureSession devicePositionForSampleBuffer:sampleBuffer];
260*d9f75844SAndroid Build Coastguard Worker  if (cameraPosition != AVCaptureDevicePositionUnspecified) {
261*d9f75844SAndroid Build Coastguard Worker    usingFrontCamera = AVCaptureDevicePositionFront == cameraPosition;
262*d9f75844SAndroid Build Coastguard Worker  } else {
263*d9f75844SAndroid Build Coastguard Worker    AVCaptureDeviceInput *deviceInput =
264*d9f75844SAndroid Build Coastguard Worker        (AVCaptureDeviceInput *)((AVCaptureInputPort *)connection.inputPorts.firstObject).input;
265*d9f75844SAndroid Build Coastguard Worker    usingFrontCamera = AVCaptureDevicePositionFront == deviceInput.device.position;
266*d9f75844SAndroid Build Coastguard Worker  }
267*d9f75844SAndroid Build Coastguard Worker  switch (_orientation) {
268*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationPortrait:
269*d9f75844SAndroid Build Coastguard Worker      _rotation = RTCVideoRotation_90;
270*d9f75844SAndroid Build Coastguard Worker      break;
271*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationPortraitUpsideDown:
272*d9f75844SAndroid Build Coastguard Worker      _rotation = RTCVideoRotation_270;
273*d9f75844SAndroid Build Coastguard Worker      break;
274*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationLandscapeLeft:
275*d9f75844SAndroid Build Coastguard Worker      _rotation = usingFrontCamera ? RTCVideoRotation_180 : RTCVideoRotation_0;
276*d9f75844SAndroid Build Coastguard Worker      break;
277*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationLandscapeRight:
278*d9f75844SAndroid Build Coastguard Worker      _rotation = usingFrontCamera ? RTCVideoRotation_0 : RTCVideoRotation_180;
279*d9f75844SAndroid Build Coastguard Worker      break;
280*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationFaceUp:
281*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationFaceDown:
282*d9f75844SAndroid Build Coastguard Worker    case UIDeviceOrientationUnknown:
283*d9f75844SAndroid Build Coastguard Worker      // Ignore.
284*d9f75844SAndroid Build Coastguard Worker      break;
285*d9f75844SAndroid Build Coastguard Worker  }
286*d9f75844SAndroid Build Coastguard Worker#else
287*d9f75844SAndroid Build Coastguard Worker  // No rotation on Mac.
288*d9f75844SAndroid Build Coastguard Worker  _rotation = RTCVideoRotation_0;
289*d9f75844SAndroid Build Coastguard Worker#endif
290*d9f75844SAndroid Build Coastguard Worker
291*d9f75844SAndroid Build Coastguard Worker  RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer =
292*d9f75844SAndroid Build Coastguard Worker      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBuffer];
293*d9f75844SAndroid Build Coastguard Worker  int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) *
294*d9f75844SAndroid Build Coastguard Worker      kNanosecondsPerSecond;
295*d9f75844SAndroid Build Coastguard Worker  RTC_OBJC_TYPE(RTCVideoFrame) *videoFrame =
296*d9f75844SAndroid Build Coastguard Worker      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:rtcPixelBuffer
297*d9f75844SAndroid Build Coastguard Worker                                                  rotation:_rotation
298*d9f75844SAndroid Build Coastguard Worker                                               timeStampNs:timeStampNs];
299*d9f75844SAndroid Build Coastguard Worker  [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
300*d9f75844SAndroid Build Coastguard Worker}
301*d9f75844SAndroid Build Coastguard Worker
302*d9f75844SAndroid Build Coastguard Worker- (void)captureOutput:(AVCaptureOutput *)captureOutput
303*d9f75844SAndroid Build Coastguard Worker    didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
304*d9f75844SAndroid Build Coastguard Worker         fromConnection:(AVCaptureConnection *)connection {
305*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
306*d9f75844SAndroid Build Coastguard Worker  CFStringRef droppedReason =
307*d9f75844SAndroid Build Coastguard Worker      CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_DroppedFrameReason, nil);
308*d9f75844SAndroid Build Coastguard Worker#else
309*d9f75844SAndroid Build Coastguard Worker  // DroppedFrameReason unavailable on macOS.
310*d9f75844SAndroid Build Coastguard Worker  CFStringRef droppedReason = nil;
311*d9f75844SAndroid Build Coastguard Worker#endif
312*d9f75844SAndroid Build Coastguard Worker  RTCLogError(@"Dropped sample buffer. Reason: %@", (__bridge NSString *)droppedReason);
313*d9f75844SAndroid Build Coastguard Worker}
314*d9f75844SAndroid Build Coastguard Worker
315*d9f75844SAndroid Build Coastguard Worker#pragma mark - AVCaptureSession notifications
316*d9f75844SAndroid Build Coastguard Worker
317*d9f75844SAndroid Build Coastguard Worker- (void)handleCaptureSessionInterruption:(NSNotification *)notification {
318*d9f75844SAndroid Build Coastguard Worker  NSString *reasonString = nil;
319*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
320*d9f75844SAndroid Build Coastguard Worker  NSNumber *reason = notification.userInfo[AVCaptureSessionInterruptionReasonKey];
321*d9f75844SAndroid Build Coastguard Worker  if (reason) {
322*d9f75844SAndroid Build Coastguard Worker    switch (reason.intValue) {
323*d9f75844SAndroid Build Coastguard Worker      case AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableInBackground:
324*d9f75844SAndroid Build Coastguard Worker        reasonString = @"VideoDeviceNotAvailableInBackground";
325*d9f75844SAndroid Build Coastguard Worker        break;
326*d9f75844SAndroid Build Coastguard Worker      case AVCaptureSessionInterruptionReasonAudioDeviceInUseByAnotherClient:
327*d9f75844SAndroid Build Coastguard Worker        reasonString = @"AudioDeviceInUseByAnotherClient";
328*d9f75844SAndroid Build Coastguard Worker        break;
329*d9f75844SAndroid Build Coastguard Worker      case AVCaptureSessionInterruptionReasonVideoDeviceInUseByAnotherClient:
330*d9f75844SAndroid Build Coastguard Worker        reasonString = @"VideoDeviceInUseByAnotherClient";
331*d9f75844SAndroid Build Coastguard Worker        break;
332*d9f75844SAndroid Build Coastguard Worker      case AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultipleForegroundApps:
333*d9f75844SAndroid Build Coastguard Worker        reasonString = @"VideoDeviceNotAvailableWithMultipleForegroundApps";
334*d9f75844SAndroid Build Coastguard Worker        break;
335*d9f75844SAndroid Build Coastguard Worker    }
336*d9f75844SAndroid Build Coastguard Worker  }
337*d9f75844SAndroid Build Coastguard Worker#endif
338*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Capture session interrupted: %@", reasonString);
339*d9f75844SAndroid Build Coastguard Worker}
340*d9f75844SAndroid Build Coastguard Worker
341*d9f75844SAndroid Build Coastguard Worker- (void)handleCaptureSessionInterruptionEnded:(NSNotification *)notification {
342*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Capture session interruption ended.");
343*d9f75844SAndroid Build Coastguard Worker}
344*d9f75844SAndroid Build Coastguard Worker
345*d9f75844SAndroid Build Coastguard Worker- (void)handleCaptureSessionRuntimeError:(NSNotification *)notification {
346*d9f75844SAndroid Build Coastguard Worker  NSError *error = [notification.userInfo objectForKey:AVCaptureSessionErrorKey];
347*d9f75844SAndroid Build Coastguard Worker  RTCLogError(@"Capture session runtime error: %@", error);
348*d9f75844SAndroid Build Coastguard Worker
349*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
350*d9f75844SAndroid Build Coastguard Worker                                              block:^{
351*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
352*d9f75844SAndroid Build Coastguard Worker                                                if (error.code == AVErrorMediaServicesWereReset) {
353*d9f75844SAndroid Build Coastguard Worker                                                  [self handleNonFatalError];
354*d9f75844SAndroid Build Coastguard Worker                                                } else {
355*d9f75844SAndroid Build Coastguard Worker                                                  [self handleFatalError];
356*d9f75844SAndroid Build Coastguard Worker                                                }
357*d9f75844SAndroid Build Coastguard Worker#else
358*d9f75844SAndroid Build Coastguard Worker        [self handleFatalError];
359*d9f75844SAndroid Build Coastguard Worker#endif
360*d9f75844SAndroid Build Coastguard Worker                                              }];
361*d9f75844SAndroid Build Coastguard Worker}
362*d9f75844SAndroid Build Coastguard Worker
363*d9f75844SAndroid Build Coastguard Worker- (void)handleCaptureSessionDidStartRunning:(NSNotification *)notification {
364*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Capture session started.");
365*d9f75844SAndroid Build Coastguard Worker
366*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
367*d9f75844SAndroid Build Coastguard Worker                                              block:^{
368*d9f75844SAndroid Build Coastguard Worker                                                // If we successfully restarted after an unknown
369*d9f75844SAndroid Build Coastguard Worker                                                // error, allow future retries on fatal errors.
370*d9f75844SAndroid Build Coastguard Worker                                                self.hasRetriedOnFatalError = NO;
371*d9f75844SAndroid Build Coastguard Worker                                              }];
372*d9f75844SAndroid Build Coastguard Worker}
373*d9f75844SAndroid Build Coastguard Worker
374*d9f75844SAndroid Build Coastguard Worker- (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification {
375*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"Capture session stopped.");
376*d9f75844SAndroid Build Coastguard Worker}
377*d9f75844SAndroid Build Coastguard Worker
378*d9f75844SAndroid Build Coastguard Worker- (void)handleFatalError {
379*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher)
380*d9f75844SAndroid Build Coastguard Worker      dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
381*d9f75844SAndroid Build Coastguard Worker                    block:^{
382*d9f75844SAndroid Build Coastguard Worker                      if (!self.hasRetriedOnFatalError) {
383*d9f75844SAndroid Build Coastguard Worker                        RTCLogWarning(@"Attempting to recover from fatal capture error.");
384*d9f75844SAndroid Build Coastguard Worker                        [self handleNonFatalError];
385*d9f75844SAndroid Build Coastguard Worker                        self.hasRetriedOnFatalError = YES;
386*d9f75844SAndroid Build Coastguard Worker                      } else {
387*d9f75844SAndroid Build Coastguard Worker                        RTCLogError(@"Previous fatal error recovery failed.");
388*d9f75844SAndroid Build Coastguard Worker                      }
389*d9f75844SAndroid Build Coastguard Worker                    }];
390*d9f75844SAndroid Build Coastguard Worker}
391*d9f75844SAndroid Build Coastguard Worker
392*d9f75844SAndroid Build Coastguard Worker- (void)handleNonFatalError {
393*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
394*d9f75844SAndroid Build Coastguard Worker                                              block:^{
395*d9f75844SAndroid Build Coastguard Worker                                                RTCLog(@"Restarting capture session after error.");
396*d9f75844SAndroid Build Coastguard Worker                                                if (self.isRunning) {
397*d9f75844SAndroid Build Coastguard Worker                                                  [self.captureSession startRunning];
398*d9f75844SAndroid Build Coastguard Worker                                                }
399*d9f75844SAndroid Build Coastguard Worker                                              }];
400*d9f75844SAndroid Build Coastguard Worker}
401*d9f75844SAndroid Build Coastguard Worker
402*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
403*d9f75844SAndroid Build Coastguard Worker
404*d9f75844SAndroid Build Coastguard Worker#pragma mark - UIApplication notifications
405*d9f75844SAndroid Build Coastguard Worker
406*d9f75844SAndroid Build Coastguard Worker- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
407*d9f75844SAndroid Build Coastguard Worker  [RTC_OBJC_TYPE(RTCDispatcher)
408*d9f75844SAndroid Build Coastguard Worker      dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
409*d9f75844SAndroid Build Coastguard Worker                    block:^{
410*d9f75844SAndroid Build Coastguard Worker                      if (self.isRunning && !self.captureSession.isRunning) {
411*d9f75844SAndroid Build Coastguard Worker                        RTCLog(@"Restarting capture session on active.");
412*d9f75844SAndroid Build Coastguard Worker                        [self.captureSession startRunning];
413*d9f75844SAndroid Build Coastguard Worker                      }
414*d9f75844SAndroid Build Coastguard Worker                    }];
415*d9f75844SAndroid Build Coastguard Worker}
416*d9f75844SAndroid Build Coastguard Worker
417*d9f75844SAndroid Build Coastguard Worker#endif  // TARGET_OS_IPHONE
418*d9f75844SAndroid Build Coastguard Worker
419*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private
420*d9f75844SAndroid Build Coastguard Worker
421*d9f75844SAndroid Build Coastguard Worker- (dispatch_queue_t)frameQueue {
422*d9f75844SAndroid Build Coastguard Worker  if (!_frameQueue) {
423*d9f75844SAndroid Build Coastguard Worker    _frameQueue = RTCDispatchQueueCreateWithTarget(
424*d9f75844SAndroid Build Coastguard Worker        "org.webrtc.cameravideocapturer.video",
425*d9f75844SAndroid Build Coastguard Worker        DISPATCH_QUEUE_SERIAL,
426*d9f75844SAndroid Build Coastguard Worker        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
427*d9f75844SAndroid Build Coastguard Worker  }
428*d9f75844SAndroid Build Coastguard Worker  return _frameQueue;
429*d9f75844SAndroid Build Coastguard Worker}
430*d9f75844SAndroid Build Coastguard Worker
431*d9f75844SAndroid Build Coastguard Worker- (BOOL)setupCaptureSession:(AVCaptureSession *)captureSession {
432*d9f75844SAndroid Build Coastguard Worker  NSAssert(_captureSession == nil, @"Setup capture session called twice.");
433*d9f75844SAndroid Build Coastguard Worker  _captureSession = captureSession;
434*d9f75844SAndroid Build Coastguard Worker#if defined(WEBRTC_IOS)
435*d9f75844SAndroid Build Coastguard Worker  _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;
436*d9f75844SAndroid Build Coastguard Worker  _captureSession.usesApplicationAudioSession = NO;
437*d9f75844SAndroid Build Coastguard Worker#endif
438*d9f75844SAndroid Build Coastguard Worker  [self setupVideoDataOutput];
439*d9f75844SAndroid Build Coastguard Worker  // Add the output.
440*d9f75844SAndroid Build Coastguard Worker  if (![_captureSession canAddOutput:_videoDataOutput]) {
441*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Video data output unsupported.");
442*d9f75844SAndroid Build Coastguard Worker    return NO;
443*d9f75844SAndroid Build Coastguard Worker  }
444*d9f75844SAndroid Build Coastguard Worker  [_captureSession addOutput:_videoDataOutput];
445*d9f75844SAndroid Build Coastguard Worker
446*d9f75844SAndroid Build Coastguard Worker  return YES;
447*d9f75844SAndroid Build Coastguard Worker}
448*d9f75844SAndroid Build Coastguard Worker
449*d9f75844SAndroid Build Coastguard Worker- (void)setupVideoDataOutput {
450*d9f75844SAndroid Build Coastguard Worker  NSAssert(_videoDataOutput == nil, @"Setup video data output called twice.");
451*d9f75844SAndroid Build Coastguard Worker  AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
452*d9f75844SAndroid Build Coastguard Worker
453*d9f75844SAndroid Build Coastguard Worker  // `videoDataOutput.availableVideoCVPixelFormatTypes` returns the pixel formats supported by the
454*d9f75844SAndroid Build Coastguard Worker  // device with the most efficient output format first. Find the first format that we support.
455*d9f75844SAndroid Build Coastguard Worker  NSSet<NSNumber *> *supportedPixelFormats =
456*d9f75844SAndroid Build Coastguard Worker      [RTC_OBJC_TYPE(RTCCVPixelBuffer) supportedPixelFormats];
457*d9f75844SAndroid Build Coastguard Worker  NSMutableOrderedSet *availablePixelFormats =
458*d9f75844SAndroid Build Coastguard Worker      [NSMutableOrderedSet orderedSetWithArray:videoDataOutput.availableVideoCVPixelFormatTypes];
459*d9f75844SAndroid Build Coastguard Worker  [availablePixelFormats intersectSet:supportedPixelFormats];
460*d9f75844SAndroid Build Coastguard Worker  NSNumber *pixelFormat = availablePixelFormats.firstObject;
461*d9f75844SAndroid Build Coastguard Worker  NSAssert(pixelFormat, @"Output device has no supported formats.");
462*d9f75844SAndroid Build Coastguard Worker
463*d9f75844SAndroid Build Coastguard Worker  _preferredOutputPixelFormat = [pixelFormat unsignedIntValue];
464*d9f75844SAndroid Build Coastguard Worker  _outputPixelFormat = _preferredOutputPixelFormat;
465*d9f75844SAndroid Build Coastguard Worker  videoDataOutput.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey : pixelFormat};
466*d9f75844SAndroid Build Coastguard Worker  videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
467*d9f75844SAndroid Build Coastguard Worker  [videoDataOutput setSampleBufferDelegate:self queue:self.frameQueue];
468*d9f75844SAndroid Build Coastguard Worker  _videoDataOutput = videoDataOutput;
469*d9f75844SAndroid Build Coastguard Worker}
470*d9f75844SAndroid Build Coastguard Worker
471*d9f75844SAndroid Build Coastguard Worker- (void)updateVideoDataOutputPixelFormat:(AVCaptureDeviceFormat *)format {
472*d9f75844SAndroid Build Coastguard Worker  FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(format.formatDescription);
473*d9f75844SAndroid Build Coastguard Worker  if (![[RTC_OBJC_TYPE(RTCCVPixelBuffer) supportedPixelFormats] containsObject:@(mediaSubType)]) {
474*d9f75844SAndroid Build Coastguard Worker    mediaSubType = _preferredOutputPixelFormat;
475*d9f75844SAndroid Build Coastguard Worker  }
476*d9f75844SAndroid Build Coastguard Worker
477*d9f75844SAndroid Build Coastguard Worker  if (mediaSubType != _outputPixelFormat) {
478*d9f75844SAndroid Build Coastguard Worker    _outputPixelFormat = mediaSubType;
479*d9f75844SAndroid Build Coastguard Worker    _videoDataOutput.videoSettings =
480*d9f75844SAndroid Build Coastguard Worker        @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(mediaSubType) };
481*d9f75844SAndroid Build Coastguard Worker  }
482*d9f75844SAndroid Build Coastguard Worker}
483*d9f75844SAndroid Build Coastguard Worker
484*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private, called inside capture queue
485*d9f75844SAndroid Build Coastguard Worker
486*d9f75844SAndroid Build Coastguard Worker- (void)updateDeviceCaptureFormat:(AVCaptureDeviceFormat *)format fps:(NSInteger)fps {
487*d9f75844SAndroid Build Coastguard Worker  NSAssert([RTC_OBJC_TYPE(RTCDispatcher) isOnQueueForType:RTCDispatcherTypeCaptureSession],
488*d9f75844SAndroid Build Coastguard Worker           @"updateDeviceCaptureFormat must be called on the capture queue.");
489*d9f75844SAndroid Build Coastguard Worker  @try {
490*d9f75844SAndroid Build Coastguard Worker    _currentDevice.activeFormat = format;
491*d9f75844SAndroid Build Coastguard Worker    _currentDevice.activeVideoMinFrameDuration = CMTimeMake(1, fps);
492*d9f75844SAndroid Build Coastguard Worker  } @catch (NSException *exception) {
493*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Failed to set active format!\n User info:%@", exception.userInfo);
494*d9f75844SAndroid Build Coastguard Worker    return;
495*d9f75844SAndroid Build Coastguard Worker  }
496*d9f75844SAndroid Build Coastguard Worker}
497*d9f75844SAndroid Build Coastguard Worker
498*d9f75844SAndroid Build Coastguard Worker- (void)reconfigureCaptureSessionInput {
499*d9f75844SAndroid Build Coastguard Worker  NSAssert([RTC_OBJC_TYPE(RTCDispatcher) isOnQueueForType:RTCDispatcherTypeCaptureSession],
500*d9f75844SAndroid Build Coastguard Worker           @"reconfigureCaptureSessionInput must be called on the capture queue.");
501*d9f75844SAndroid Build Coastguard Worker  NSError *error = nil;
502*d9f75844SAndroid Build Coastguard Worker  AVCaptureDeviceInput *input =
503*d9f75844SAndroid Build Coastguard Worker      [AVCaptureDeviceInput deviceInputWithDevice:_currentDevice error:&error];
504*d9f75844SAndroid Build Coastguard Worker  if (!input) {
505*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Failed to create front camera input: %@", error.localizedDescription);
506*d9f75844SAndroid Build Coastguard Worker    return;
507*d9f75844SAndroid Build Coastguard Worker  }
508*d9f75844SAndroid Build Coastguard Worker  [_captureSession beginConfiguration];
509*d9f75844SAndroid Build Coastguard Worker  for (AVCaptureDeviceInput *oldInput in [_captureSession.inputs copy]) {
510*d9f75844SAndroid Build Coastguard Worker    [_captureSession removeInput:oldInput];
511*d9f75844SAndroid Build Coastguard Worker  }
512*d9f75844SAndroid Build Coastguard Worker  if ([_captureSession canAddInput:input]) {
513*d9f75844SAndroid Build Coastguard Worker    [_captureSession addInput:input];
514*d9f75844SAndroid Build Coastguard Worker  } else {
515*d9f75844SAndroid Build Coastguard Worker    RTCLogError(@"Cannot add camera as an input to the session.");
516*d9f75844SAndroid Build Coastguard Worker  }
517*d9f75844SAndroid Build Coastguard Worker  [_captureSession commitConfiguration];
518*d9f75844SAndroid Build Coastguard Worker}
519*d9f75844SAndroid Build Coastguard Worker
520*d9f75844SAndroid Build Coastguard Worker- (void)updateOrientation {
521*d9f75844SAndroid Build Coastguard Worker  NSAssert([RTC_OBJC_TYPE(RTCDispatcher) isOnQueueForType:RTCDispatcherTypeCaptureSession],
522*d9f75844SAndroid Build Coastguard Worker           @"updateOrientation must be called on the capture queue.");
523*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
524*d9f75844SAndroid Build Coastguard Worker  _orientation = [UIDevice currentDevice].orientation;
525*d9f75844SAndroid Build Coastguard Worker#endif
526*d9f75844SAndroid Build Coastguard Worker}
527*d9f75844SAndroid Build Coastguard Worker
528*d9f75844SAndroid Build Coastguard Worker@end
529