xref: /aosp_15_r20/external/webrtc/sdk/objc/unittests/RTCMTLVideoView_xctest.m (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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