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