blob: f854c91edcb5b347c917ab35d44f386fe44cf7cc [file] [log] [blame]
Anders Carlssone5960ce2017-06-22 15:26:30 +02001/*
2 * Copyright 2017 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 "WebRTC/RTCVideoFrameBuffer.h"
12
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020013#include "common_video/libyuv/include/webrtc_libyuv.h"
14#include "rtc_base/checks.h"
15#include "rtc_base/logging.h"
Mirko Bonadei65432062017-12-11 09:32:13 +010016#include "third_party/libyuv/include/libyuv.h"
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020017
Anders Carlsson498644e2018-04-05 13:07:39 +020018#if !defined(NDEBUG) && defined(WEBRTC_IOS)
19#import <UIKit/UIKit.h>
20#import <VideoToolbox/VideoToolbox.h>
21#endif
22
Anders Carlssone5960ce2017-06-22 15:26:30 +020023@implementation RTCCVPixelBuffer {
24 int _width;
25 int _height;
26 int _bufferWidth;
27 int _bufferHeight;
28 int _cropWidth;
29 int _cropHeight;
Anders Carlssone5960ce2017-06-22 15:26:30 +020030}
31
32@synthesize pixelBuffer = _pixelBuffer;
andersc151aa6b2017-08-25 01:33:18 -070033@synthesize cropX = _cropX;
34@synthesize cropY = _cropY;
Anders Carlssonfe9d8172018-04-03 11:40:39 +020035@synthesize cropWidth = _cropWidth;
36@synthesize cropHeight = _cropHeight;
Anders Carlssone5960ce2017-06-22 15:26:30 +020037
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020038+ (NSSet<NSNumber*>*)supportedPixelFormats {
39 return [NSSet setWithObjects:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
40 @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange),
41 @(kCVPixelFormatType_32BGRA),
42 @(kCVPixelFormatType_32ARGB),
43 nil];
44}
45
Anders Carlssone5960ce2017-06-22 15:26:30 +020046- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer {
47 return [self initWithPixelBuffer:pixelBuffer
48 adaptedWidth:CVPixelBufferGetWidth(pixelBuffer)
49 adaptedHeight:CVPixelBufferGetHeight(pixelBuffer)
50 cropWidth:CVPixelBufferGetWidth(pixelBuffer)
51 cropHeight:CVPixelBufferGetHeight(pixelBuffer)
52 cropX:0
53 cropY:0];
54}
55
56- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer
57 adaptedWidth:(int)adaptedWidth
58 adaptedHeight:(int)adaptedHeight
59 cropWidth:(int)cropWidth
60 cropHeight:(int)cropHeight
61 cropX:(int)cropX
62 cropY:(int)cropY {
63 if (self = [super init]) {
64 _width = adaptedWidth;
65 _height = adaptedHeight;
66 _pixelBuffer = pixelBuffer;
67 _bufferWidth = CVPixelBufferGetWidth(_pixelBuffer);
68 _bufferHeight = CVPixelBufferGetHeight(_pixelBuffer);
69 _cropWidth = cropWidth;
70 _cropHeight = cropHeight;
71 // Can only crop at even pixels.
72 _cropX = cropX & ~1;
73 _cropY = cropY & ~1;
74 CVBufferRetain(_pixelBuffer);
75 }
76
77 return self;
78}
79
80- (void)dealloc {
81 CVBufferRelease(_pixelBuffer);
82}
83
84- (int)width {
85 return _width;
86}
87
88- (int)height {
89 return _height;
90}
91
92- (BOOL)requiresCropping {
93 return _cropWidth != _bufferWidth || _cropHeight != _bufferHeight;
94}
95
96- (BOOL)requiresScalingToWidth:(int)width height:(int)height {
97 return _cropWidth != width || _cropHeight != height;
98}
99
100- (int)bufferSizeForCroppingAndScalingToWidth:(int)width height:(int)height {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200101 const OSType srcPixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
102 switch (srcPixelFormat) {
103 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
104 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
105 int srcChromaWidth = (_cropWidth + 1) / 2;
106 int srcChromaHeight = (_cropHeight + 1) / 2;
107 int dstChromaWidth = (width + 1) / 2;
108 int dstChromaHeight = (height + 1) / 2;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200109
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200110 return srcChromaWidth * srcChromaHeight * 2 + dstChromaWidth * dstChromaHeight * 2;
111 }
112 case kCVPixelFormatType_32BGRA:
113 case kCVPixelFormatType_32ARGB: {
114 return 0; // Scaling RGBA frames does not require a temporary buffer.
115 }
116 }
117 RTC_NOTREACHED() << "Unsupported pixel format.";
118 return 0;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200119}
120
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200121- (BOOL)cropAndScaleTo:(CVPixelBufferRef)outputPixelBuffer
122 withTempBuffer:(nullable uint8_t*)tmpBuffer {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200123 const OSType srcPixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200124 const OSType dstPixelFormat = CVPixelBufferGetPixelFormatType(outputPixelBuffer);
125
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200126 switch (srcPixelFormat) {
127 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
128 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200129 size_t dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
130 size_t dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
131 if (dstWidth > 0 && dstHeight > 0) {
132 RTC_DCHECK(dstPixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
133 dstPixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
134 if ([self requiresScalingToWidth:dstWidth height:dstHeight]) {
135 RTC_DCHECK(tmpBuffer);
136 }
137 [self cropAndScaleNV12To:outputPixelBuffer withTempBuffer:tmpBuffer];
138 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200139 break;
140 }
141 case kCVPixelFormatType_32BGRA:
142 case kCVPixelFormatType_32ARGB: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200143 RTC_DCHECK(srcPixelFormat == dstPixelFormat);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200144 [self cropAndScaleARGBTo:outputPixelBuffer];
145 break;
146 }
147 default: { RTC_NOTREACHED() << "Unsupported pixel format."; }
148 }
149
150 return YES;
151}
152
153- (id<RTCI420Buffer>)toI420 {
154 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
155
156 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
157
158 RTCMutableI420Buffer* i420Buffer =
159 [[RTCMutableI420Buffer alloc] initWithWidth:[self width] height:[self height]];
160
161 switch (pixelFormat) {
162 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
163 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
164 const uint8_t* srcY =
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200165 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 0));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200166 const int srcYStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 0);
167 const uint8_t* srcUV =
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200168 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 1));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200169 const int srcUVStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 1);
170
171 // Crop just by modifying pointers.
172 srcY += srcYStride * _cropY + _cropX;
173 srcUV += srcUVStride * (_cropY / 2) + _cropX;
174
175 // TODO(magjed): Use a frame buffer pool.
176 webrtc::NV12ToI420Scaler nv12ToI420Scaler;
177 nv12ToI420Scaler.NV12ToI420Scale(srcY,
178 srcYStride,
179 srcUV,
180 srcUVStride,
181 _cropWidth,
182 _cropHeight,
183 i420Buffer.mutableDataY,
184 i420Buffer.strideY,
185 i420Buffer.mutableDataU,
186 i420Buffer.strideU,
187 i420Buffer.mutableDataV,
188 i420Buffer.strideV,
189 i420Buffer.width,
190 i420Buffer.height);
191 break;
192 }
193 case kCVPixelFormatType_32BGRA:
194 case kCVPixelFormatType_32ARGB: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200195 CVPixelBufferRef scaledPixelBuffer = NULL;
196 CVPixelBufferRef sourcePixelBuffer = NULL;
197 if ([self requiresCropping] ||
198 [self requiresScalingToWidth:i420Buffer.width height:i420Buffer.height]) {
199 CVPixelBufferCreate(
200 NULL, i420Buffer.width, i420Buffer.height, pixelFormat, NULL, &scaledPixelBuffer);
201 [self cropAndScaleTo:scaledPixelBuffer withTempBuffer:NULL];
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200202
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200203 CVPixelBufferLockBaseAddress(scaledPixelBuffer, kCVPixelBufferLock_ReadOnly);
204 sourcePixelBuffer = scaledPixelBuffer;
205 } else {
206 sourcePixelBuffer = _pixelBuffer;
207 }
208 const uint8_t* src = static_cast<uint8_t*>(CVPixelBufferGetBaseAddress(sourcePixelBuffer));
209 const size_t bytesPerRow = CVPixelBufferGetBytesPerRow(sourcePixelBuffer);
210
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200211 if (pixelFormat == kCVPixelFormatType_32BGRA) {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200212 // Corresponds to libyuv::FOURCC_ARGB
213 libyuv::ARGBToI420(src,
214 bytesPerRow,
215 i420Buffer.mutableDataY,
216 i420Buffer.strideY,
217 i420Buffer.mutableDataU,
218 i420Buffer.strideU,
219 i420Buffer.mutableDataV,
220 i420Buffer.strideV,
221 i420Buffer.width,
222 i420Buffer.height);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200223 } else if (pixelFormat == kCVPixelFormatType_32ARGB) {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200224 // Corresponds to libyuv::FOURCC_BGRA
225 libyuv::BGRAToI420(src,
226 bytesPerRow,
227 i420Buffer.mutableDataY,
228 i420Buffer.strideY,
229 i420Buffer.mutableDataU,
230 i420Buffer.strideU,
231 i420Buffer.mutableDataV,
232 i420Buffer.strideV,
233 i420Buffer.width,
234 i420Buffer.height);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200235 }
236
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200237 if (scaledPixelBuffer) {
238 CVPixelBufferUnlockBaseAddress(scaledPixelBuffer, kCVPixelBufferLock_ReadOnly);
239 CVBufferRelease(scaledPixelBuffer);
240 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200241 break;
242 }
243 default: { RTC_NOTREACHED() << "Unsupported pixel format."; }
244 }
245
246 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
247
248 return i420Buffer;
249}
250
Anders Carlsson498644e2018-04-05 13:07:39 +0200251#pragma mark - Debugging
252
253#if !defined(NDEBUG) && defined(WEBRTC_IOS)
254- (id)debugQuickLookObject {
255 CGImageRef cgImage;
256 VTCreateCGImageFromCVPixelBuffer(_pixelBuffer, NULL, &cgImage);
257 UIImage *image = [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationUp];
258 CGImageRelease(cgImage);
259 return image;
260}
261#endif
262
263#pragma mark - Private
264
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200265- (void)cropAndScaleNV12To:(CVPixelBufferRef)outputPixelBuffer withTempBuffer:(uint8_t*)tmpBuffer {
Anders Carlssone5960ce2017-06-22 15:26:30 +0200266 // Prepare output pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200267 CVReturn cvRet = CVPixelBufferLockBaseAddress(outputPixelBuffer, 0);
268 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100269 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200270 }
271 const int dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
272 const int dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
273 uint8_t* dstY =
274 reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(outputPixelBuffer, 0));
275 const int dstYStride = CVPixelBufferGetBytesPerRowOfPlane(outputPixelBuffer, 0);
276 uint8_t* dstUV =
277 reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(outputPixelBuffer, 1));
278 const int dstUVStride = CVPixelBufferGetBytesPerRowOfPlane(outputPixelBuffer, 1);
279
280 // Prepare source pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200281 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200282 const uint8_t* srcY = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 0));
Anders Carlssone5960ce2017-06-22 15:26:30 +0200283 const int srcYStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 0);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200284 const uint8_t* srcUV = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 1));
Anders Carlssone5960ce2017-06-22 15:26:30 +0200285 const int srcUVStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 1);
286
287 // Crop just by modifying pointers.
288 srcY += srcYStride * _cropY + _cropX;
289 srcUV += srcUVStride * (_cropY / 2) + _cropX;
290
291 webrtc::NV12Scale(tmpBuffer,
292 srcY,
293 srcYStride,
294 srcUV,
295 srcUVStride,
296 _cropWidth,
297 _cropHeight,
298 dstY,
299 dstYStride,
300 dstUV,
301 dstUVStride,
302 dstWidth,
303 dstHeight);
304
305 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
306 CVPixelBufferUnlockBaseAddress(outputPixelBuffer, 0);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200307}
308
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200309- (void)cropAndScaleARGBTo:(CVPixelBufferRef)outputPixelBuffer {
310 // Prepare output pointers.
311 CVReturn cvRet = CVPixelBufferLockBaseAddress(outputPixelBuffer, 0);
312 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100313 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200314 }
315 const int dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
316 const int dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200317
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200318 uint8_t* dst = reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddress(outputPixelBuffer));
319 const int dstStride = CVPixelBufferGetBytesPerRow(outputPixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200320
321 // Prepare source pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200322 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200323 const uint8_t* src = static_cast<uint8_t*>(CVPixelBufferGetBaseAddress(_pixelBuffer));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200324 const int srcStride = CVPixelBufferGetBytesPerRow(_pixelBuffer);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200325
326 // Crop just by modifying pointers.
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200327 src += srcStride * _cropY + _cropX;
328 libyuv::ARGBScale(src,
329 srcStride,
330 _cropWidth,
331 _cropHeight,
332 dst,
333 dstStride,
334 dstWidth,
335 dstHeight,
336 libyuv::kFilterBox);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200337
338 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200339 CVPixelBufferUnlockBaseAddress(outputPixelBuffer, 0);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200340}
341
342@end