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