1/* 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import <XCTest/XCTest.h> 12 13#import <Foundation/Foundation.h> 14#import <MetalKit/MetalKit.h> 15#import <OCMock/OCMock.h> 16 17#import "components/renderer/metal/RTCMTLVideoView.h" 18 19#import "api/video_frame_buffer/RTCNativeI420Buffer.h" 20#import "base/RTCVideoFrameBuffer.h" 21#import "components/renderer/metal/RTCMTLNV12Renderer.h" 22#import "components/video_frame_buffer/RTCCVPixelBuffer.h" 23 24static size_t kBufferWidth = 200; 25static size_t kBufferHeight = 200; 26 27// Extension of RTC_OBJC_TYPE(RTCMTLVideoView) for testing purposes. 28@interface RTC_OBJC_TYPE (RTCMTLVideoView) 29(Testing) 30 31 @property(nonatomic, readonly) MTKView *metalView; 32 33+ (BOOL)isMetalAvailable; 34+ (UIView *)createMetalView:(CGRect)frame; 35+ (id<RTCMTLRenderer>)createNV12Renderer; 36+ (id<RTCMTLRenderer>)createI420Renderer; 37- (void)drawInMTKView:(id)view; 38@end 39 40@interface RTCMTLVideoViewTests : XCTestCase 41@property(nonatomic, strong) id classMock; 42@property(nonatomic, strong) id rendererNV12Mock; 43@property(nonatomic, strong) id rendererI420Mock; 44@property(nonatomic, strong) id frameMock; 45@end 46 47@implementation RTCMTLVideoViewTests 48 49@synthesize classMock = _classMock; 50@synthesize rendererNV12Mock = _rendererNV12Mock; 51@synthesize rendererI420Mock = _rendererI420Mock; 52@synthesize frameMock = _frameMock; 53 54- (void)setUp { 55 self.classMock = OCMClassMock([RTC_OBJC_TYPE(RTCMTLVideoView) class]); 56 [self startMockingNilView]; 57} 58 59- (void)tearDown { 60 [self.classMock stopMocking]; 61 [self.rendererI420Mock stopMocking]; 62 [self.rendererNV12Mock stopMocking]; 63 [self.frameMock stopMocking]; 64 self.classMock = nil; 65 self.rendererI420Mock = nil; 66 self.rendererNV12Mock = nil; 67 self.frameMock = nil; 68} 69 70- (id)frameMockWithCVPixelBuffer:(BOOL)hasCVPixelBuffer { 71 id frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); 72 if (hasCVPixelBuffer) { 73 CVPixelBufferRef pixelBufferRef; 74 CVPixelBufferCreate(kCFAllocatorDefault, 75 kBufferWidth, 76 kBufferHeight, 77 kCVPixelFormatType_420YpCbCr8Planar, 78 nil, 79 &pixelBufferRef); 80 OCMStub([frameMock buffer]) 81 .andReturn([[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]); 82 } else { 83 OCMStub([frameMock buffer]) 84 .andReturn([[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithWidth:kBufferWidth 85 height:kBufferHeight]); 86 } 87 OCMStub([((RTC_OBJC_TYPE(RTCVideoFrame) *)frameMock) width]).andReturn(kBufferWidth); 88 OCMStub([((RTC_OBJC_TYPE(RTCVideoFrame) *)frameMock) height]).andReturn(kBufferHeight); 89 OCMStub([frameMock timeStampNs]).andReturn(arc4random_uniform(INT_MAX)); 90 return frameMock; 91} 92 93- (id)rendererMockWithSuccessfulSetup:(BOOL)success { 94 id rendererMock = OCMClassMock([RTCMTLRenderer class]); 95 OCMStub([rendererMock addRenderingDestination:[OCMArg any]]).andReturn(success); 96 return rendererMock; 97} 98 99- (void)startMockingNilView { 100 // Use OCMock 2 syntax here until OCMock is upgraded to 3.4 101 [[[self.classMock stub] andReturn:nil] createMetalView:CGRectZero]; 102} 103 104#pragma mark - Test cases 105 106- (void)testInitAssertsIfMetalUnavailabe { 107 // given 108 OCMStub([self.classMock isMetalAvailable]).andReturn(NO); 109 110 // when 111 BOOL asserts = NO; 112 @try { 113 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 114 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero]; 115 (void)realView; 116 } @catch (NSException *ex) { 117 asserts = YES; 118 } 119 120 XCTAssertTrue(asserts); 121} 122 123- (void)testRTCVideoRenderNilFrameCallback { 124 // given 125 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 126 127 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 128 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 129 self.frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); 130 131 [[self.frameMock reject] buffer]; 132 [[self.classMock reject] createNV12Renderer]; 133 [[self.classMock reject] createI420Renderer]; 134 135 // when 136 [realView renderFrame:nil]; 137 [realView drawInMTKView:realView.metalView]; 138 139 // then 140 [self.frameMock verify]; 141 [self.classMock verify]; 142} 143 144- (void)testRTCVideoRenderFrameCallbackI420 { 145 // given 146 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 147 self.rendererI420Mock = [self rendererMockWithSuccessfulSetup:YES]; 148 self.frameMock = [self frameMockWithCVPixelBuffer:NO]; 149 150 OCMExpect([self.rendererI420Mock drawFrame:self.frameMock]); 151 OCMExpect([self.classMock createI420Renderer]).andReturn(self.rendererI420Mock); 152 [[self.classMock reject] createNV12Renderer]; 153 154 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 155 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 156 157 // when 158 [realView renderFrame:self.frameMock]; 159 [realView drawInMTKView:realView.metalView]; 160 161 // then 162 [self.rendererI420Mock verify]; 163 [self.classMock verify]; 164} 165 166- (void)testRTCVideoRenderFrameCallbackNV12 { 167 // given 168 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 169 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 170 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 171 172 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 173 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 174 [[self.classMock reject] createI420Renderer]; 175 176 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 177 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 178 179 // when 180 [realView renderFrame:self.frameMock]; 181 [realView drawInMTKView:realView.metalView]; 182 183 // then 184 [self.rendererNV12Mock verify]; 185 [self.classMock verify]; 186} 187 188- (void)testRTCVideoRenderWorksAfterReconstruction { 189 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 190 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 191 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 192 193 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 194 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 195 [[self.classMock reject] createI420Renderer]; 196 197 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 198 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 199 200 [realView renderFrame:self.frameMock]; 201 [realView drawInMTKView:realView.metalView]; 202 [self.rendererNV12Mock verify]; 203 [self.classMock verify]; 204 205 // Recreate view. 206 realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 207 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 208 // View hould reinit renderer. 209 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 210 211 [realView renderFrame:self.frameMock]; 212 [realView drawInMTKView:realView.metalView]; 213 [self.rendererNV12Mock verify]; 214 [self.classMock verify]; 215} 216 217- (void)testDontRedrawOldFrame { 218 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 219 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 220 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 221 222 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 223 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 224 [[self.classMock reject] createI420Renderer]; 225 226 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 227 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 228 [realView renderFrame:self.frameMock]; 229 [realView drawInMTKView:realView.metalView]; 230 231 [self.rendererNV12Mock verify]; 232 [self.classMock verify]; 233 234 [[self.rendererNV12Mock reject] drawFrame:[OCMArg any]]; 235 236 [realView renderFrame:self.frameMock]; 237 [realView drawInMTKView:realView.metalView]; 238 239 [self.rendererNV12Mock verify]; 240} 241 242- (void)testDoDrawNewFrame { 243 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 244 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 245 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 246 247 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 248 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 249 [[self.classMock reject] createI420Renderer]; 250 251 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 252 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 253 [realView renderFrame:self.frameMock]; 254 [realView drawInMTKView:realView.metalView]; 255 256 [self.rendererNV12Mock verify]; 257 [self.classMock verify]; 258 259 // Get new frame. 260 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 261 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 262 263 [realView renderFrame:self.frameMock]; 264 [realView drawInMTKView:realView.metalView]; 265 266 [self.rendererNV12Mock verify]; 267} 268 269- (void)testReportsSizeChangesToDelegate { 270 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 271 272 id delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoViewDelegate))); 273 CGSize size = CGSizeMake(640, 480); 274 OCMExpect([delegateMock videoView:[OCMArg any] didChangeVideoSize:size]); 275 276 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 277 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 278 realView.delegate = delegateMock; 279 [realView setSize:size]; 280 281 // Delegate method is invoked with a dispatch_async. 282 OCMVerifyAllWithDelay(delegateMock, 1); 283} 284 285- (void)testSetContentMode { 286 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 287 id metalKitView = OCMClassMock([MTKView class]); 288 [[[[self.classMock stub] ignoringNonObjectArgs] andReturn:metalKitView] 289 createMetalView:CGRectZero]; 290 OCMExpect([metalKitView setContentMode:UIViewContentModeScaleAspectFill]); 291 292 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] init]; 293 [realView setVideoContentMode:UIViewContentModeScaleAspectFill]; 294 295 OCMVerify(metalKitView); 296} 297 298@end 299