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