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#import "components/video_frame_buffer/RTCCVPixelBuffer.h" 15 16#import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h" 17#import "base/RTCVideoFrame.h" 18#import "base/RTCVideoFrameBuffer.h" 19#import "frame_buffer_helpers.h" 20 21#include "common_video/libyuv/include/webrtc_libyuv.h" 22#include "third_party/libyuv/include/libyuv.h" 23 24namespace { 25 26struct ToI420WithCropAndScaleSetting { 27 int inputWidth; 28 int inputHeight; 29 int offsetX; 30 int offsetY; 31 int cropWidth; 32 int cropHeight; 33 int scaleWidth; 34 int scaleHeight; 35}; 36 37constexpr const ToI420WithCropAndScaleSetting kToI420WithCropAndScaleSettings[] = { 38 ToI420WithCropAndScaleSetting{ 39 .inputWidth = 640, 40 .inputHeight = 360, 41 .offsetX = 0, 42 .offsetY = 0, 43 .cropWidth = 640, 44 .cropHeight = 360, 45 .scaleWidth = 320, 46 .scaleHeight = 180, 47 }, 48 ToI420WithCropAndScaleSetting{ 49 .inputWidth = 640, 50 .inputHeight = 360, 51 .offsetX = 160, 52 .offsetY = 90, 53 .cropWidth = 160, 54 .cropHeight = 90, 55 .scaleWidth = 320, 56 .scaleHeight = 180, 57 }, 58}; 59 60} // namespace 61 62@interface RTCCVPixelBufferTests : XCTestCase 63@end 64 65@implementation RTCCVPixelBufferTests { 66} 67 68- (void)testRequiresCroppingNoCrop { 69 CVPixelBufferRef pixelBufferRef = NULL; 70 CVPixelBufferCreate( 71 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 72 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 73 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 74 75 XCTAssertFalse([buffer requiresCropping]); 76 77 CVBufferRelease(pixelBufferRef); 78} 79 80- (void)testRequiresCroppingWithCrop { 81 CVPixelBufferRef pixelBufferRef = NULL; 82 CVPixelBufferCreate( 83 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 84 RTC_OBJC_TYPE(RTCCVPixelBuffer) *croppedBuffer = 85 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef 86 adaptedWidth:720 87 adaptedHeight:1280 88 cropWidth:360 89 cropHeight:640 90 cropX:100 91 cropY:100]; 92 93 XCTAssertTrue([croppedBuffer requiresCropping]); 94 95 CVBufferRelease(pixelBufferRef); 96} 97 98- (void)testRequiresScalingNoScale { 99 CVPixelBufferRef pixelBufferRef = NULL; 100 CVPixelBufferCreate( 101 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 102 103 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 104 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 105 XCTAssertFalse([buffer requiresScalingToWidth:720 height:1280]); 106 107 CVBufferRelease(pixelBufferRef); 108} 109 110- (void)testRequiresScalingWithScale { 111 CVPixelBufferRef pixelBufferRef = NULL; 112 CVPixelBufferCreate( 113 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 114 115 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 116 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 117 XCTAssertTrue([buffer requiresScalingToWidth:360 height:640]); 118 119 CVBufferRelease(pixelBufferRef); 120} 121 122- (void)testRequiresScalingWithScaleAndMatchingCrop { 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 adaptedWidth:720 130 adaptedHeight:1280 131 cropWidth:360 132 cropHeight:640 133 cropX:100 134 cropY:100]; 135 XCTAssertFalse([buffer requiresScalingToWidth:360 height:640]); 136 137 CVBufferRelease(pixelBufferRef); 138} 139 140- (void)testBufferSize_NV12 { 141 CVPixelBufferRef pixelBufferRef = NULL; 142 CVPixelBufferCreate( 143 NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 144 145 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 146 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 147 XCTAssertEqual([buffer bufferSizeForCroppingAndScalingToWidth:360 height:640], 576000); 148 149 CVBufferRelease(pixelBufferRef); 150} 151 152- (void)testBufferSize_RGB { 153 CVPixelBufferRef pixelBufferRef = NULL; 154 CVPixelBufferCreate(NULL, 720, 1280, kCVPixelFormatType_32BGRA, NULL, &pixelBufferRef); 155 156 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 157 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 158 XCTAssertEqual([buffer bufferSizeForCroppingAndScalingToWidth:360 height:640], 0); 159 160 CVBufferRelease(pixelBufferRef); 161} 162 163- (void)testCropAndScale_NV12 { 164 [self cropAndScaleTestWithNV12]; 165} 166 167- (void)testCropAndScaleNoOp_NV12 { 168 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 169 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 170 outputSize:CGSizeMake(720, 1280)]; 171} 172 173- (void)testCropAndScale_NV12FullToVideo { 174 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 175 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; 176} 177 178- (void)testCropAndScaleZeroSizeFrame_NV12 { 179 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 180 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 181 outputSize:CGSizeMake(0, 0)]; 182} 183 184- (void)testCropAndScaleToSmallFormat_NV12 { 185 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 186 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 187 outputSize:CGSizeMake(148, 320)]; 188} 189 190- (void)testCropAndScaleToOddFormat_NV12 { 191 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 192 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 193 outputSize:CGSizeMake(361, 640)]; 194} 195 196- (void)testCropAndScale_32BGRA { 197 [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32BGRA]; 198} 199 200- (void)testCropAndScale_32ARGB { 201 [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB]; 202} 203 204- (void)testCropAndScaleWithSmallCropInfo_32ARGB { 205 [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB cropX:2 cropY:3]; 206} 207 208- (void)testCropAndScaleWithLargeCropInfo_32ARGB { 209 [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB cropX:200 cropY:300]; 210} 211 212- (void)testToI420_NV12 { 213 [self toI420WithPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; 214} 215 216- (void)testToI420_32BGRA { 217 [self toI420WithPixelFormat:kCVPixelFormatType_32BGRA]; 218} 219 220- (void)testToI420_32ARGB { 221 [self toI420WithPixelFormat:kCVPixelFormatType_32ARGB]; 222} 223 224- (void)testToI420WithCropAndScale_NV12 { 225 for (const auto &setting : kToI420WithCropAndScaleSettings) { 226 [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 227 setting:setting]; 228 } 229} 230 231- (void)testToI420WithCropAndScale_32BGRA { 232 for (const auto &setting : kToI420WithCropAndScaleSettings) { 233 [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_32BGRA setting:setting]; 234 } 235} 236 237- (void)testToI420WithCropAndScale_32ARGB { 238 for (const auto &setting : kToI420WithCropAndScaleSettings) { 239 [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_32ARGB setting:setting]; 240 } 241} 242 243- (void)testScaleBufferTest { 244 CVPixelBufferRef pixelBufferRef = NULL; 245 CVPixelBufferCreate( 246 NULL, 1920, 1080, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); 247 248 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(1920, 1080); 249 CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); 250 251 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 252 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 253 254 XCTAssertEqual(buffer.width, 1920); 255 XCTAssertEqual(buffer.height, 1080); 256 XCTAssertEqual(buffer.cropX, 0); 257 XCTAssertEqual(buffer.cropY, 0); 258 XCTAssertEqual(buffer.cropWidth, 1920); 259 XCTAssertEqual(buffer.cropHeight, 1080); 260 261 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer2 = 262 (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)[buffer cropAndScaleWith:320 263 offsetY:180 264 cropWidth:1280 265 cropHeight:720 266 scaleWidth:960 267 scaleHeight:540]; 268 269 XCTAssertEqual(buffer2.width, 960); 270 XCTAssertEqual(buffer2.height, 540); 271 XCTAssertEqual(buffer2.cropX, 320); 272 XCTAssertEqual(buffer2.cropY, 180); 273 XCTAssertEqual(buffer2.cropWidth, 1280); 274 XCTAssertEqual(buffer2.cropHeight, 720); 275 276 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer3 = 277 (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)[buffer2 cropAndScaleWith:240 278 offsetY:135 279 cropWidth:480 280 cropHeight:270 281 scaleWidth:320 282 scaleHeight:180]; 283 284 XCTAssertEqual(buffer3.width, 320); 285 XCTAssertEqual(buffer3.height, 180); 286 XCTAssertEqual(buffer3.cropX, 640); 287 XCTAssertEqual(buffer3.cropY, 360); 288 XCTAssertEqual(buffer3.cropWidth, 640); 289 XCTAssertEqual(buffer3.cropHeight, 360); 290 291 CVBufferRelease(pixelBufferRef); 292} 293 294#pragma mark - Shared test code 295 296- (void)cropAndScaleTestWithNV12 { 297 [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 298 outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; 299} 300 301- (void)cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat outputFormat:(OSType)outputFormat { 302 [self cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat 303 outputFormat:(OSType)outputFormat 304 outputSize:CGSizeMake(360, 640)]; 305} 306 307- (void)cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat 308 outputFormat:(OSType)outputFormat 309 outputSize:(CGSize)outputSize { 310 CVPixelBufferRef pixelBufferRef = NULL; 311 CVPixelBufferCreate(NULL, 720, 1280, inputFormat, NULL, &pixelBufferRef); 312 313 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(720, 1280); 314 CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); 315 316 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 317 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 318 XCTAssertEqual(buffer.width, 720); 319 XCTAssertEqual(buffer.height, 1280); 320 321 CVPixelBufferRef outputPixelBufferRef = NULL; 322 CVPixelBufferCreate( 323 NULL, outputSize.width, outputSize.height, outputFormat, NULL, &outputPixelBufferRef); 324 325 std::vector<uint8_t> frameScaleBuffer; 326 if ([buffer requiresScalingToWidth:outputSize.width height:outputSize.height]) { 327 int size = 328 [buffer bufferSizeForCroppingAndScalingToWidth:outputSize.width height:outputSize.height]; 329 frameScaleBuffer.resize(size); 330 } else { 331 frameScaleBuffer.clear(); 332 } 333 frameScaleBuffer.shrink_to_fit(); 334 335 [buffer cropAndScaleTo:outputPixelBufferRef withTempBuffer:frameScaleBuffer.data()]; 336 337 RTC_OBJC_TYPE(RTCCVPixelBuffer) *scaledBuffer = 338 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:outputPixelBufferRef]; 339 XCTAssertEqual(scaledBuffer.width, outputSize.width); 340 XCTAssertEqual(scaledBuffer.height, outputSize.height); 341 342 if (outputSize.width > 0 && outputSize.height > 0) { 343 RTC_OBJC_TYPE(RTCI420Buffer) *originalBufferI420 = [buffer toI420]; 344 RTC_OBJC_TYPE(RTCI420Buffer) *scaledBufferI420 = [scaledBuffer toI420]; 345 double psnr = 346 I420PSNR(*[originalBufferI420 nativeI420Buffer], *[scaledBufferI420 nativeI420Buffer]); 347 XCTAssertEqual(psnr, webrtc::kPerfectPSNR); 348 } 349 350 CVBufferRelease(pixelBufferRef); 351} 352 353- (void)cropAndScaleTestWithRGBPixelFormat:(OSType)pixelFormat { 354 [self cropAndScaleTestWithRGBPixelFormat:pixelFormat cropX:0 cropY:0]; 355} 356 357- (void)cropAndScaleTestWithRGBPixelFormat:(OSType)pixelFormat cropX:(int)cropX cropY:(int)cropY { 358 CVPixelBufferRef pixelBufferRef = NULL; 359 CVPixelBufferCreate(NULL, 720, 1280, pixelFormat, NULL, &pixelBufferRef); 360 361 DrawGradientInRGBPixelBuffer(pixelBufferRef); 362 363 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] 364 initWithPixelBuffer:pixelBufferRef 365 adaptedWidth:CVPixelBufferGetWidth(pixelBufferRef) 366 adaptedHeight:CVPixelBufferGetHeight(pixelBufferRef) 367 cropWidth:CVPixelBufferGetWidth(pixelBufferRef) - cropX 368 cropHeight:CVPixelBufferGetHeight(pixelBufferRef) - cropY 369 cropX:cropX 370 cropY:cropY]; 371 372 XCTAssertEqual(buffer.width, 720); 373 XCTAssertEqual(buffer.height, 1280); 374 375 CVPixelBufferRef outputPixelBufferRef = NULL; 376 CVPixelBufferCreate(NULL, 360, 640, pixelFormat, NULL, &outputPixelBufferRef); 377 [buffer cropAndScaleTo:outputPixelBufferRef withTempBuffer:NULL]; 378 379 RTC_OBJC_TYPE(RTCCVPixelBuffer) *scaledBuffer = 380 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:outputPixelBufferRef]; 381 XCTAssertEqual(scaledBuffer.width, 360); 382 XCTAssertEqual(scaledBuffer.height, 640); 383 384 RTC_OBJC_TYPE(RTCI420Buffer) *originalBufferI420 = [buffer toI420]; 385 RTC_OBJC_TYPE(RTCI420Buffer) *scaledBufferI420 = [scaledBuffer toI420]; 386 double psnr = 387 I420PSNR(*[originalBufferI420 nativeI420Buffer], *[scaledBufferI420 nativeI420Buffer]); 388 XCTAssertEqual(psnr, webrtc::kPerfectPSNR); 389 390 CVBufferRelease(pixelBufferRef); 391} 392 393- (void)toI420WithPixelFormat:(OSType)pixelFormat { 394 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = CreateI420Gradient(360, 640); 395 396 CVPixelBufferRef pixelBufferRef = NULL; 397 CVPixelBufferCreate(NULL, 360, 640, pixelFormat, NULL, &pixelBufferRef); 398 399 CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); 400 401 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 402 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 403 RTC_OBJC_TYPE(RTCI420Buffer) *fromCVPixelBuffer = [buffer toI420]; 404 405 double psnr = I420PSNR(*i420Buffer, *[fromCVPixelBuffer nativeI420Buffer]); 406 double target = webrtc::kPerfectPSNR; 407 if (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) { 408 // libyuv's I420ToRGB functions seem to lose some quality. 409 target = 19.0; 410 } 411 XCTAssertGreaterThanOrEqual(psnr, target); 412 413 CVBufferRelease(pixelBufferRef); 414} 415 416- (void)toI420WithCropAndScaleWithPixelFormat:(OSType)pixelFormat 417 setting:(const ToI420WithCropAndScaleSetting &)setting { 418 rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = 419 CreateI420Gradient(setting.inputWidth, setting.inputHeight); 420 421 CVPixelBufferRef pixelBufferRef = NULL; 422 CVPixelBufferCreate( 423 NULL, setting.inputWidth, setting.inputHeight, pixelFormat, NULL, &pixelBufferRef); 424 425 CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); 426 427 RTC_OBJC_TYPE(RTCI420Buffer) *objcI420Buffer = 428 [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer]; 429 RTC_OBJC_TYPE(RTCI420Buffer) *scaledObjcI420Buffer = 430 (RTC_OBJC_TYPE(RTCI420Buffer) *)[objcI420Buffer cropAndScaleWith:setting.offsetX 431 offsetY:setting.offsetY 432 cropWidth:setting.cropWidth 433 cropHeight:setting.cropHeight 434 scaleWidth:setting.scaleWidth 435 scaleHeight:setting.scaleHeight]; 436 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = 437 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; 438 id<RTC_OBJC_TYPE(RTCVideoFrameBuffer)> scaledBuffer = 439 [buffer cropAndScaleWith:setting.offsetX 440 offsetY:setting.offsetY 441 cropWidth:setting.cropWidth 442 cropHeight:setting.cropHeight 443 scaleWidth:setting.scaleWidth 444 scaleHeight:setting.scaleHeight]; 445 XCTAssertTrue([scaledBuffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]); 446 447 RTC_OBJC_TYPE(RTCI420Buffer) *fromCVPixelBuffer = [scaledBuffer toI420]; 448 449 double psnr = 450 I420PSNR(*[scaledObjcI420Buffer nativeI420Buffer], *[fromCVPixelBuffer nativeI420Buffer]); 451 double target = webrtc::kPerfectPSNR; 452 if (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) { 453 // libyuv's I420ToRGB functions seem to lose some quality. 454 target = 19.0; 455 } 456 XCTAssertGreaterThanOrEqual(psnr, target); 457 458 CVBufferRelease(pixelBufferRef); 459} 460 461@end 462