xref: /aosp_15_r20/external/webrtc/sdk/objc/unittests/ObjCVideoTrackSource_xctest.mm (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1/*
2 *  Copyright 2018 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 <Foundation/Foundation.h>
12#import <XCTest/XCTest.h>
13
14#include "sdk/objc/native/src/objc_video_track_source.h"
15
16#import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h"
17#import "base/RTCVideoFrame.h"
18#import "base/RTCVideoFrameBuffer.h"
19#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
20#import "frame_buffer_helpers.h"
21
22#include "api/scoped_refptr.h"
23#include "common_video/libyuv/include/webrtc_libyuv.h"
24#include "media/base/fake_video_renderer.h"
25#include "sdk/objc/native/api/video_frame.h"
26
27typedef void (^VideoSinkCallback)(RTC_OBJC_TYPE(RTCVideoFrame) *);
28
29namespace {
30
31class ObjCCallbackVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
32 public:
33  ObjCCallbackVideoSink(VideoSinkCallback callback) : callback_(callback) {}
34
35  void OnFrame(const webrtc::VideoFrame &frame) override {
36    callback_(NativeToObjCVideoFrame(frame));
37  }
38
39 private:
40  VideoSinkCallback callback_;
41};
42
43}  // namespace
44
45@interface ObjCVideoTrackSourceTests : XCTestCase
46@end
47
48@implementation ObjCVideoTrackSourceTests {
49  rtc::scoped_refptr<webrtc::ObjCVideoTrackSource> _video_source;
50}
51
52- (void)setUp {
53  _video_source = rtc::make_ref_counted<webrtc::ObjCVideoTrackSource>();
54}
55
56- (void)tearDown {
57  _video_source = NULL;
58}
59
60- (void)testOnCapturedFrameAdaptsFrame {
61  CVPixelBufferRef pixelBufferRef = NULL;
62  CVPixelBufferCreate(
63      NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
64
65  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
66      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
67
68  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
69      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
70                                                  rotation:RTCVideoRotation_0
71                                               timeStampNs:0];
72
73  cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer();
74  const rtc::VideoSinkWants video_sink_wants;
75  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
76  video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants);
77
78  _video_source->OnOutputFormatRequest(640, 360, 30);
79  _video_source->OnCapturedFrame(frame);
80
81  XCTAssertEqual(video_renderer->num_rendered_frames(), 1);
82  XCTAssertEqual(video_renderer->width(), 360);
83  XCTAssertEqual(video_renderer->height(), 640);
84
85  CVBufferRelease(pixelBufferRef);
86}
87
88- (void)testOnCapturedFrameAdaptsFrameWithAlignment {
89  // Requesting to adapt 1280x720 to 912x514 gives 639x360 without alignment. The 639 causes issues
90  // with some hardware encoders (e.g. HEVC) so in this test we verify that the alignment is set and
91  // respected.
92
93  CVPixelBufferRef pixelBufferRef = NULL;
94  CVPixelBufferCreate(
95      NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
96
97  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
98      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
99
100  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
101      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
102                                                  rotation:RTCVideoRotation_0
103                                               timeStampNs:0];
104
105  cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer();
106  const rtc::VideoSinkWants video_sink_wants;
107  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
108  video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants);
109
110  _video_source->OnOutputFormatRequest(912, 514, 30);
111  _video_source->OnCapturedFrame(frame);
112
113  XCTAssertEqual(video_renderer->num_rendered_frames(), 1);
114  XCTAssertEqual(video_renderer->width(), 360);
115  XCTAssertEqual(video_renderer->height(), 640);
116
117  CVBufferRelease(pixelBufferRef);
118}
119
120- (void)testOnCapturedFrameAdaptationResultsInCommonResolutions {
121  // Some of the most common resolutions used in the wild are 640x360, 480x270 and 320x180.
122  // Make sure that we properly scale down to exactly these resolutions.
123  CVPixelBufferRef pixelBufferRef = NULL;
124  CVPixelBufferCreate(
125      NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
126
127  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
128      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
129
130  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
131      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
132                                                  rotation:RTCVideoRotation_0
133                                               timeStampNs:0];
134
135  cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer();
136  const rtc::VideoSinkWants video_sink_wants;
137  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
138  video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants);
139
140  _video_source->OnOutputFormatRequest(640, 360, 30);
141  _video_source->OnCapturedFrame(frame);
142
143  XCTAssertEqual(video_renderer->num_rendered_frames(), 1);
144  XCTAssertEqual(video_renderer->width(), 360);
145  XCTAssertEqual(video_renderer->height(), 640);
146
147  _video_source->OnOutputFormatRequest(480, 270, 30);
148  _video_source->OnCapturedFrame(frame);
149
150  XCTAssertEqual(video_renderer->num_rendered_frames(), 2);
151  XCTAssertEqual(video_renderer->width(), 270);
152  XCTAssertEqual(video_renderer->height(), 480);
153
154  _video_source->OnOutputFormatRequest(320, 180, 30);
155  _video_source->OnCapturedFrame(frame);
156
157  XCTAssertEqual(video_renderer->num_rendered_frames(), 3);
158  XCTAssertEqual(video_renderer->width(), 180);
159  XCTAssertEqual(video_renderer->height(), 320);
160
161  CVBufferRelease(pixelBufferRef);
162}
163
164- (void)testOnCapturedFrameWithoutAdaptation {
165  CVPixelBufferRef pixelBufferRef = NULL;
166  CVPixelBufferCreate(
167      NULL, 360, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
168
169  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
170      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
171  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
172      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
173                                                  rotation:RTCVideoRotation_0
174                                               timeStampNs:0];
175
176  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
177  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
178    XCTAssertEqual(frame.width, outputFrame.width);
179    XCTAssertEqual(frame.height, outputFrame.height);
180
181    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
182    XCTAssertEqual(buffer.cropX, outputBuffer.cropX);
183    XCTAssertEqual(buffer.cropY, outputBuffer.cropY);
184    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
185
186    [callbackExpectation fulfill];
187  });
188
189  const rtc::VideoSinkWants video_sink_wants;
190  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
191  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
192
193  _video_source->OnOutputFormatRequest(640, 360, 30);
194  _video_source->OnCapturedFrame(frame);
195
196  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
197  CVBufferRelease(pixelBufferRef);
198}
199
200- (void)testOnCapturedFrameCVPixelBufferNeedsAdaptation {
201  CVPixelBufferRef pixelBufferRef = NULL;
202  CVPixelBufferCreate(
203      NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
204
205  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
206      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
207  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
208      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
209                                                  rotation:RTCVideoRotation_0
210                                               timeStampNs:0];
211
212  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
213  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
214    XCTAssertEqual(outputFrame.width, 360);
215    XCTAssertEqual(outputFrame.height, 640);
216
217    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
218    XCTAssertEqual(outputBuffer.cropX, 0);
219    XCTAssertEqual(outputBuffer.cropY, 0);
220    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
221
222    [callbackExpectation fulfill];
223  });
224
225  const rtc::VideoSinkWants video_sink_wants;
226  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
227  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
228
229  _video_source->OnOutputFormatRequest(640, 360, 30);
230  _video_source->OnCapturedFrame(frame);
231
232  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
233  CVBufferRelease(pixelBufferRef);
234}
235
236- (void)testOnCapturedFrameCVPixelBufferNeedsCropping {
237  CVPixelBufferRef pixelBufferRef = NULL;
238  CVPixelBufferCreate(
239      NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
240
241  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
242      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef];
243  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
244      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
245                                                  rotation:RTCVideoRotation_0
246                                               timeStampNs:0];
247
248  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
249  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
250    XCTAssertEqual(outputFrame.width, 360);
251    XCTAssertEqual(outputFrame.height, 640);
252
253    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
254    XCTAssertEqual(outputBuffer.cropX, 10);
255    XCTAssertEqual(outputBuffer.cropY, 0);
256    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
257
258    [callbackExpectation fulfill];
259  });
260
261  const rtc::VideoSinkWants video_sink_wants;
262  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
263  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
264
265  _video_source->OnOutputFormatRequest(640, 360, 30);
266  _video_source->OnCapturedFrame(frame);
267
268  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
269  CVBufferRelease(pixelBufferRef);
270}
271
272- (void)testOnCapturedFramePreAdaptedCVPixelBufferNeedsAdaptation {
273  CVPixelBufferRef pixelBufferRef = NULL;
274  CVPixelBufferCreate(
275      NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
276
277  // Create a frame that's already adapted down.
278  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
279      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef
280                                                      adaptedWidth:640
281                                                     adaptedHeight:360
282                                                         cropWidth:720
283                                                        cropHeight:1280
284                                                             cropX:0
285                                                             cropY:0];
286  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
287      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
288                                                  rotation:RTCVideoRotation_0
289                                               timeStampNs:0];
290
291  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
292  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
293    XCTAssertEqual(outputFrame.width, 480);
294    XCTAssertEqual(outputFrame.height, 270);
295
296    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
297    XCTAssertEqual(outputBuffer.cropX, 0);
298    XCTAssertEqual(outputBuffer.cropY, 0);
299    XCTAssertEqual(outputBuffer.cropWidth, 640);
300    XCTAssertEqual(outputBuffer.cropHeight, 360);
301    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
302
303    [callbackExpectation fulfill];
304  });
305
306  const rtc::VideoSinkWants video_sink_wants;
307  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
308  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
309
310  _video_source->OnOutputFormatRequest(480, 270, 30);
311  _video_source->OnCapturedFrame(frame);
312
313  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
314  CVBufferRelease(pixelBufferRef);
315}
316
317- (void)testOnCapturedFramePreCroppedCVPixelBufferNeedsCropping {
318  CVPixelBufferRef pixelBufferRef = NULL;
319  CVPixelBufferCreate(
320      NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
321
322  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
323      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef
324                                                      adaptedWidth:370
325                                                     adaptedHeight:640
326                                                         cropWidth:370
327                                                        cropHeight:640
328                                                             cropX:10
329                                                             cropY:0];
330  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
331      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
332                                                  rotation:RTCVideoRotation_0
333                                               timeStampNs:0];
334
335  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
336  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
337    XCTAssertEqual(outputFrame.width, 360);
338    XCTAssertEqual(outputFrame.height, 640);
339
340    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
341    XCTAssertEqual(outputBuffer.cropX, 14);
342    XCTAssertEqual(outputBuffer.cropY, 0);
343    XCTAssertEqual(outputBuffer.cropWidth, 360);
344    XCTAssertEqual(outputBuffer.cropHeight, 640);
345    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
346
347    [callbackExpectation fulfill];
348  });
349
350  const rtc::VideoSinkWants video_sink_wants;
351  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
352  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
353
354  _video_source->OnOutputFormatRequest(640, 360, 30);
355  _video_source->OnCapturedFrame(frame);
356
357  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
358  CVBufferRelease(pixelBufferRef);
359}
360
361- (void)testOnCapturedFrameSmallerPreCroppedCVPixelBufferNeedsCropping {
362  CVPixelBufferRef pixelBufferRef = NULL;
363  CVPixelBufferCreate(
364      NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
365
366  RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer =
367      [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef
368                                                      adaptedWidth:300
369                                                     adaptedHeight:640
370                                                         cropWidth:300
371                                                        cropHeight:640
372                                                             cropX:40
373                                                             cropY:0];
374  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
375      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
376                                                  rotation:RTCVideoRotation_0
377                                               timeStampNs:0];
378
379  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
380  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
381    XCTAssertEqual(outputFrame.width, 300);
382    XCTAssertEqual(outputFrame.height, 534);
383
384    RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer;
385    XCTAssertEqual(outputBuffer.cropX, 40);
386    XCTAssertEqual(outputBuffer.cropY, 52);
387    XCTAssertEqual(outputBuffer.cropWidth, 300);
388    XCTAssertEqual(outputBuffer.cropHeight, 534);
389    XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
390
391    [callbackExpectation fulfill];
392  });
393
394  const rtc::VideoSinkWants video_sink_wants;
395  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
396  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
397
398  _video_source->OnOutputFormatRequest(640, 360, 30);
399  _video_source->OnCapturedFrame(frame);
400
401  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
402  CVBufferRelease(pixelBufferRef);
403}
404
405- (void)testOnCapturedFrameI420BufferNeedsAdaptation {
406  rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(720, 1280);
407  RTC_OBJC_TYPE(RTCI420Buffer) *buffer =
408      [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer];
409  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
410      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
411                                                  rotation:RTCVideoRotation_0
412                                               timeStampNs:0];
413
414  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
415  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
416    XCTAssertEqual(outputFrame.width, 360);
417    XCTAssertEqual(outputFrame.height, 640);
418
419    RTC_OBJC_TYPE(RTCI420Buffer) *outputBuffer = (RTC_OBJC_TYPE(RTCI420Buffer) *)outputFrame.buffer;
420
421    double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]);
422    XCTAssertEqual(psnr, webrtc::kPerfectPSNR);
423
424    [callbackExpectation fulfill];
425  });
426
427  const rtc::VideoSinkWants video_sink_wants;
428  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
429  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
430
431  _video_source->OnOutputFormatRequest(640, 360, 30);
432  _video_source->OnCapturedFrame(frame);
433
434  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
435}
436
437- (void)testOnCapturedFrameI420BufferNeedsCropping {
438  rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(380, 640);
439  RTC_OBJC_TYPE(RTCI420Buffer) *buffer =
440      [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer];
441  RTC_OBJC_TYPE(RTCVideoFrame) *frame =
442      [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer
443                                                  rotation:RTCVideoRotation_0
444                                               timeStampNs:0];
445
446  XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
447  ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) {
448    XCTAssertEqual(outputFrame.width, 360);
449    XCTAssertEqual(outputFrame.height, 640);
450
451    RTC_OBJC_TYPE(RTCI420Buffer) *outputBuffer = (RTC_OBJC_TYPE(RTCI420Buffer) *)outputFrame.buffer;
452
453    double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]);
454    XCTAssertGreaterThanOrEqual(psnr, 40);
455
456    [callbackExpectation fulfill];
457  });
458
459  const rtc::VideoSinkWants video_sink_wants;
460  rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source.get();
461  video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
462
463  _video_source->OnOutputFormatRequest(640, 360, 30);
464  _video_source->OnCapturedFrame(frame);
465
466  [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
467}
468
469@end
470