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