xref: /aosp_15_r20/external/webrtc/sdk/objc/unittests/RTCCameraVideoCapturerTests.mm (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 <OCMock/OCMock.h>
12*d9f75844SAndroid Build Coastguard Worker#import <XCTest/XCTest.h>
13*d9f75844SAndroid Build Coastguard Worker
14*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
15*d9f75844SAndroid Build Coastguard Worker#import <UIKit/UIKit.h>
16*d9f75844SAndroid Build Coastguard Worker#endif
17*d9f75844SAndroid Build Coastguard Worker
18*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/gunit.h"
19*d9f75844SAndroid Build Coastguard Worker
20*d9f75844SAndroid Build Coastguard Worker#import "base/RTCVideoFrame.h"
21*d9f75844SAndroid Build Coastguard Worker#import "components/capturer/RTCCameraVideoCapturer.h"
22*d9f75844SAndroid Build Coastguard Worker#import "helpers/AVCaptureSession+DevicePosition.h"
23*d9f75844SAndroid Build Coastguard Worker#import "helpers/RTCDispatcher.h"
24*d9f75844SAndroid Build Coastguard Worker#import "helpers/scoped_cftyperef.h"
25*d9f75844SAndroid Build Coastguard Worker
26*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
27*d9f75844SAndroid Build Coastguard Worker// Helper method.
28*d9f75844SAndroid Build Coastguard WorkerCMSampleBufferRef createTestSampleBufferRef() {
29*d9f75844SAndroid Build Coastguard Worker
30*d9f75844SAndroid Build Coastguard Worker  // This image is already in the testing bundle.
31*d9f75844SAndroid Build Coastguard Worker  UIImage *image = [UIImage imageNamed:@"Default.png"];
32*d9f75844SAndroid Build Coastguard Worker  CGSize size = image.size;
33*d9f75844SAndroid Build Coastguard Worker  CGImageRef imageRef = [image CGImage];
34*d9f75844SAndroid Build Coastguard Worker
35*d9f75844SAndroid Build Coastguard Worker  CVPixelBufferRef pixelBuffer = nullptr;
36*d9f75844SAndroid Build Coastguard Worker  CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, nil,
37*d9f75844SAndroid Build Coastguard Worker                      &pixelBuffer);
38*d9f75844SAndroid Build Coastguard Worker
39*d9f75844SAndroid Build Coastguard Worker  CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
40*d9f75844SAndroid Build Coastguard Worker  // We don't care about bitsPerComponent and bytesPerRow so arbitrary value of 8 for both.
41*d9f75844SAndroid Build Coastguard Worker  CGContextRef context = CGBitmapContextCreate(nil, size.width, size.height, 8, 8 * size.width,
42*d9f75844SAndroid Build Coastguard Worker                                               rgbColorSpace, kCGImageAlphaPremultipliedFirst);
43*d9f75844SAndroid Build Coastguard Worker
44*d9f75844SAndroid Build Coastguard Worker  CGContextDrawImage(
45*d9f75844SAndroid Build Coastguard Worker      context, CGRectMake(0, 0, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)), imageRef);
46*d9f75844SAndroid Build Coastguard Worker
47*d9f75844SAndroid Build Coastguard Worker  CGColorSpaceRelease(rgbColorSpace);
48*d9f75844SAndroid Build Coastguard Worker  CGContextRelease(context);
49*d9f75844SAndroid Build Coastguard Worker
50*d9f75844SAndroid Build Coastguard Worker  // We don't really care about the timing.
51*d9f75844SAndroid Build Coastguard Worker  CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};
52*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionRef description = nullptr;
53*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &description);
54*d9f75844SAndroid Build Coastguard Worker
55*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = nullptr;
56*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, YES, NULL, NULL, description,
57*d9f75844SAndroid Build Coastguard Worker                                     &timing, &sampleBuffer);
58*d9f75844SAndroid Build Coastguard Worker  CFRelease(pixelBuffer);
59*d9f75844SAndroid Build Coastguard Worker
60*d9f75844SAndroid Build Coastguard Worker  return sampleBuffer;
61*d9f75844SAndroid Build Coastguard Worker
62*d9f75844SAndroid Build Coastguard Worker}
63*d9f75844SAndroid Build Coastguard Worker#endif
64*d9f75844SAndroid Build Coastguard Worker@interface RTC_OBJC_TYPE (RTCCameraVideoCapturer)
65*d9f75844SAndroid Build Coastguard Worker(Tests)<AVCaptureVideoDataOutputSampleBufferDelegate> -
66*d9f75844SAndroid Build Coastguard Worker    (instancetype)initWithDelegate
67*d9f75844SAndroid Build Coastguard Worker    : (__weak id<RTC_OBJC_TYPE(RTCVideoCapturerDelegate)>)delegate captureSession
68*d9f75844SAndroid Build Coastguard Worker    : (AVCaptureSession *)captureSession;
69*d9f75844SAndroid Build Coastguard Worker@end
70*d9f75844SAndroid Build Coastguard Worker
71*d9f75844SAndroid Build Coastguard Worker@interface RTCCameraVideoCapturerTests : XCTestCase
72*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) id delegateMock;
73*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) id deviceMock;
74*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) id captureConnectionMock;
75*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) id captureSessionMock;
76*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) RTC_OBJC_TYPE(RTCCameraVideoCapturer) * capturer;
77*d9f75844SAndroid Build Coastguard Worker@end
78*d9f75844SAndroid Build Coastguard Worker
79*d9f75844SAndroid Build Coastguard Worker@implementation RTCCameraVideoCapturerTests
80*d9f75844SAndroid Build Coastguard Worker@synthesize delegateMock = _delegateMock;
81*d9f75844SAndroid Build Coastguard Worker@synthesize deviceMock = _deviceMock;
82*d9f75844SAndroid Build Coastguard Worker@synthesize captureConnectionMock = _captureConnectionMock;
83*d9f75844SAndroid Build Coastguard Worker@synthesize captureSessionMock = _captureSessionMock;
84*d9f75844SAndroid Build Coastguard Worker@synthesize capturer = _capturer;
85*d9f75844SAndroid Build Coastguard Worker
86*d9f75844SAndroid Build Coastguard Worker- (void)setup {
87*d9f75844SAndroid Build Coastguard Worker  self.delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoCapturerDelegate)));
88*d9f75844SAndroid Build Coastguard Worker  self.captureConnectionMock = OCMClassMock([AVCaptureConnection class]);
89*d9f75844SAndroid Build Coastguard Worker  self.capturer =
90*d9f75844SAndroid Build Coastguard Worker      [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:self.delegateMock];
91*d9f75844SAndroid Build Coastguard Worker  self.deviceMock = [self createDeviceMock];
92*d9f75844SAndroid Build Coastguard Worker}
93*d9f75844SAndroid Build Coastguard Worker
94*d9f75844SAndroid Build Coastguard Worker- (void)setupWithMockedCaptureSession {
95*d9f75844SAndroid Build Coastguard Worker  self.captureSessionMock = OCMStrictClassMock([AVCaptureSession class]);
96*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock setSessionPreset:[OCMArg any]]);
97*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock setUsesApplicationAudioSession:NO]);
98*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock canAddOutput:[OCMArg any]]).andReturn(YES);
99*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock addOutput:[OCMArg any]]);
100*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock beginConfiguration]);
101*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.captureSessionMock commitConfiguration]);
102*d9f75844SAndroid Build Coastguard Worker  self.delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoCapturerDelegate)));
103*d9f75844SAndroid Build Coastguard Worker  self.captureConnectionMock = OCMClassMock([AVCaptureConnection class]);
104*d9f75844SAndroid Build Coastguard Worker  self.capturer =
105*d9f75844SAndroid Build Coastguard Worker      [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:self.delegateMock
106*d9f75844SAndroid Build Coastguard Worker                                                       captureSession:self.captureSessionMock];
107*d9f75844SAndroid Build Coastguard Worker  self.deviceMock = [self createDeviceMock];
108*d9f75844SAndroid Build Coastguard Worker}
109*d9f75844SAndroid Build Coastguard Worker
110*d9f75844SAndroid Build Coastguard Worker- (void)tearDown {
111*d9f75844SAndroid Build Coastguard Worker  [self.delegateMock stopMocking];
112*d9f75844SAndroid Build Coastguard Worker  [self.deviceMock stopMocking];
113*d9f75844SAndroid Build Coastguard Worker  self.delegateMock = nil;
114*d9f75844SAndroid Build Coastguard Worker  self.deviceMock = nil;
115*d9f75844SAndroid Build Coastguard Worker  self.capturer = nil;
116*d9f75844SAndroid Build Coastguard Worker}
117*d9f75844SAndroid Build Coastguard Worker
118*d9f75844SAndroid Build Coastguard Worker#pragma mark - utils
119*d9f75844SAndroid Build Coastguard Worker
120*d9f75844SAndroid Build Coastguard Worker- (id)createDeviceMock {
121*d9f75844SAndroid Build Coastguard Worker  return OCMClassMock([AVCaptureDevice class]);
122*d9f75844SAndroid Build Coastguard Worker}
123*d9f75844SAndroid Build Coastguard Worker
124*d9f75844SAndroid Build Coastguard Worker#pragma mark - test cases
125*d9f75844SAndroid Build Coastguard Worker
126*d9f75844SAndroid Build Coastguard Worker- (void)testSetupSession {
127*d9f75844SAndroid Build Coastguard Worker  AVCaptureSession *session = self.capturer.captureSession;
128*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE(session != nil);
129*d9f75844SAndroid Build Coastguard Worker
130*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
131*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(session.sessionPreset, AVCaptureSessionPresetInputPriority);
132*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(session.usesApplicationAudioSession, NO);
133*d9f75844SAndroid Build Coastguard Worker#endif
134*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(session.outputs.count, 1u);
135*d9f75844SAndroid Build Coastguard Worker}
136*d9f75844SAndroid Build Coastguard Worker
137*d9f75844SAndroid Build Coastguard Worker- (void)testSetupSessionOutput {
138*d9f75844SAndroid Build Coastguard Worker  AVCaptureVideoDataOutput *videoOutput = self.capturer.captureSession.outputs[0];
139*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(videoOutput.alwaysDiscardsLateVideoFrames, NO);
140*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(videoOutput.sampleBufferDelegate, self.capturer);
141*d9f75844SAndroid Build Coastguard Worker}
142*d9f75844SAndroid Build Coastguard Worker
143*d9f75844SAndroid Build Coastguard Worker- (void)testSupportedFormatsForDevice {
144*d9f75844SAndroid Build Coastguard Worker  // given
145*d9f75844SAndroid Build Coastguard Worker  id validFormat1 = OCMClassMock([AVCaptureDeviceFormat class]);
146*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionRef format;
147*d9f75844SAndroid Build Coastguard Worker
148*d9f75844SAndroid Build Coastguard Worker  // We don't care about width and heigth so arbitrary 123 and 456 values.
149*d9f75844SAndroid Build Coastguard Worker  int width = 123;
150*d9f75844SAndroid Build Coastguard Worker  int height = 456;
151*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_420YpCbCr8PlanarFullRange, width, height,
152*d9f75844SAndroid Build Coastguard Worker                                 nil, &format);
153*d9f75844SAndroid Build Coastguard Worker  OCMStub([validFormat1 formatDescription]).andReturn(format);
154*d9f75844SAndroid Build Coastguard Worker
155*d9f75844SAndroid Build Coastguard Worker  id validFormat2 = OCMClassMock([AVCaptureDeviceFormat class]);
156*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, width,
157*d9f75844SAndroid Build Coastguard Worker                                 height, nil, &format);
158*d9f75844SAndroid Build Coastguard Worker  OCMStub([validFormat2 formatDescription]).andReturn(format);
159*d9f75844SAndroid Build Coastguard Worker
160*d9f75844SAndroid Build Coastguard Worker  id invalidFormat = OCMClassMock([AVCaptureDeviceFormat class]);
161*d9f75844SAndroid Build Coastguard Worker  CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_422YpCbCr8_yuvs, width, height, nil,
162*d9f75844SAndroid Build Coastguard Worker                                 &format);
163*d9f75844SAndroid Build Coastguard Worker  OCMStub([invalidFormat formatDescription]).andReturn(format);
164*d9f75844SAndroid Build Coastguard Worker
165*d9f75844SAndroid Build Coastguard Worker  NSArray *formats = @[ validFormat1, validFormat2, invalidFormat ];
166*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock formats]).andReturn(formats);
167*d9f75844SAndroid Build Coastguard Worker
168*d9f75844SAndroid Build Coastguard Worker  // when
169*d9f75844SAndroid Build Coastguard Worker  NSArray *supportedFormats =
170*d9f75844SAndroid Build Coastguard Worker      [RTC_OBJC_TYPE(RTCCameraVideoCapturer) supportedFormatsForDevice:self.deviceMock];
171*d9f75844SAndroid Build Coastguard Worker
172*d9f75844SAndroid Build Coastguard Worker  // then
173*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(supportedFormats.count, 3u);
174*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE([supportedFormats containsObject:validFormat1]);
175*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE([supportedFormats containsObject:validFormat2]);
176*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE([supportedFormats containsObject:invalidFormat]);
177*d9f75844SAndroid Build Coastguard Worker
178*d9f75844SAndroid Build Coastguard Worker  // cleanup
179*d9f75844SAndroid Build Coastguard Worker  [validFormat1 stopMocking];
180*d9f75844SAndroid Build Coastguard Worker  [validFormat2 stopMocking];
181*d9f75844SAndroid Build Coastguard Worker  [invalidFormat stopMocking];
182*d9f75844SAndroid Build Coastguard Worker  validFormat1 = nil;
183*d9f75844SAndroid Build Coastguard Worker  validFormat2 = nil;
184*d9f75844SAndroid Build Coastguard Worker  invalidFormat = nil;
185*d9f75844SAndroid Build Coastguard Worker}
186*d9f75844SAndroid Build Coastguard Worker
187*d9f75844SAndroid Build Coastguard Worker- (void)testDelegateCallbackNotCalledWhenInvalidBuffer {
188*d9f75844SAndroid Build Coastguard Worker  // given
189*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = nullptr;
190*d9f75844SAndroid Build Coastguard Worker  [[self.delegateMock reject] capturer:[OCMArg any] didCaptureVideoFrame:[OCMArg any]];
191*d9f75844SAndroid Build Coastguard Worker
192*d9f75844SAndroid Build Coastguard Worker  // when
193*d9f75844SAndroid Build Coastguard Worker  [self.capturer captureOutput:self.capturer.captureSession.outputs[0]
194*d9f75844SAndroid Build Coastguard Worker         didOutputSampleBuffer:sampleBuffer
195*d9f75844SAndroid Build Coastguard Worker                fromConnection:self.captureConnectionMock];
196*d9f75844SAndroid Build Coastguard Worker
197*d9f75844SAndroid Build Coastguard Worker  // then
198*d9f75844SAndroid Build Coastguard Worker  [self.delegateMock verify];
199*d9f75844SAndroid Build Coastguard Worker}
200*d9f75844SAndroid Build Coastguard Worker
201*d9f75844SAndroid Build Coastguard Worker
202*d9f75844SAndroid Build Coastguard Worker- (void)testDelegateCallbackWithValidBufferAndOrientationUpdate {
203*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
204*d9f75844SAndroid Build Coastguard Worker  [UIDevice.currentDevice setValue:@(UIDeviceOrientationPortraitUpsideDown) forKey:@"orientation"];
205*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = createTestSampleBufferRef();
206*d9f75844SAndroid Build Coastguard Worker
207*d9f75844SAndroid Build Coastguard Worker  // then
208*d9f75844SAndroid Build Coastguard Worker  [[self.delegateMock expect] capturer:self.capturer
209*d9f75844SAndroid Build Coastguard Worker                  didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) *
210*d9f75844SAndroid Build Coastguard Worker                                                                    expectedFrame) {
211*d9f75844SAndroid Build Coastguard Worker                    EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_270);
212*d9f75844SAndroid Build Coastguard Worker                    return YES;
213*d9f75844SAndroid Build Coastguard Worker                  }]];
214*d9f75844SAndroid Build Coastguard Worker
215*d9f75844SAndroid Build Coastguard Worker  // when
216*d9f75844SAndroid Build Coastguard Worker  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
217*d9f75844SAndroid Build Coastguard Worker  [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];
218*d9f75844SAndroid Build Coastguard Worker
219*d9f75844SAndroid Build Coastguard Worker  // We need to wait for the dispatch to finish.
220*d9f75844SAndroid Build Coastguard Worker  WAIT(0, 1000);
221*d9f75844SAndroid Build Coastguard Worker
222*d9f75844SAndroid Build Coastguard Worker  [self.capturer captureOutput:self.capturer.captureSession.outputs[0]
223*d9f75844SAndroid Build Coastguard Worker         didOutputSampleBuffer:sampleBuffer
224*d9f75844SAndroid Build Coastguard Worker                fromConnection:self.captureConnectionMock];
225*d9f75844SAndroid Build Coastguard Worker
226*d9f75844SAndroid Build Coastguard Worker  [self.delegateMock verify];
227*d9f75844SAndroid Build Coastguard Worker  CFRelease(sampleBuffer);
228*d9f75844SAndroid Build Coastguard Worker#endif
229*d9f75844SAndroid Build Coastguard Worker}
230*d9f75844SAndroid Build Coastguard Worker
231*d9f75844SAndroid Build Coastguard Worker- (void)testRotationCamera:(AVCaptureDevicePosition)camera
232*d9f75844SAndroid Build Coastguard Worker           withOrientation:(UIDeviceOrientation)deviceOrientation {
233*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
234*d9f75844SAndroid Build Coastguard Worker  // Mock the AVCaptureConnection as we will get the camera position from the connection's
235*d9f75844SAndroid Build Coastguard Worker  // input ports.
236*d9f75844SAndroid Build Coastguard Worker  AVCaptureDeviceInput *inputPortMock = OCMClassMock([AVCaptureDeviceInput class]);
237*d9f75844SAndroid Build Coastguard Worker  AVCaptureInputPort *captureInputPort = OCMClassMock([AVCaptureInputPort class]);
238*d9f75844SAndroid Build Coastguard Worker  NSArray *inputPortsArrayMock = @[captureInputPort];
239*d9f75844SAndroid Build Coastguard Worker  AVCaptureDevice *captureDeviceMock = OCMClassMock([AVCaptureDevice class]);
240*d9f75844SAndroid Build Coastguard Worker  OCMStub(((AVCaptureConnection *)self.captureConnectionMock).inputPorts).
241*d9f75844SAndroid Build Coastguard Worker      andReturn(inputPortsArrayMock);
242*d9f75844SAndroid Build Coastguard Worker  OCMStub(captureInputPort.input).andReturn(inputPortMock);
243*d9f75844SAndroid Build Coastguard Worker  OCMStub(inputPortMock.device).andReturn(captureDeviceMock);
244*d9f75844SAndroid Build Coastguard Worker  OCMStub(captureDeviceMock.position).andReturn(camera);
245*d9f75844SAndroid Build Coastguard Worker
246*d9f75844SAndroid Build Coastguard Worker  [UIDevice.currentDevice setValue:@(deviceOrientation) forKey:@"orientation"];
247*d9f75844SAndroid Build Coastguard Worker
248*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = createTestSampleBufferRef();
249*d9f75844SAndroid Build Coastguard Worker
250*d9f75844SAndroid Build Coastguard Worker  [[self.delegateMock expect] capturer:self.capturer
251*d9f75844SAndroid Build Coastguard Worker                  didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) *
252*d9f75844SAndroid Build Coastguard Worker                                                                    expectedFrame) {
253*d9f75844SAndroid Build Coastguard Worker                    if (camera == AVCaptureDevicePositionFront) {
254*d9f75844SAndroid Build Coastguard Worker                      if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
255*d9f75844SAndroid Build Coastguard Worker                        EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_180);
256*d9f75844SAndroid Build Coastguard Worker                      } else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
257*d9f75844SAndroid Build Coastguard Worker                        EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_0);
258*d9f75844SAndroid Build Coastguard Worker                      }
259*d9f75844SAndroid Build Coastguard Worker                    } else if (camera == AVCaptureDevicePositionBack) {
260*d9f75844SAndroid Build Coastguard Worker                      if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
261*d9f75844SAndroid Build Coastguard Worker                        EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_0);
262*d9f75844SAndroid Build Coastguard Worker                      } else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
263*d9f75844SAndroid Build Coastguard Worker                        EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_180);
264*d9f75844SAndroid Build Coastguard Worker                      }
265*d9f75844SAndroid Build Coastguard Worker                    }
266*d9f75844SAndroid Build Coastguard Worker                    return YES;
267*d9f75844SAndroid Build Coastguard Worker                  }]];
268*d9f75844SAndroid Build Coastguard Worker
269*d9f75844SAndroid Build Coastguard Worker  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
270*d9f75844SAndroid Build Coastguard Worker  [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];
271*d9f75844SAndroid Build Coastguard Worker
272*d9f75844SAndroid Build Coastguard Worker  // We need to wait for the dispatch to finish.
273*d9f75844SAndroid Build Coastguard Worker  WAIT(0, 1000);
274*d9f75844SAndroid Build Coastguard Worker
275*d9f75844SAndroid Build Coastguard Worker  [self.capturer captureOutput:self.capturer.captureSession.outputs[0]
276*d9f75844SAndroid Build Coastguard Worker         didOutputSampleBuffer:sampleBuffer
277*d9f75844SAndroid Build Coastguard Worker                fromConnection:self.captureConnectionMock];
278*d9f75844SAndroid Build Coastguard Worker
279*d9f75844SAndroid Build Coastguard Worker  [self.delegateMock verify];
280*d9f75844SAndroid Build Coastguard Worker
281*d9f75844SAndroid Build Coastguard Worker  CFRelease(sampleBuffer);
282*d9f75844SAndroid Build Coastguard Worker#endif
283*d9f75844SAndroid Build Coastguard Worker}
284*d9f75844SAndroid Build Coastguard Worker
285*d9f75844SAndroid Build Coastguard Worker- (void)setExif:(CMSampleBufferRef)sampleBuffer {
286*d9f75844SAndroid Build Coastguard Worker  rtc::ScopedCFTypeRef<CFMutableDictionaryRef> exif(CFDictionaryCreateMutable(
287*d9f75844SAndroid Build Coastguard Worker      kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
288*d9f75844SAndroid Build Coastguard Worker  CFDictionarySetValue(exif.get(), CFSTR("LensModel"), CFSTR("iPhone SE back camera 4.15mm f/2.2"));
289*d9f75844SAndroid Build Coastguard Worker  CMSetAttachment(sampleBuffer, CFSTR("{Exif}"), exif.get(), kCMAttachmentMode_ShouldPropagate);
290*d9f75844SAndroid Build Coastguard Worker}
291*d9f75844SAndroid Build Coastguard Worker
292*d9f75844SAndroid Build Coastguard Worker- (void)testRotationFrame {
293*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
294*d9f75844SAndroid Build Coastguard Worker  // Mock the AVCaptureConnection as we will get the camera position from the connection's
295*d9f75844SAndroid Build Coastguard Worker  // input ports.
296*d9f75844SAndroid Build Coastguard Worker  AVCaptureDeviceInput *inputPortMock = OCMClassMock([AVCaptureDeviceInput class]);
297*d9f75844SAndroid Build Coastguard Worker  AVCaptureInputPort *captureInputPort = OCMClassMock([AVCaptureInputPort class]);
298*d9f75844SAndroid Build Coastguard Worker  NSArray *inputPortsArrayMock = @[captureInputPort];
299*d9f75844SAndroid Build Coastguard Worker  AVCaptureDevice *captureDeviceMock = OCMClassMock([AVCaptureDevice class]);
300*d9f75844SAndroid Build Coastguard Worker  OCMStub(((AVCaptureConnection *)self.captureConnectionMock).inputPorts).
301*d9f75844SAndroid Build Coastguard Worker      andReturn(inputPortsArrayMock);
302*d9f75844SAndroid Build Coastguard Worker  OCMStub(captureInputPort.input).andReturn(inputPortMock);
303*d9f75844SAndroid Build Coastguard Worker  OCMStub(inputPortMock.device).andReturn(captureDeviceMock);
304*d9f75844SAndroid Build Coastguard Worker  OCMStub(captureDeviceMock.position).andReturn(AVCaptureDevicePositionFront);
305*d9f75844SAndroid Build Coastguard Worker
306*d9f75844SAndroid Build Coastguard Worker  [UIDevice.currentDevice setValue:@(UIDeviceOrientationLandscapeLeft) forKey:@"orientation"];
307*d9f75844SAndroid Build Coastguard Worker
308*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = createTestSampleBufferRef();
309*d9f75844SAndroid Build Coastguard Worker
310*d9f75844SAndroid Build Coastguard Worker  [[self.delegateMock expect] capturer:self.capturer
311*d9f75844SAndroid Build Coastguard Worker                  didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) *
312*d9f75844SAndroid Build Coastguard Worker                                                                    expectedFrame) {
313*d9f75844SAndroid Build Coastguard Worker                    // Front camera and landscape left should return 180. But the frame's exif
314*d9f75844SAndroid Build Coastguard Worker                    // we add below says its from the back camera, so rotation should be 0.
315*d9f75844SAndroid Build Coastguard Worker                    EXPECT_EQ(expectedFrame.rotation, RTCVideoRotation_0);
316*d9f75844SAndroid Build Coastguard Worker                    return YES;
317*d9f75844SAndroid Build Coastguard Worker                  }]];
318*d9f75844SAndroid Build Coastguard Worker
319*d9f75844SAndroid Build Coastguard Worker  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
320*d9f75844SAndroid Build Coastguard Worker  [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];
321*d9f75844SAndroid Build Coastguard Worker
322*d9f75844SAndroid Build Coastguard Worker  // We need to wait for the dispatch to finish.
323*d9f75844SAndroid Build Coastguard Worker  WAIT(0, 1000);
324*d9f75844SAndroid Build Coastguard Worker
325*d9f75844SAndroid Build Coastguard Worker  [self setExif:sampleBuffer];
326*d9f75844SAndroid Build Coastguard Worker
327*d9f75844SAndroid Build Coastguard Worker  [self.capturer captureOutput:self.capturer.captureSession.outputs[0]
328*d9f75844SAndroid Build Coastguard Worker         didOutputSampleBuffer:sampleBuffer
329*d9f75844SAndroid Build Coastguard Worker                fromConnection:self.captureConnectionMock];
330*d9f75844SAndroid Build Coastguard Worker
331*d9f75844SAndroid Build Coastguard Worker  [self.delegateMock verify];
332*d9f75844SAndroid Build Coastguard Worker  CFRelease(sampleBuffer);
333*d9f75844SAndroid Build Coastguard Worker#endif
334*d9f75844SAndroid Build Coastguard Worker}
335*d9f75844SAndroid Build Coastguard Worker
336*d9f75844SAndroid Build Coastguard Worker- (void)testImageExif {
337*d9f75844SAndroid Build Coastguard Worker#if TARGET_OS_IPHONE
338*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = createTestSampleBufferRef();
339*d9f75844SAndroid Build Coastguard Worker  [self setExif:sampleBuffer];
340*d9f75844SAndroid Build Coastguard Worker
341*d9f75844SAndroid Build Coastguard Worker  AVCaptureDevicePosition cameraPosition = [AVCaptureSession
342*d9f75844SAndroid Build Coastguard Worker                                            devicePositionForSampleBuffer:sampleBuffer];
343*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(cameraPosition, AVCaptureDevicePositionBack);
344*d9f75844SAndroid Build Coastguard Worker#endif
345*d9f75844SAndroid Build Coastguard Worker}
346*d9f75844SAndroid Build Coastguard Worker
347*d9f75844SAndroid Build Coastguard Worker- (void)testStartingAndStoppingCapture {
348*d9f75844SAndroid Build Coastguard Worker  id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
349*d9f75844SAndroid Build Coastguard Worker  id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
350*d9f75844SAndroid Build Coastguard Worker  OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]])
351*d9f75844SAndroid Build Coastguard Worker      .andReturn(expectedDeviceInputMock);
352*d9f75844SAndroid Build Coastguard Worker
353*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES);
354*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock unlockForConfiguration]);
355*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES);
356*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]);
357*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]);
358*d9f75844SAndroid Build Coastguard Worker
359*d9f75844SAndroid Build Coastguard Worker  // Set expectation that the capture session should be started with correct device.
360*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]);
361*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock startRunning]);
362*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock stopRunning]);
363*d9f75844SAndroid Build Coastguard Worker
364*d9f75844SAndroid Build Coastguard Worker  id format = OCMClassMock([AVCaptureDeviceFormat class]);
365*d9f75844SAndroid Build Coastguard Worker  [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30];
366*d9f75844SAndroid Build Coastguard Worker  [self.capturer stopCapture];
367*d9f75844SAndroid Build Coastguard Worker
368*d9f75844SAndroid Build Coastguard Worker  // Start capture code is dispatched async.
369*d9f75844SAndroid Build Coastguard Worker  OCMVerifyAllWithDelay(_captureSessionMock, 15);
370*d9f75844SAndroid Build Coastguard Worker}
371*d9f75844SAndroid Build Coastguard Worker
372*d9f75844SAndroid Build Coastguard Worker- (void)testStartCaptureFailingToLockForConfiguration {
373*d9f75844SAndroid Build Coastguard Worker  // The captureSessionMock is a strict mock, so this test will crash if the startCapture
374*d9f75844SAndroid Build Coastguard Worker  // method does not return when failing to lock for configuration.
375*d9f75844SAndroid Build Coastguard Worker  OCMExpect([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(NO);
376*d9f75844SAndroid Build Coastguard Worker
377*d9f75844SAndroid Build Coastguard Worker  id format = OCMClassMock([AVCaptureDeviceFormat class]);
378*d9f75844SAndroid Build Coastguard Worker  [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30];
379*d9f75844SAndroid Build Coastguard Worker
380*d9f75844SAndroid Build Coastguard Worker  // Start capture code is dispatched async.
381*d9f75844SAndroid Build Coastguard Worker  OCMVerifyAllWithDelay(self.deviceMock, 15);
382*d9f75844SAndroid Build Coastguard Worker}
383*d9f75844SAndroid Build Coastguard Worker
384*d9f75844SAndroid Build Coastguard Worker- (void)testStartingAndStoppingCaptureWithCallbacks {
385*d9f75844SAndroid Build Coastguard Worker  id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
386*d9f75844SAndroid Build Coastguard Worker  id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
387*d9f75844SAndroid Build Coastguard Worker  OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]])
388*d9f75844SAndroid Build Coastguard Worker      .andReturn(expectedDeviceInputMock);
389*d9f75844SAndroid Build Coastguard Worker
390*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES);
391*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock unlockForConfiguration]);
392*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES);
393*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]);
394*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]);
395*d9f75844SAndroid Build Coastguard Worker
396*d9f75844SAndroid Build Coastguard Worker  // Set expectation that the capture session should be started with correct device.
397*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]);
398*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock startRunning]);
399*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock stopRunning]);
400*d9f75844SAndroid Build Coastguard Worker
401*d9f75844SAndroid Build Coastguard Worker  dispatch_semaphore_t completedStopSemaphore = dispatch_semaphore_create(0);
402*d9f75844SAndroid Build Coastguard Worker
403*d9f75844SAndroid Build Coastguard Worker  __block BOOL completedStart = NO;
404*d9f75844SAndroid Build Coastguard Worker  id format = OCMClassMock([AVCaptureDeviceFormat class]);
405*d9f75844SAndroid Build Coastguard Worker  [self.capturer startCaptureWithDevice:self.deviceMock
406*d9f75844SAndroid Build Coastguard Worker                                 format:format
407*d9f75844SAndroid Build Coastguard Worker                                    fps:30
408*d9f75844SAndroid Build Coastguard Worker                      completionHandler:^(NSError *error) {
409*d9f75844SAndroid Build Coastguard Worker                        EXPECT_EQ(error, nil);
410*d9f75844SAndroid Build Coastguard Worker                        completedStart = YES;
411*d9f75844SAndroid Build Coastguard Worker                      }];
412*d9f75844SAndroid Build Coastguard Worker
413*d9f75844SAndroid Build Coastguard Worker  __block BOOL completedStop = NO;
414*d9f75844SAndroid Build Coastguard Worker  [self.capturer stopCaptureWithCompletionHandler:^{
415*d9f75844SAndroid Build Coastguard Worker    completedStop = YES;
416*d9f75844SAndroid Build Coastguard Worker    dispatch_semaphore_signal(completedStopSemaphore);
417*d9f75844SAndroid Build Coastguard Worker  }];
418*d9f75844SAndroid Build Coastguard Worker
419*d9f75844SAndroid Build Coastguard Worker  dispatch_semaphore_wait(completedStopSemaphore,
420*d9f75844SAndroid Build Coastguard Worker                          dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC));
421*d9f75844SAndroid Build Coastguard Worker  OCMVerifyAllWithDelay(_captureSessionMock, 15);
422*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE(completedStart);
423*d9f75844SAndroid Build Coastguard Worker  EXPECT_TRUE(completedStop);
424*d9f75844SAndroid Build Coastguard Worker}
425*d9f75844SAndroid Build Coastguard Worker
426*d9f75844SAndroid Build Coastguard Worker- (void)testStartCaptureFailingToLockForConfigurationWithCallback {
427*d9f75844SAndroid Build Coastguard Worker  id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
428*d9f75844SAndroid Build Coastguard Worker  id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
429*d9f75844SAndroid Build Coastguard Worker  OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]])
430*d9f75844SAndroid Build Coastguard Worker      .andReturn(expectedDeviceInputMock);
431*d9f75844SAndroid Build Coastguard Worker
432*d9f75844SAndroid Build Coastguard Worker  id errorMock = OCMClassMock([NSError class]);
433*d9f75844SAndroid Build Coastguard Worker
434*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:errorMock]]).andReturn(NO);
435*d9f75844SAndroid Build Coastguard Worker  OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES);
436*d9f75844SAndroid Build Coastguard Worker  OCMStub([self.deviceMock unlockForConfiguration]);
437*d9f75844SAndroid Build Coastguard Worker
438*d9f75844SAndroid Build Coastguard Worker  OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]);
439*d9f75844SAndroid Build Coastguard Worker
440*d9f75844SAndroid Build Coastguard Worker  dispatch_semaphore_t completedStartSemaphore = dispatch_semaphore_create(0);
441*d9f75844SAndroid Build Coastguard Worker  __block NSError *callbackError = nil;
442*d9f75844SAndroid Build Coastguard Worker
443*d9f75844SAndroid Build Coastguard Worker  id format = OCMClassMock([AVCaptureDeviceFormat class]);
444*d9f75844SAndroid Build Coastguard Worker  [self.capturer startCaptureWithDevice:self.deviceMock
445*d9f75844SAndroid Build Coastguard Worker                                 format:format
446*d9f75844SAndroid Build Coastguard Worker                                    fps:30
447*d9f75844SAndroid Build Coastguard Worker                      completionHandler:^(NSError *error) {
448*d9f75844SAndroid Build Coastguard Worker                        callbackError = error;
449*d9f75844SAndroid Build Coastguard Worker                        dispatch_semaphore_signal(completedStartSemaphore);
450*d9f75844SAndroid Build Coastguard Worker                      }];
451*d9f75844SAndroid Build Coastguard Worker
452*d9f75844SAndroid Build Coastguard Worker  long ret = dispatch_semaphore_wait(completedStartSemaphore,
453*d9f75844SAndroid Build Coastguard Worker                                     dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC));
454*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(ret, 0);
455*d9f75844SAndroid Build Coastguard Worker  EXPECT_EQ(callbackError, errorMock);
456*d9f75844SAndroid Build Coastguard Worker}
457*d9f75844SAndroid Build Coastguard Worker
458*d9f75844SAndroid Build Coastguard Worker@end
459