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