1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.core; 18 19 import static androidx.camera.core.ImageProcessingUtil.Result.ERROR_CONVERSION; 20 import static androidx.camera.core.ImageProcessingUtil.Result.SUCCESS; 21 22 import android.graphics.Bitmap; 23 import android.graphics.ImageFormat; 24 import android.media.Image; 25 import android.media.ImageWriter; 26 import android.os.Build; 27 import android.util.Log; 28 import android.view.Surface; 29 30 import androidx.annotation.IntRange; 31 import androidx.annotation.RequiresApi; 32 import androidx.annotation.RestrictTo; 33 import androidx.camera.core.impl.ImageOutputConfig; 34 import androidx.camera.core.impl.ImageReaderProxy; 35 import androidx.camera.core.internal.compat.ImageWriterCompat; 36 import androidx.camera.core.internal.utils.ImageUtil; 37 import androidx.core.util.Preconditions; 38 39 import org.jspecify.annotations.NonNull; 40 import org.jspecify.annotations.Nullable; 41 42 import java.nio.ByteBuffer; 43 import java.util.Locale; 44 45 /** 46 * Utility class to convert an {@link Image} from YUV to RGB. 47 * 48 */ 49 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 50 public final class ImageProcessingUtil { 51 52 private static final String TAG = "ImageProcessingUtil"; 53 public static final String JNI_LIB_NAME = "image_processing_util_jni"; 54 private static int sImageCount = 0; 55 56 static { 57 System.loadLibrary(JNI_LIB_NAME); 58 } 59 60 enum Result { 61 UNKNOWN, 62 SUCCESS, 63 ERROR_CONVERSION, // Native conversion error. 64 } 65 ImageProcessingUtil()66 private ImageProcessingUtil() { 67 } 68 69 /** 70 * Wraps a JPEG byte array with an {@link Image}. 71 * 72 * <p>This methods wraps the given byte array with an {@link Image} via the help of the 73 * given ImageReader. The image format of the ImageReader has to be JPEG, and the JPEG image 74 * size has to match the size of the ImageReader. 75 */ convertJpegBytesToImage( @onNull ImageReaderProxy jpegImageReaderProxy, byte @NonNull [] jpegBytes)76 public static @Nullable ImageProxy convertJpegBytesToImage( 77 @NonNull ImageReaderProxy jpegImageReaderProxy, 78 byte @NonNull [] jpegBytes) { 79 Preconditions.checkArgument(jpegImageReaderProxy.getImageFormat() == ImageFormat.JPEG); 80 Preconditions.checkNotNull(jpegBytes); 81 82 Surface surface = jpegImageReaderProxy.getSurface(); 83 Preconditions.checkNotNull(surface); 84 85 if (nativeWriteJpegToSurface(jpegBytes, surface) != 0) { 86 Logger.e(TAG, "Failed to enqueue JPEG image."); 87 return null; 88 } 89 90 final ImageProxy imageProxy = jpegImageReaderProxy.acquireLatestImage(); 91 if (imageProxy == null) { 92 Logger.e(TAG, "Failed to get acquire JPEG image."); 93 } 94 return imageProxy; 95 } 96 97 98 /** 99 * Copies information from a given Bitmap to the address of the ByteBuffer 100 * 101 * @param bitmap source bitmap 102 * @param byteBuffer destination ByteBuffer 103 * @param bufferStride the stride of the ByteBuffer 104 */ copyBitmapToByteBuffer(@onNull Bitmap bitmap, @NonNull ByteBuffer byteBuffer, int bufferStride)105 public static void copyBitmapToByteBuffer(@NonNull Bitmap bitmap, 106 @NonNull ByteBuffer byteBuffer, int bufferStride) { 107 int bitmapStride = bitmap.getRowBytes(); 108 int width = bitmap.getWidth(); 109 int height = bitmap.getHeight(); 110 nativeCopyBetweenByteBufferAndBitmap(bitmap, byteBuffer, bitmapStride, bufferStride, width, 111 height, false); 112 } 113 114 /** 115 * Copies information from a ByteBuffer to the address of the Bitmap 116 * 117 * @param bitmap destination Bitmap 118 * @param byteBuffer source ByteBuffer 119 * @param bufferStride the stride of the ByteBuffer 120 * 121 */ copyByteBufferToBitmap(@onNull Bitmap bitmap, @NonNull ByteBuffer byteBuffer, int bufferStride)122 public static void copyByteBufferToBitmap(@NonNull Bitmap bitmap, 123 @NonNull ByteBuffer byteBuffer, int bufferStride) { 124 int bitmapStride = bitmap.getRowBytes(); 125 int width = bitmap.getWidth(); 126 int height = bitmap.getHeight(); 127 nativeCopyBetweenByteBufferAndBitmap(bitmap, byteBuffer, bufferStride, bitmapStride, width, 128 height, true); 129 } 130 131 /** 132 * Writes a JPEG bytes data as an Image into the Surface. Returns true if it succeeds and false 133 * otherwise. 134 */ writeJpegBytesToSurface( @onNull Surface surface, byte @NonNull [] jpegBytes)135 public static boolean writeJpegBytesToSurface( 136 @NonNull Surface surface, 137 byte @NonNull [] jpegBytes) { 138 Preconditions.checkNotNull(jpegBytes); 139 Preconditions.checkNotNull(surface); 140 141 if (nativeWriteJpegToSurface(jpegBytes, surface) != 0) { 142 Logger.e(TAG, "Failed to enqueue JPEG image."); 143 return false; 144 } 145 return true; 146 } 147 148 /** 149 * Convert a YUV_420_888 Image to a JPEG bytes data as an Image into the Surface. 150 * 151 * <p>Returns true if it succeeds and false otherwise. 152 */ convertYuvToJpegBytesIntoSurface( @onNull Image image, @IntRange(from = 1, to = 100) int jpegQuality, @ImageOutputConfig.RotationDegreesValue int rotationDegrees, @NonNull Surface outputSurface)153 public static boolean convertYuvToJpegBytesIntoSurface( 154 @NonNull Image image, 155 @IntRange(from = 1, to = 100) int jpegQuality, 156 @ImageOutputConfig.RotationDegreesValue int rotationDegrees, 157 @NonNull Surface outputSurface) { 158 return convertYuvToJpegBytesIntoSurface(new AndroidImageProxy(image), jpegQuality, 159 rotationDegrees, outputSurface); 160 } 161 162 /** 163 * Convert a YUV_420_888 ImageProxy to a JPEG bytes data as an Image into the Surface. 164 * 165 * <p>Returns true if it succeeds and false otherwise. 166 */ convertYuvToJpegBytesIntoSurface( @onNull ImageProxy imageProxy, @IntRange(from = 1, to = 100) int jpegQuality, @ImageOutputConfig.RotationDegreesValue int rotationDegrees, @NonNull Surface outputSurface)167 public static boolean convertYuvToJpegBytesIntoSurface( 168 @NonNull ImageProxy imageProxy, 169 @IntRange(from = 1, to = 100) int jpegQuality, 170 @ImageOutputConfig.RotationDegreesValue int rotationDegrees, 171 @NonNull Surface outputSurface) { 172 try { 173 byte[] jpegBytes = 174 ImageUtil.yuvImageToJpegByteArray( 175 imageProxy, null, jpegQuality, rotationDegrees); 176 return writeJpegBytesToSurface(outputSurface, 177 jpegBytes); 178 } catch (ImageUtil.CodecFailedException e) { 179 Logger.e(TAG, "Failed to encode YUV to JPEG", e); 180 return false; 181 } 182 } 183 184 /** 185 * Converts image proxy in YUV to RGB. 186 * 187 * Currently this config supports the devices which generated NV21, NV12, I420 YUV layout, 188 * otherwise the input YUV layout will be converted to NV12 first and then to RGBA_8888 as a 189 * fallback. 190 * 191 * @param imageProxy input image proxy in YUV. 192 * @param rgbImageReaderProxy output image reader proxy in RGB. 193 * @param rgbConvertedBuffer intermediate image buffer for format conversion. 194 * @param rotationDegrees output image rotation degrees. 195 * @param onePixelShiftEnabled true if one pixel shift should be applied, otherwise false. 196 * @return output image proxy in RGB. 197 */ convertYUVToRGB( @onNull ImageProxy imageProxy, @NonNull ImageReaderProxy rgbImageReaderProxy, @Nullable ByteBuffer rgbConvertedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees, boolean onePixelShiftEnabled)198 public static @Nullable ImageProxy convertYUVToRGB( 199 @NonNull ImageProxy imageProxy, 200 @NonNull ImageReaderProxy rgbImageReaderProxy, 201 @Nullable ByteBuffer rgbConvertedBuffer, 202 @IntRange(from = 0, to = 359) int rotationDegrees, 203 boolean onePixelShiftEnabled) { 204 if (!isSupportedYUVFormat(imageProxy)) { 205 Logger.e(TAG, "Unsupported format for YUV to RGB"); 206 return null; 207 } 208 long startTimeMillis = System.currentTimeMillis(); 209 210 if (!isSupportedRotationDegrees(rotationDegrees)) { 211 Logger.e(TAG, "Unsupported rotation degrees for rotate RGB"); 212 return null; 213 } 214 215 // Convert YUV To RGB and write data to surface 216 Result result = convertYUVToRGBInternal( 217 imageProxy, 218 rgbImageReaderProxy.getSurface(), 219 rgbConvertedBuffer, 220 rotationDegrees, 221 onePixelShiftEnabled); 222 223 if (result == ERROR_CONVERSION) { 224 Logger.e(TAG, "YUV to RGB conversion failure"); 225 return null; 226 } 227 if (Log.isLoggable("MH", Log.DEBUG)) { 228 // The log is used to profile the ImageProcessing performance and only shows in the 229 // mobile harness tests. 230 Logger.d(TAG, String.format(Locale.US, 231 "Image processing performance profiling, duration: [%d], image count: %d", 232 (System.currentTimeMillis() - startTimeMillis), sImageCount)); 233 sImageCount++; 234 } 235 236 // Retrieve ImageProxy in RGB 237 final ImageProxy rgbImageProxy = rgbImageReaderProxy.acquireLatestImage(); 238 if (rgbImageProxy == null) { 239 Logger.e(TAG, "YUV to RGB acquireLatestImage failure"); 240 return null; 241 } 242 243 // Close ImageProxy for the next image 244 SingleCloseImageProxy wrappedRgbImageProxy = new SingleCloseImageProxy(rgbImageProxy); 245 wrappedRgbImageProxy.addOnImageCloseListener(image -> { 246 // Close YUV image proxy when RGB image proxy is closed by app. 247 if (rgbImageProxy != null && imageProxy != null) { 248 imageProxy.close(); 249 } 250 }); 251 return wrappedRgbImageProxy; 252 } 253 254 /** 255 * Converts image proxy in YUV to {@link Bitmap}. 256 * 257 * <p> Different from {@link ImageProcessingUtil#convertYUVToRGB( 258 * ImageProxy, ImageReaderProxy, ByteBuffer, int, boolean)}, this function converts to 259 * {@link Bitmap} in RGBA directly. If input format is invalid, 260 * {@link IllegalArgumentException} will be thrown. If the conversion to bitmap failed, 261 * {@link UnsupportedOperationException} will be thrown. 262 * 263 * @param imageProxy input image proxy in YUV. 264 * @return bitmap output bitmap in RGBA. 265 */ convertYUVToBitmap(@onNull ImageProxy imageProxy)266 public static @NonNull Bitmap convertYUVToBitmap(@NonNull ImageProxy imageProxy) { 267 if (imageProxy.getFormat() != ImageFormat.YUV_420_888) { 268 throw new IllegalArgumentException("Input image format must be YUV_420_888"); 269 } 270 271 int imageWidth = imageProxy.getWidth(); 272 int imageHeight = imageProxy.getHeight(); 273 int srcStrideY = imageProxy.getPlanes()[0].getRowStride(); 274 int srcStrideU = imageProxy.getPlanes()[1].getRowStride(); 275 int srcStrideV = imageProxy.getPlanes()[2].getRowStride(); 276 int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride(); 277 int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride(); 278 279 Bitmap bitmap = Bitmap.createBitmap(imageProxy.getWidth(), 280 imageProxy.getHeight(), Bitmap.Config.ARGB_8888); 281 int bitmapStride = bitmap.getRowBytes(); 282 283 int result = nativeConvertAndroid420ToBitmap( 284 imageProxy.getPlanes()[0].getBuffer(), 285 srcStrideY, 286 imageProxy.getPlanes()[1].getBuffer(), 287 srcStrideU, 288 imageProxy.getPlanes()[2].getBuffer(), 289 srcStrideV, 290 srcPixelStrideY, 291 srcPixelStrideUV, 292 bitmap, 293 bitmapStride, 294 imageWidth, 295 imageHeight); 296 if (result != 0) { 297 throw new UnsupportedOperationException("YUV to RGB conversion failed"); 298 } 299 return bitmap; 300 } 301 302 /** 303 * Applies one pixel shift workaround for YUV image 304 * 305 * @param imageProxy input image proxy in YUV. 306 * @return true if one pixel shift is applied successfully, otherwise false. 307 */ applyPixelShiftForYUV(@onNull ImageProxy imageProxy)308 public static boolean applyPixelShiftForYUV(@NonNull ImageProxy imageProxy) { 309 if (!isSupportedYUVFormat(imageProxy)) { 310 Logger.e(TAG, "Unsupported format for YUV to RGB"); 311 return false; 312 } 313 314 Result result = applyPixelShiftInternal(imageProxy); 315 316 if (result == ERROR_CONVERSION) { 317 Logger.e(TAG, "One pixel shift for YUV failure"); 318 return false; 319 } 320 return true; 321 } 322 323 /** 324 * Rotates YUV image proxy. 325 * 326 * @param imageProxy input image proxy. 327 * @param rotatedImageReaderProxy input image reader proxy. 328 * @param rotatedImageWriter output image writer. 329 * @param yRotatedBuffer intermediate image buffer for y plane rotation. 330 * @param uRotatedBuffer intermediate image buffer for u plane rotation. 331 * @param vRotatedBuffer intermediate image buffer for v plane rotation. 332 * @param rotationDegrees output image rotation degrees. 333 * @return rotated image proxy or null if rotation fails or format is not supported. 334 */ rotateYUV( @onNull ImageProxy imageProxy, @NonNull ImageReaderProxy rotatedImageReaderProxy, @NonNull ImageWriter rotatedImageWriter, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees)335 public static @Nullable ImageProxy rotateYUV( 336 @NonNull ImageProxy imageProxy, 337 @NonNull ImageReaderProxy rotatedImageReaderProxy, 338 @NonNull ImageWriter rotatedImageWriter, 339 @NonNull ByteBuffer yRotatedBuffer, 340 @NonNull ByteBuffer uRotatedBuffer, 341 @NonNull ByteBuffer vRotatedBuffer, 342 @IntRange(from = 0, to = 359) int rotationDegrees) { 343 if (!isSupportedYUVFormat(imageProxy)) { 344 Logger.e(TAG, "Unsupported format for rotate YUV"); 345 return null; 346 } 347 348 if (!isSupportedRotationDegrees(rotationDegrees)) { 349 Logger.e(TAG, "Unsupported rotation degrees for rotate YUV"); 350 return null; 351 } 352 353 Result result = ERROR_CONVERSION; 354 355 // YUV rotation is checking non-zero rotation degrees in java layer to avoid unnecessary 356 // overhead, while RGB rotation is checking in c++ layer. 357 if (Build.VERSION.SDK_INT >= 23 && rotationDegrees > 0) { 358 result = rotateYUVInternal( 359 imageProxy, 360 rotatedImageWriter, 361 yRotatedBuffer, 362 uRotatedBuffer, 363 vRotatedBuffer, 364 rotationDegrees); 365 } 366 367 if (result == ERROR_CONVERSION) { 368 Logger.e(TAG, "rotate YUV failure"); 369 return null; 370 } 371 372 // Retrieve ImageProxy in rotated YUV 373 ImageProxy rotatedImageProxy = rotatedImageReaderProxy.acquireLatestImage(); 374 if (rotatedImageProxy == null) { 375 Logger.e(TAG, "YUV rotation acquireLatestImage failure"); 376 return null; 377 } 378 379 SingleCloseImageProxy wrappedRotatedImageProxy = new SingleCloseImageProxy( 380 rotatedImageProxy); 381 wrappedRotatedImageProxy.addOnImageCloseListener(image -> { 382 // Close original YUV image proxy when rotated YUV image is closed by app. 383 if (rotatedImageProxy != null && imageProxy != null) { 384 imageProxy.close(); 385 } 386 }); 387 388 return wrappedRotatedImageProxy; 389 } 390 391 392 /** 393 * Rotates YUV image proxy and output the NV21 format image proxy with the delegated byte 394 * buffers. 395 * 396 * @param imageProxy input image proxy. 397 * @param yRotatedBuffer intermediate image buffer for y plane rotation. 398 * @param uRotatedBuffer intermediate image buffer for u plane rotation. 399 * @param vRotatedBuffer intermediate image buffer for v plane rotation. 400 * @param nv21YDelegatedBuffer delegated image buffer for y plane. 401 * @param nv21UVDelegatedBuffer delegated image buffer for u/v plane. 402 * @param rotationDegrees output image rotation degrees. 403 * @return rotated image proxy or null if rotation fails or format is not supported. 404 */ rotateYUVAndConvertToNV21( @onNull ImageProxy imageProxy, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @NonNull ByteBuffer nv21YDelegatedBuffer, @NonNull ByteBuffer nv21UVDelegatedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees)405 public static @Nullable ImageProxy rotateYUVAndConvertToNV21( 406 @NonNull ImageProxy imageProxy, 407 @NonNull ByteBuffer yRotatedBuffer, 408 @NonNull ByteBuffer uRotatedBuffer, 409 @NonNull ByteBuffer vRotatedBuffer, 410 @NonNull ByteBuffer nv21YDelegatedBuffer, 411 @NonNull ByteBuffer nv21UVDelegatedBuffer, 412 @IntRange(from = 0, to = 359) int rotationDegrees) { 413 if (!isSupportedYUVFormat(imageProxy)) { 414 Logger.e(TAG, "Unsupported format for rotate YUV"); 415 return null; 416 } 417 418 if (!isSupportedRotationDegrees(rotationDegrees)) { 419 Logger.e(TAG, "Unsupported rotation degrees for rotate YUV"); 420 return null; 421 } 422 423 // If both rotation and format conversion processing are unnecessary, directly return here. 424 if (rotationDegrees == 0 && isNV21FormatImage(imageProxy)) { 425 return null; 426 } 427 428 int rotatedWidth = 429 (rotationDegrees % 180 == 0) ? imageProxy.getWidth() : imageProxy.getHeight(); 430 int rotatedHeight = 431 (rotationDegrees % 180 == 0) ? imageProxy.getHeight() : imageProxy.getWidth(); 432 433 ByteBuffer position1ChildByteBuffer = nativeNewDirectByteBuffer( 434 nv21UVDelegatedBuffer, 1, nv21UVDelegatedBuffer.capacity()); 435 436 int result = nativeRotateYUV( 437 imageProxy.getPlanes()[0].getBuffer(), 438 imageProxy.getPlanes()[0].getRowStride(), 439 imageProxy.getPlanes()[1].getBuffer(), 440 imageProxy.getPlanes()[1].getRowStride(), 441 imageProxy.getPlanes()[2].getBuffer(), 442 imageProxy.getPlanes()[2].getRowStride(), 443 imageProxy.getPlanes()[2].getPixelStride(), 444 nv21YDelegatedBuffer, 445 rotatedWidth, 446 1, 447 position1ChildByteBuffer, 448 rotatedWidth, 449 2, 450 nv21UVDelegatedBuffer, 451 rotatedWidth, 452 2, 453 yRotatedBuffer, 454 uRotatedBuffer, 455 vRotatedBuffer, 456 imageProxy.getWidth(), 457 imageProxy.getHeight(), 458 rotationDegrees); 459 460 if (result != 0) { 461 Logger.e(TAG, "rotate YUV failure"); 462 return null; 463 } 464 465 // Wraps to NV21ImageProxy to make sure that the returned v plane position is in front of u 466 // plane position. 467 return new SingleCloseImageProxy( 468 new NV21ImageProxy(imageProxy, 469 nv21YDelegatedBuffer, 470 position1ChildByteBuffer, 471 nv21UVDelegatedBuffer, 472 rotatedWidth, 473 rotatedHeight, 474 rotationDegrees)); 475 } 476 isSupportedYUVFormat(@onNull ImageProxy imageProxy)477 private static boolean isSupportedYUVFormat(@NonNull ImageProxy imageProxy) { 478 return imageProxy.getFormat() == ImageFormat.YUV_420_888 479 && imageProxy.getPlanes().length == 3; 480 } 481 isSupportedRotationDegrees( @ntRangefrom = 0, to = 359) int rotationDegrees)482 private static boolean isSupportedRotationDegrees( 483 @IntRange(from = 0, to = 359) int rotationDegrees) { 484 return rotationDegrees == 0 485 || rotationDegrees == 90 486 || rotationDegrees == 180 487 || rotationDegrees == 270; 488 } 489 convertYUVToRGBInternal( @onNull ImageProxy imageProxy, @NonNull Surface surface, @Nullable ByteBuffer rgbConvertedBuffer, @ImageOutputConfig.RotationDegreesValue int rotation, boolean onePixelShiftEnabled)490 private static @NonNull Result convertYUVToRGBInternal( 491 @NonNull ImageProxy imageProxy, 492 @NonNull Surface surface, 493 @Nullable ByteBuffer rgbConvertedBuffer, 494 @ImageOutputConfig.RotationDegreesValue int rotation, 495 boolean onePixelShiftEnabled) { 496 int imageWidth = imageProxy.getWidth(); 497 int imageHeight = imageProxy.getHeight(); 498 int srcStrideY = imageProxy.getPlanes()[0].getRowStride(); 499 int srcStrideU = imageProxy.getPlanes()[1].getRowStride(); 500 int srcStrideV = imageProxy.getPlanes()[2].getRowStride(); 501 int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride(); 502 int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride(); 503 504 int startOffsetY = onePixelShiftEnabled ? srcPixelStrideY : 0; 505 int startOffsetU = onePixelShiftEnabled ? srcPixelStrideUV : 0; 506 int startOffsetV = onePixelShiftEnabled ? srcPixelStrideUV : 0; 507 508 int result = nativeConvertAndroid420ToABGR( 509 imageProxy.getPlanes()[0].getBuffer(), 510 srcStrideY, 511 imageProxy.getPlanes()[1].getBuffer(), 512 srcStrideU, 513 imageProxy.getPlanes()[2].getBuffer(), 514 srcStrideV, 515 srcPixelStrideY, 516 srcPixelStrideUV, 517 surface, 518 rgbConvertedBuffer, 519 imageWidth, 520 imageHeight, 521 startOffsetY, 522 startOffsetU, 523 startOffsetV, 524 rotation); 525 if (result != 0) { 526 return ERROR_CONVERSION; 527 } 528 return SUCCESS; 529 } 530 applyPixelShiftInternal(@onNull ImageProxy imageProxy)531 private static @NonNull Result applyPixelShiftInternal(@NonNull ImageProxy imageProxy) { 532 int imageWidth = imageProxy.getWidth(); 533 int imageHeight = imageProxy.getHeight(); 534 int srcStrideY = imageProxy.getPlanes()[0].getRowStride(); 535 int srcStrideU = imageProxy.getPlanes()[1].getRowStride(); 536 int srcStrideV = imageProxy.getPlanes()[2].getRowStride(); 537 int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride(); 538 int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride(); 539 540 int startOffsetY = srcPixelStrideY; 541 int startOffsetU = srcPixelStrideUV; 542 int startOffsetV = srcPixelStrideUV; 543 544 int result = nativeShiftPixel( 545 imageProxy.getPlanes()[0].getBuffer(), 546 srcStrideY, 547 imageProxy.getPlanes()[1].getBuffer(), 548 srcStrideU, 549 imageProxy.getPlanes()[2].getBuffer(), 550 srcStrideV, 551 srcPixelStrideY, 552 srcPixelStrideUV, 553 imageWidth, 554 imageHeight, 555 startOffsetY, 556 startOffsetU, 557 startOffsetV); 558 if (result != 0) { 559 return ERROR_CONVERSION; 560 } 561 return SUCCESS; 562 } 563 564 @RequiresApi(23) rotateYUVInternal( @onNull ImageProxy imageProxy, @NonNull ImageWriter rotatedImageWriter, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)565 private static @Nullable Result rotateYUVInternal( 566 @NonNull ImageProxy imageProxy, 567 @NonNull ImageWriter rotatedImageWriter, 568 @NonNull ByteBuffer yRotatedBuffer, 569 @NonNull ByteBuffer uRotatedBuffer, 570 @NonNull ByteBuffer vRotatedBuffer, 571 @ImageOutputConfig.RotationDegreesValue int rotationDegrees) { 572 int imageWidth = imageProxy.getWidth(); 573 int imageHeight = imageProxy.getHeight(); 574 int srcStrideY = imageProxy.getPlanes()[0].getRowStride(); 575 int srcStrideU = imageProxy.getPlanes()[1].getRowStride(); 576 int srcStrideV = imageProxy.getPlanes()[2].getRowStride(); 577 int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride(); 578 579 Image rotatedImage = ImageWriterCompat.dequeueInputImage(rotatedImageWriter); 580 if (rotatedImage == null) { 581 return ERROR_CONVERSION; 582 } 583 584 int result = nativeRotateYUV( 585 imageProxy.getPlanes()[0].getBuffer(), 586 srcStrideY, 587 imageProxy.getPlanes()[1].getBuffer(), 588 srcStrideU, 589 imageProxy.getPlanes()[2].getBuffer(), 590 srcStrideV, 591 srcPixelStrideUV, 592 rotatedImage.getPlanes()[0].getBuffer(), 593 rotatedImage.getPlanes()[0].getRowStride(), 594 rotatedImage.getPlanes()[0].getPixelStride(), 595 rotatedImage.getPlanes()[1].getBuffer(), 596 rotatedImage.getPlanes()[1].getRowStride(), 597 rotatedImage.getPlanes()[1].getPixelStride(), 598 rotatedImage.getPlanes()[2].getBuffer(), 599 rotatedImage.getPlanes()[2].getRowStride(), 600 rotatedImage.getPlanes()[2].getPixelStride(), 601 yRotatedBuffer, 602 uRotatedBuffer, 603 vRotatedBuffer, 604 imageWidth, 605 imageHeight, 606 rotationDegrees); 607 608 if (result != 0) { 609 return ERROR_CONVERSION; 610 } 611 612 ImageWriterCompat.queueInputImage(rotatedImageWriter, rotatedImage); 613 return SUCCESS; 614 } 615 616 /** 617 * Checks whether the image proxy data is formatted in NV21. 618 */ isNV21FormatImage(@onNull ImageProxy imageProxy)619 public static boolean isNV21FormatImage(@NonNull ImageProxy imageProxy) { 620 if (imageProxy.getPlanes().length != 3) { 621 return false; 622 } 623 if (imageProxy.getPlanes()[1].getPixelStride() != 2) { 624 return false; 625 } 626 return nativeGetYUVImageVUOff( 627 imageProxy.getPlanes()[2].getBuffer(), 628 imageProxy.getPlanes()[1].getBuffer()) == -1; 629 } 630 631 /** 632 * A wrapper to make sure that the returned v plane position (getPlanes()[2]) is in front of 633 * u plane position (getPlanes()[1]). So that the following operations can correctly check 634 * whether the format is NV12 or NV21 by the plane buffers' pointer positions. 635 * 636 * <p>The callers need to ensure that the v data is put in the plane with former position and v 637 * data is put in the plane with the later position in the associated image proxy. 638 */ 639 private static class NV21ImageProxy extends ForwardingImageProxy { 640 private final ImageProxy.PlaneProxy[] mPlanes; 641 private final int mWidth; 642 private final int mHeight; 643 NV21ImageProxy(@onNull ImageProxy imageProxy, @NonNull ByteBuffer delegateBufferY, @NonNull ByteBuffer delegateBufferU, @NonNull ByteBuffer delegateBufferV, int width, int height, @IntRange(from = 0, to = 359) int rotatedRotationDegrees)644 NV21ImageProxy(@NonNull ImageProxy imageProxy, 645 @NonNull ByteBuffer delegateBufferY, 646 @NonNull ByteBuffer delegateBufferU, 647 @NonNull ByteBuffer delegateBufferV, 648 int width, int height, 649 @IntRange(from = 0, to = 359) int rotatedRotationDegrees) { 650 super(imageProxy); 651 mPlanes = createPlanes(delegateBufferY, delegateBufferU, delegateBufferV, width); 652 mWidth = width; 653 mHeight = height; 654 } 655 656 @Override getHeight()657 public int getHeight() { 658 return mHeight; 659 } 660 661 @Override getWidth()662 public int getWidth() { 663 return mWidth; 664 } 665 666 @Override getPlanes()667 public ImageProxy.PlaneProxy @NonNull [] getPlanes() { 668 return mPlanes; 669 } 670 createPlanes( @onNull ByteBuffer delegateBufferY, @NonNull ByteBuffer delegateBufferU, @NonNull ByteBuffer delegateBufferV, int rowStride )671 private ImageProxy.PlaneProxy @NonNull [] createPlanes( 672 @NonNull ByteBuffer delegateBufferY, 673 @NonNull ByteBuffer delegateBufferU, 674 @NonNull ByteBuffer delegateBufferV, 675 int rowStride 676 ) { 677 ImageProxy.PlaneProxy[] planes = new ImageProxy.PlaneProxy[3]; 678 planes[0] = new ImageProxy.PlaneProxy() { 679 @Override 680 public int getRowStride() { 681 return rowStride; 682 } 683 684 @Override 685 public int getPixelStride() { 686 return 1; 687 } 688 689 @Override 690 public @NonNull ByteBuffer getBuffer() { 691 return delegateBufferY; 692 } 693 }; 694 planes[1] = new NV21PlaneProxy(delegateBufferU, rowStride); 695 planes[2] = new NV21PlaneProxy(delegateBufferV, rowStride); 696 697 return planes; 698 } 699 } 700 701 private static class NV21PlaneProxy implements ImageProxy.PlaneProxy { 702 private final ByteBuffer mByteBuffer; 703 private final int mRowStride; 704 NV21PlaneProxy(@onNull ByteBuffer byteBuffer, int rowStride)705 NV21PlaneProxy(@NonNull ByteBuffer byteBuffer, int rowStride) { 706 mByteBuffer = byteBuffer; 707 mRowStride = rowStride; 708 } 709 710 @Override getRowStride()711 public int getRowStride() { 712 return mRowStride; 713 } 714 715 @Override getPixelStride()716 public int getPixelStride() { 717 // Force return pixel stride value 2 718 return 2; 719 } 720 721 @Override getBuffer()722 public @NonNull ByteBuffer getBuffer() { 723 return mByteBuffer; 724 } 725 } 726 nativeCopyBetweenByteBufferAndBitmap(Bitmap bitmap, ByteBuffer byteBuffer, int sourceStride, int destinationStride, int width, int height, boolean isCopyBufferToBitmap)727 private static native int nativeCopyBetweenByteBufferAndBitmap(Bitmap bitmap, 728 ByteBuffer byteBuffer, 729 int sourceStride, int destinationStride, int width, int height, 730 boolean isCopyBufferToBitmap); 731 732 nativeWriteJpegToSurface(byte @NonNull [] jpegArray, @NonNull Surface surface)733 private static native int nativeWriteJpegToSurface(byte @NonNull [] jpegArray, 734 @NonNull Surface surface); 735 nativeConvertAndroid420ToABGR( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, @Nullable Surface surface, @Nullable ByteBuffer convertedByteBufferRGB, int width, int height, int startOffsetY, int startOffsetU, int startOffsetV, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)736 private static native int nativeConvertAndroid420ToABGR( 737 @NonNull ByteBuffer srcByteBufferY, 738 int srcStrideY, 739 @NonNull ByteBuffer srcByteBufferU, 740 int srcStrideU, 741 @NonNull ByteBuffer srcByteBufferV, 742 int srcStrideV, 743 int srcPixelStrideY, 744 int srcPixelStrideUV, 745 @Nullable Surface surface, 746 @Nullable ByteBuffer convertedByteBufferRGB, 747 int width, 748 int height, 749 int startOffsetY, 750 int startOffsetU, 751 int startOffsetV, 752 @ImageOutputConfig.RotationDegreesValue int rotationDegrees); 753 nativeConvertAndroid420ToBitmap( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, @NonNull Bitmap bitmap, int bitmapStride, int width, int height)754 private static native int nativeConvertAndroid420ToBitmap( 755 @NonNull ByteBuffer srcByteBufferY, 756 int srcStrideY, 757 @NonNull ByteBuffer srcByteBufferU, 758 int srcStrideU, 759 @NonNull ByteBuffer srcByteBufferV, 760 int srcStrideV, 761 int srcPixelStrideY, 762 int srcPixelStrideUV, 763 @NonNull Bitmap bitmap, 764 int bitmapStride, 765 int width, 766 int height); 767 nativeShiftPixel( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, int width, int height, int startOffsetY, int startOffsetU, int startOffsetV)768 private static native int nativeShiftPixel( 769 @NonNull ByteBuffer srcByteBufferY, 770 int srcStrideY, 771 @NonNull ByteBuffer srcByteBufferU, 772 int srcStrideU, 773 @NonNull ByteBuffer srcByteBufferV, 774 int srcStrideV, 775 int srcPixelStrideY, 776 int srcPixelStrideUV, 777 int width, 778 int height, 779 int startOffsetY, 780 int startOffsetU, 781 int startOffsetV); 782 nativeRotateYUV( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideUV, @NonNull ByteBuffer dstByteBufferY, int dstStrideY, int dstPixelStrideY, @NonNull ByteBuffer dstByteBufferU, int dstStrideU, int dstPixelStrideU, @NonNull ByteBuffer dstByteBufferV, int dstStrideV, int dstPixelStrideV, @NonNull ByteBuffer rotatedByteBufferY, @NonNull ByteBuffer rotatedByteBufferU, @NonNull ByteBuffer rotatedByteBufferV, int width, int height, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)783 private static native int nativeRotateYUV( 784 @NonNull ByteBuffer srcByteBufferY, 785 int srcStrideY, 786 @NonNull ByteBuffer srcByteBufferU, 787 int srcStrideU, 788 @NonNull ByteBuffer srcByteBufferV, 789 int srcStrideV, 790 int srcPixelStrideUV, 791 @NonNull ByteBuffer dstByteBufferY, 792 int dstStrideY, 793 int dstPixelStrideY, 794 @NonNull ByteBuffer dstByteBufferU, 795 int dstStrideU, 796 int dstPixelStrideU, 797 @NonNull ByteBuffer dstByteBufferV, 798 int dstStrideV, 799 int dstPixelStrideV, 800 @NonNull ByteBuffer rotatedByteBufferY, 801 @NonNull ByteBuffer rotatedByteBufferU, 802 @NonNull ByteBuffer rotatedByteBufferV, 803 int width, 804 int height, 805 @ImageOutputConfig.RotationDegreesValue int rotationDegrees); 806 807 /** 808 * Checks the V and U planes' ByteBuffer start position pointer distance. 809 * 810 * <p>This can be used to determine whether the YUV image is NV12 or NV21 data type. 811 */ nativeGetYUVImageVUOff( @onNull ByteBuffer srcByteBufferV, @NonNull ByteBuffer srcByteBufferU )812 public static native int nativeGetYUVImageVUOff( 813 @NonNull ByteBuffer srcByteBufferV, 814 @NonNull ByteBuffer srcByteBufferU 815 ); 816 817 /** 818 * Creates ByteBuffer from the offset of the input byte buffer. 819 */ nativeNewDirectByteBuffer( @onNull ByteBuffer byteBuffer, int offset, int capacity )820 public static native @NonNull ByteBuffer nativeNewDirectByteBuffer( 821 @NonNull ByteBuffer byteBuffer, 822 int offset, 823 int capacity 824 ); 825 } 826