blob: 8c940e44c39a5f1cc3b8461353cab3c875268647 [file] [log] [blame]
Anders Carlssonfe9d8172018-04-03 11:40:39 +02001/*
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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020014#include "sdk/objc/native/src/objc_video_track_source.h"
Anders Carlssonfe9d8172018-04-03 11:40:39 +020015
Anders Carlsson4e5af962018-09-03 14:44:50 +020016#import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020017#import "base/RTCVideoFrame.h"
18#import "base/RTCVideoFrameBuffer.h"
19#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
20#import "frame_buffer_helpers.h"
21
Mirko Bonadeid9708072019-01-25 20:26:48 +010022#include "api/scoped_refptr.h"
Anders Carlssonfe9d8172018-04-03 11:40:39 +020023#include "common_video/libyuv/include/webrtc_libyuv.h"
Steve Anton10542f22019-01-11 09:11:00 -080024#include "media/base/fake_video_renderer.h"
25#include "rtc_base/ref_counted_object.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020026#include "sdk/objc/native/api/video_frame.h"
Anders Carlssonfe9d8172018-04-03 11:40:39 +020027
28typedef void (^VideoSinkCallback)(RTCVideoFrame *);
29
30namespace {
31
32class ObjCCallbackVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
33 public:
34 ObjCCallbackVideoSink(VideoSinkCallback callback) : callback_(callback) {}
35
Mirko Bonadei17aff352018-07-26 12:20:40 +020036 void OnFrame(const webrtc::VideoFrame &frame) override {
Anders Carlssonfe9d8172018-04-03 11:40:39 +020037 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 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef];
67
68 RTCVideoFrame *frame =
69 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
70
71 cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer();
72 const rtc::VideoSinkWants video_sink_wants;
73 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
74 video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants);
75
76 _video_source->OnOutputFormatRequest(640, 360, 30);
77 _video_source->OnCapturedFrame(frame);
78
79 XCTAssertEqual(video_renderer->num_rendered_frames(), 1);
80 XCTAssertEqual(video_renderer->width(), 360);
81 XCTAssertEqual(video_renderer->height(), 640);
82
83 CVBufferRelease(pixelBufferRef);
84}
85
Peter Hanspers488eb982018-06-08 14:49:51 +020086- (void)testOnCapturedFrameAdaptsFrameWithAlignment {
87 // Requesting to adapt 1280x720 to 912x514 gives 639x360 without alignment. The 639 causes issues
88 // with some hardware encoders (e.g. HEVC) so in this test we verify that the alignment is set and
89 // respected.
90
91 CVPixelBufferRef pixelBufferRef = NULL;
92 CVPixelBufferCreate(
93 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
94
95 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef];
96
97 RTCVideoFrame *frame =
98 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
99
100 cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer();
101 const rtc::VideoSinkWants video_sink_wants;
102 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
103 video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants);
104
105 _video_source->OnOutputFormatRequest(912, 514, 30);
106 _video_source->OnCapturedFrame(frame);
107
108 XCTAssertEqual(video_renderer->num_rendered_frames(), 1);
109 XCTAssertEqual(video_renderer->width(), 360);
110 XCTAssertEqual(video_renderer->height(), 640);
111
112 CVBufferRelease(pixelBufferRef);
113}
114
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200115- (void)testOnCapturedFrameWithoutAdaptation {
116 CVPixelBufferRef pixelBufferRef = NULL;
117 CVPixelBufferCreate(
118 NULL, 360, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
119
120 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef];
121 RTCVideoFrame *frame =
122 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
123
124 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
125 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
126 XCTAssertEqual(frame.width, outputFrame.width);
127 XCTAssertEqual(frame.height, outputFrame.height);
128
129 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
130 XCTAssertEqual(buffer.cropX, outputBuffer.cropX);
131 XCTAssertEqual(buffer.cropY, outputBuffer.cropY);
132 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
133
134 [callbackExpectation fulfill];
135 });
136
137 const rtc::VideoSinkWants video_sink_wants;
138 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
139 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
140
141 _video_source->OnOutputFormatRequest(640, 360, 30);
142 _video_source->OnCapturedFrame(frame);
143
144 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
145 CVBufferRelease(pixelBufferRef);
146}
147
148- (void)testOnCapturedFrameCVPixelBufferNeedsAdaptation {
149 CVPixelBufferRef pixelBufferRef = NULL;
150 CVPixelBufferCreate(
151 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
152
153 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef];
154 RTCVideoFrame *frame =
155 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
156
157 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
158 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
159 XCTAssertEqual(outputFrame.width, 360);
160 XCTAssertEqual(outputFrame.height, 640);
161
162 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
163 XCTAssertEqual(outputBuffer.cropX, 0);
164 XCTAssertEqual(outputBuffer.cropY, 0);
165 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
166
167 [callbackExpectation fulfill];
168 });
169
170 const rtc::VideoSinkWants video_sink_wants;
171 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
172 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
173
174 _video_source->OnOutputFormatRequest(640, 360, 30);
175 _video_source->OnCapturedFrame(frame);
176
177 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
178 CVBufferRelease(pixelBufferRef);
179}
180
181- (void)testOnCapturedFrameCVPixelBufferNeedsCropping {
182 CVPixelBufferRef pixelBufferRef = NULL;
183 CVPixelBufferCreate(
184 NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
185
186 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef];
187 RTCVideoFrame *frame =
188 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
189
190 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
191 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
192 XCTAssertEqual(outputFrame.width, 360);
193 XCTAssertEqual(outputFrame.height, 640);
194
195 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
196 XCTAssertEqual(outputBuffer.cropX, 10);
197 XCTAssertEqual(outputBuffer.cropY, 0);
198 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
199
200 [callbackExpectation fulfill];
201 });
202
203 const rtc::VideoSinkWants video_sink_wants;
204 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
205 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
206
207 _video_source->OnOutputFormatRequest(640, 360, 30);
208 _video_source->OnCapturedFrame(frame);
209
210 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
211 CVBufferRelease(pixelBufferRef);
212}
213
214- (void)testOnCapturedFramePreAdaptedCVPixelBufferNeedsAdaptation {
215 CVPixelBufferRef pixelBufferRef = NULL;
216 CVPixelBufferCreate(
217 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
218
219 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef
220 adaptedWidth:700
221 adaptedHeight:700
222 cropWidth:720
223 cropHeight:1280
224 cropX:0
225 cropY:0];
226 RTCVideoFrame *frame =
227 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
228
229 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
230 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
Peter Hanspers488eb982018-06-08 14:49:51 +0200231 XCTAssertEqual(outputFrame.width, 300);
232 XCTAssertEqual(outputFrame.height, 516);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200233
234 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
Peter Hanspers488eb982018-06-08 14:49:51 +0200235 XCTAssertEqual(outputBuffer.cropX, 150);
236 XCTAssertEqual(outputBuffer.cropY, 6);
237 XCTAssertEqual(outputBuffer.cropWidth, 400);
238 XCTAssertEqual(outputBuffer.cropHeight, 688);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200239 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
240
241 [callbackExpectation fulfill];
242 });
243
244 const rtc::VideoSinkWants video_sink_wants;
245 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
246 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
247
248 _video_source->OnOutputFormatRequest(640, 360, 30);
249 _video_source->OnCapturedFrame(frame);
250
251 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
252 CVBufferRelease(pixelBufferRef);
253}
254
255- (void)testOnCapturedFramePreCroppedCVPixelBufferNeedsCropping {
256 CVPixelBufferRef pixelBufferRef = NULL;
257 CVPixelBufferCreate(
258 NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
259
260 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef
261 adaptedWidth:370
262 adaptedHeight:640
263 cropWidth:370
264 cropHeight:640
265 cropX:10
266 cropY:0];
267 RTCVideoFrame *frame =
268 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
269
270 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
271 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
272 XCTAssertEqual(outputFrame.width, 360);
273 XCTAssertEqual(outputFrame.height, 640);
274
275 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
276 XCTAssertEqual(outputBuffer.cropX, 14);
277 XCTAssertEqual(outputBuffer.cropY, 0);
278 XCTAssertEqual(outputBuffer.cropWidth, 360);
279 XCTAssertEqual(outputBuffer.cropHeight, 640);
280 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
281
282 [callbackExpectation fulfill];
283 });
284
285 const rtc::VideoSinkWants video_sink_wants;
286 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
287 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
288
289 _video_source->OnOutputFormatRequest(640, 360, 30);
290 _video_source->OnCapturedFrame(frame);
291
292 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
293 CVBufferRelease(pixelBufferRef);
294}
295
296- (void)testOnCapturedFrameSmallerPreCroppedCVPixelBufferNeedsCropping {
297 CVPixelBufferRef pixelBufferRef = NULL;
298 CVPixelBufferCreate(
299 NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef);
300
301 RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBufferRef
302 adaptedWidth:300
303 adaptedHeight:640
304 cropWidth:300
305 cropHeight:640
306 cropX:40
307 cropY:0];
308 RTCVideoFrame *frame =
309 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
310
311 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
312 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
313 XCTAssertEqual(outputFrame.width, 300);
Peter Hanspers488eb982018-06-08 14:49:51 +0200314 XCTAssertEqual(outputFrame.height, 536);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200315
316 RTCCVPixelBuffer *outputBuffer = outputFrame.buffer;
317 XCTAssertEqual(outputBuffer.cropX, 40);
318 XCTAssertEqual(outputBuffer.cropY, 52);
319 XCTAssertEqual(outputBuffer.cropWidth, 300);
Peter Hanspers488eb982018-06-08 14:49:51 +0200320 XCTAssertEqual(outputBuffer.cropHeight, 536);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200321 XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer);
322
323 [callbackExpectation fulfill];
324 });
325
326 const rtc::VideoSinkWants video_sink_wants;
327 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
328 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
329
330 _video_source->OnOutputFormatRequest(640, 360, 30);
331 _video_source->OnCapturedFrame(frame);
332
333 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
334 CVBufferRelease(pixelBufferRef);
335}
336
337- (void)testOnCapturedFrameI420BufferNeedsAdaptation {
338 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(720, 1280);
339 RTCI420Buffer *buffer = [[RTCI420Buffer alloc] initWithFrameBuffer:i420Buffer];
340 RTCVideoFrame *frame =
341 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
342
343 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
344 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
345 XCTAssertEqual(outputFrame.width, 360);
346 XCTAssertEqual(outputFrame.height, 640);
347
348 RTCI420Buffer *outputBuffer = (RTCI420Buffer *)outputFrame.buffer;
349
350 double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]);
351 XCTAssertEqual(psnr, webrtc::kPerfectPSNR);
352
353 [callbackExpectation fulfill];
354 });
355
356 const rtc::VideoSinkWants video_sink_wants;
357 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
358 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
359
360 _video_source->OnOutputFormatRequest(640, 360, 30);
361 _video_source->OnCapturedFrame(frame);
362
363 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
364}
365
366- (void)testOnCapturedFrameI420BufferNeedsCropping {
367 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(380, 640);
368 RTCI420Buffer *buffer = [[RTCI420Buffer alloc] initWithFrameBuffer:i420Buffer];
369 RTCVideoFrame *frame =
370 [[RTCVideoFrame alloc] initWithBuffer:buffer rotation:RTCVideoRotation_0 timeStampNs:0];
371
372 XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"];
373 ObjCCallbackVideoSink callback_video_sink(^void(RTCVideoFrame *outputFrame) {
374 XCTAssertEqual(outputFrame.width, 360);
375 XCTAssertEqual(outputFrame.height, 640);
376
377 RTCI420Buffer *outputBuffer = (RTCI420Buffer *)outputFrame.buffer;
378
379 double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]);
380 XCTAssertGreaterThanOrEqual(psnr, 40);
381
382 [callbackExpectation fulfill];
383 });
384
385 const rtc::VideoSinkWants video_sink_wants;
386 rtc::VideoSourceInterface<webrtc::VideoFrame> *video_source_interface = _video_source;
387 video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants);
388
389 _video_source->OnOutputFormatRequest(640, 360, 30);
390 _video_source->OnCapturedFrame(frame);
391
392 [self waitForExpectations:@[ callbackExpectation ] timeout:10.0];
393}
394
395@end