blob: f572ae24d4c3441ad43bc21986c8bda91b88519b [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"
Anders Carlssone5960ce2017-06-22 15:26:30 +020016
Mirko Bonadei65432062017-12-11 09:32:13 +010017#include "third_party/libyuv/include/libyuv.h"
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020018
Anders Carlssone5960ce2017-06-22 15:26:30 +020019@implementation RTCCVPixelBuffer {
20 int _width;
21 int _height;
22 int _bufferWidth;
23 int _bufferHeight;
24 int _cropWidth;
25 int _cropHeight;
Anders Carlssone5960ce2017-06-22 15:26:30 +020026}
27
28@synthesize pixelBuffer = _pixelBuffer;
andersc151aa6b2017-08-25 01:33:18 -070029@synthesize cropX = _cropX;
30@synthesize cropY = _cropY;
Anders Carlssonfe9d8172018-04-03 11:40:39 +020031@synthesize cropWidth = _cropWidth;
32@synthesize cropHeight = _cropHeight;
Anders Carlssone5960ce2017-06-22 15:26:30 +020033
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020034+ (NSSet<NSNumber*>*)supportedPixelFormats {
35 return [NSSet setWithObjects:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
36 @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange),
37 @(kCVPixelFormatType_32BGRA),
38 @(kCVPixelFormatType_32ARGB),
39 nil];
40}
41
Anders Carlssone5960ce2017-06-22 15:26:30 +020042- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer {
43 return [self initWithPixelBuffer:pixelBuffer
44 adaptedWidth:CVPixelBufferGetWidth(pixelBuffer)
45 adaptedHeight:CVPixelBufferGetHeight(pixelBuffer)
46 cropWidth:CVPixelBufferGetWidth(pixelBuffer)
47 cropHeight:CVPixelBufferGetHeight(pixelBuffer)
48 cropX:0
49 cropY:0];
50}
51
52- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer
53 adaptedWidth:(int)adaptedWidth
54 adaptedHeight:(int)adaptedHeight
55 cropWidth:(int)cropWidth
56 cropHeight:(int)cropHeight
57 cropX:(int)cropX
58 cropY:(int)cropY {
59 if (self = [super init]) {
60 _width = adaptedWidth;
61 _height = adaptedHeight;
62 _pixelBuffer = pixelBuffer;
63 _bufferWidth = CVPixelBufferGetWidth(_pixelBuffer);
64 _bufferHeight = CVPixelBufferGetHeight(_pixelBuffer);
65 _cropWidth = cropWidth;
66 _cropHeight = cropHeight;
67 // Can only crop at even pixels.
68 _cropX = cropX & ~1;
69 _cropY = cropY & ~1;
70 CVBufferRetain(_pixelBuffer);
71 }
72
73 return self;
74}
75
76- (void)dealloc {
77 CVBufferRelease(_pixelBuffer);
78}
79
80- (int)width {
81 return _width;
82}
83
84- (int)height {
85 return _height;
86}
87
88- (BOOL)requiresCropping {
89 return _cropWidth != _bufferWidth || _cropHeight != _bufferHeight;
90}
91
92- (BOOL)requiresScalingToWidth:(int)width height:(int)height {
93 return _cropWidth != width || _cropHeight != height;
94}
95
96- (int)bufferSizeForCroppingAndScalingToWidth:(int)width height:(int)height {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020097 const OSType srcPixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
98 switch (srcPixelFormat) {
99 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
100 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
101 int srcChromaWidth = (_cropWidth + 1) / 2;
102 int srcChromaHeight = (_cropHeight + 1) / 2;
103 int dstChromaWidth = (width + 1) / 2;
104 int dstChromaHeight = (height + 1) / 2;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200105
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200106 return srcChromaWidth * srcChromaHeight * 2 + dstChromaWidth * dstChromaHeight * 2;
107 }
108 case kCVPixelFormatType_32BGRA:
109 case kCVPixelFormatType_32ARGB: {
110 return 0; // Scaling RGBA frames does not require a temporary buffer.
111 }
112 }
113 RTC_NOTREACHED() << "Unsupported pixel format.";
114 return 0;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200115}
116
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200117- (BOOL)cropAndScaleTo:(CVPixelBufferRef)outputPixelBuffer
118 withTempBuffer:(nullable uint8_t*)tmpBuffer {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200119 const OSType srcPixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200120 const OSType dstPixelFormat = CVPixelBufferGetPixelFormatType(outputPixelBuffer);
121
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200122 switch (srcPixelFormat) {
123 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
124 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200125 size_t dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
126 size_t dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
127 if (dstWidth > 0 && dstHeight > 0) {
128 RTC_DCHECK(dstPixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
129 dstPixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
130 if ([self requiresScalingToWidth:dstWidth height:dstHeight]) {
131 RTC_DCHECK(tmpBuffer);
132 }
133 [self cropAndScaleNV12To:outputPixelBuffer withTempBuffer:tmpBuffer];
134 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200135 break;
136 }
137 case kCVPixelFormatType_32BGRA:
138 case kCVPixelFormatType_32ARGB: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200139 RTC_DCHECK(srcPixelFormat == dstPixelFormat);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200140 [self cropAndScaleARGBTo:outputPixelBuffer];
141 break;
142 }
143 default: { RTC_NOTREACHED() << "Unsupported pixel format."; }
144 }
145
146 return YES;
147}
148
149- (id<RTCI420Buffer>)toI420 {
150 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(_pixelBuffer);
151
152 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
153
154 RTCMutableI420Buffer* i420Buffer =
155 [[RTCMutableI420Buffer alloc] initWithWidth:[self width] height:[self height]];
156
157 switch (pixelFormat) {
158 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
159 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: {
160 const uint8_t* srcY =
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200161 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 0));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200162 const int srcYStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 0);
163 const uint8_t* srcUV =
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200164 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 1));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200165 const int srcUVStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 1);
166
167 // Crop just by modifying pointers.
168 srcY += srcYStride * _cropY + _cropX;
169 srcUV += srcUVStride * (_cropY / 2) + _cropX;
170
171 // TODO(magjed): Use a frame buffer pool.
172 webrtc::NV12ToI420Scaler nv12ToI420Scaler;
173 nv12ToI420Scaler.NV12ToI420Scale(srcY,
174 srcYStride,
175 srcUV,
176 srcUVStride,
177 _cropWidth,
178 _cropHeight,
179 i420Buffer.mutableDataY,
180 i420Buffer.strideY,
181 i420Buffer.mutableDataU,
182 i420Buffer.strideU,
183 i420Buffer.mutableDataV,
184 i420Buffer.strideV,
185 i420Buffer.width,
186 i420Buffer.height);
187 break;
188 }
189 case kCVPixelFormatType_32BGRA:
190 case kCVPixelFormatType_32ARGB: {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200191 CVPixelBufferRef scaledPixelBuffer = NULL;
192 CVPixelBufferRef sourcePixelBuffer = NULL;
193 if ([self requiresCropping] ||
194 [self requiresScalingToWidth:i420Buffer.width height:i420Buffer.height]) {
195 CVPixelBufferCreate(
196 NULL, i420Buffer.width, i420Buffer.height, pixelFormat, NULL, &scaledPixelBuffer);
197 [self cropAndScaleTo:scaledPixelBuffer withTempBuffer:NULL];
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200198
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200199 CVPixelBufferLockBaseAddress(scaledPixelBuffer, kCVPixelBufferLock_ReadOnly);
200 sourcePixelBuffer = scaledPixelBuffer;
201 } else {
202 sourcePixelBuffer = _pixelBuffer;
203 }
204 const uint8_t* src = static_cast<uint8_t*>(CVPixelBufferGetBaseAddress(sourcePixelBuffer));
205 const size_t bytesPerRow = CVPixelBufferGetBytesPerRow(sourcePixelBuffer);
206
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200207 if (pixelFormat == kCVPixelFormatType_32BGRA) {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200208 // Corresponds to libyuv::FOURCC_ARGB
209 libyuv::ARGBToI420(src,
210 bytesPerRow,
211 i420Buffer.mutableDataY,
212 i420Buffer.strideY,
213 i420Buffer.mutableDataU,
214 i420Buffer.strideU,
215 i420Buffer.mutableDataV,
216 i420Buffer.strideV,
217 i420Buffer.width,
218 i420Buffer.height);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200219 } else if (pixelFormat == kCVPixelFormatType_32ARGB) {
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200220 // Corresponds to libyuv::FOURCC_BGRA
221 libyuv::BGRAToI420(src,
222 bytesPerRow,
223 i420Buffer.mutableDataY,
224 i420Buffer.strideY,
225 i420Buffer.mutableDataU,
226 i420Buffer.strideU,
227 i420Buffer.mutableDataV,
228 i420Buffer.strideV,
229 i420Buffer.width,
230 i420Buffer.height);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200231 }
232
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200233 if (scaledPixelBuffer) {
234 CVPixelBufferUnlockBaseAddress(scaledPixelBuffer, kCVPixelBufferLock_ReadOnly);
235 CVBufferRelease(scaledPixelBuffer);
236 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200237 break;
238 }
239 default: { RTC_NOTREACHED() << "Unsupported pixel format."; }
240 }
241
242 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
243
244 return i420Buffer;
245}
246
247- (void)cropAndScaleNV12To:(CVPixelBufferRef)outputPixelBuffer withTempBuffer:(uint8_t*)tmpBuffer {
Anders Carlssone5960ce2017-06-22 15:26:30 +0200248 // Prepare output pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200249 CVReturn cvRet = CVPixelBufferLockBaseAddress(outputPixelBuffer, 0);
250 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100251 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
Anders Carlssone5960ce2017-06-22 15:26:30 +0200252 }
253 const int dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
254 const int dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
255 uint8_t* dstY =
256 reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(outputPixelBuffer, 0));
257 const int dstYStride = CVPixelBufferGetBytesPerRowOfPlane(outputPixelBuffer, 0);
258 uint8_t* dstUV =
259 reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(outputPixelBuffer, 1));
260 const int dstUVStride = CVPixelBufferGetBytesPerRowOfPlane(outputPixelBuffer, 1);
261
262 // Prepare source pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200263 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200264 const uint8_t* srcY = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 0));
Anders Carlssone5960ce2017-06-22 15:26:30 +0200265 const int srcYStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 0);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200266 const uint8_t* srcUV = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 1));
Anders Carlssone5960ce2017-06-22 15:26:30 +0200267 const int srcUVStride = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 1);
268
269 // Crop just by modifying pointers.
270 srcY += srcYStride * _cropY + _cropX;
271 srcUV += srcUVStride * (_cropY / 2) + _cropX;
272
273 webrtc::NV12Scale(tmpBuffer,
274 srcY,
275 srcYStride,
276 srcUV,
277 srcUVStride,
278 _cropWidth,
279 _cropHeight,
280 dstY,
281 dstYStride,
282 dstUV,
283 dstUVStride,
284 dstWidth,
285 dstHeight);
286
287 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
288 CVPixelBufferUnlockBaseAddress(outputPixelBuffer, 0);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200289}
290
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200291- (void)cropAndScaleARGBTo:(CVPixelBufferRef)outputPixelBuffer {
292 // Prepare output pointers.
293 CVReturn cvRet = CVPixelBufferLockBaseAddress(outputPixelBuffer, 0);
294 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100295 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200296 }
297 const int dstWidth = CVPixelBufferGetWidth(outputPixelBuffer);
298 const int dstHeight = CVPixelBufferGetHeight(outputPixelBuffer);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200299
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200300 uint8_t* dst = reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddress(outputPixelBuffer));
301 const int dstStride = CVPixelBufferGetBytesPerRow(outputPixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200302
303 // Prepare source pointers.
Anders Carlssone5960ce2017-06-22 15:26:30 +0200304 CVPixelBufferLockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonfe9d8172018-04-03 11:40:39 +0200305 const uint8_t* src = static_cast<uint8_t*>(CVPixelBufferGetBaseAddress(_pixelBuffer));
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200306 const int srcStride = CVPixelBufferGetBytesPerRow(_pixelBuffer);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200307
308 // Crop just by modifying pointers.
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200309 src += srcStride * _cropY + _cropX;
310 libyuv::ARGBScale(src,
311 srcStride,
312 _cropWidth,
313 _cropHeight,
314 dst,
315 dstStride,
316 dstWidth,
317 dstHeight,
318 libyuv::kFilterBox);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200319
320 CVPixelBufferUnlockBaseAddress(_pixelBuffer, kCVPixelBufferLock_ReadOnly);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200321 CVPixelBufferUnlockBaseAddress(outputPixelBuffer, 0);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200322}
323
324@end