1 /* 2 * Copyright 2013 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 android.hardware.camera2.cts; 18 19 import static com.android.ex.camera2.blocking.BlockingStateCallback.*; 20 21 import android.graphics.BitmapFactory; 22 import android.graphics.ImageFormat; 23 import android.graphics.PointF; 24 import android.graphics.Rect; 25 import android.hardware.camera2.CameraAccessException; 26 import android.hardware.camera2.CameraCaptureSession; 27 import android.hardware.camera2.CameraDevice; 28 import android.hardware.camera2.CameraManager; 29 import android.hardware.camera2.CameraCharacteristics; 30 import android.hardware.camera2.CaptureFailure; 31 import android.hardware.camera2.CaptureRequest; 32 import android.hardware.camera2.CaptureResult; 33 import android.hardware.camera2.TotalCaptureResult; 34 import android.hardware.cts.helpers.CameraUtils; 35 import android.util.Size; 36 import android.hardware.camera2.params.MeteringRectangle; 37 import android.hardware.camera2.params.StreamConfigurationMap; 38 import android.media.Image; 39 import android.media.ImageReader; 40 import android.media.Image.Plane; 41 import android.os.Handler; 42 import android.util.Log; 43 import android.view.Surface; 44 45 import com.android.ex.camera2.blocking.BlockingCameraManager; 46 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 47 import com.android.ex.camera2.blocking.BlockingSessionCallback; 48 import com.android.ex.camera2.blocking.BlockingStateCallback; 49 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 50 51 import junit.framework.Assert; 52 53 import org.mockito.Mockito; 54 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.lang.reflect.Array; 58 import java.nio.ByteBuffer; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Collections; 62 import java.util.Comparator; 63 import java.util.List; 64 import java.util.concurrent.LinkedBlockingQueue; 65 import java.util.concurrent.TimeUnit; 66 67 /** 68 * A package private utility class for wrapping up the camera2 cts test common utility functions 69 */ 70 public class CameraTestUtils extends Assert { 71 private static final String TAG = "CameraTestUtils"; 72 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 73 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 74 public static final Size SIZE_BOUND_1080P = new Size(1920, 1088); 75 public static final Size SIZE_BOUND_2160P = new Size(3840, 2160); 76 // Only test the preview size that is no larger than 1080p. 77 public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P; 78 // Default timeouts for reaching various states 79 public static final int CAMERA_OPEN_TIMEOUT_MS = 3000; 80 public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000; 81 public static final int CAMERA_IDLE_TIMEOUT_MS = 3000; 82 public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000; 83 public static final int CAMERA_BUSY_TIMEOUT_MS = 1000; 84 public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000; 85 public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000; 86 public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000; 87 public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000; 88 89 public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000; 90 public static final int SESSION_CLOSE_TIMEOUT_MS = 3000; 91 public static final int SESSION_READY_TIMEOUT_MS = 3000; 92 public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000; 93 94 public static final int MAX_READER_IMAGES = 5; 95 96 /** 97 * Create an {@link android.media.ImageReader} object and get the surface. 98 * 99 * @param size The size of this ImageReader to be created. 100 * @param format The format of this ImageReader to be created 101 * @param maxNumImages The max number of images that can be acquired simultaneously. 102 * @param listener The listener used by this ImageReader to notify callbacks. 103 * @param handler The handler to use for any listener callbacks. 104 */ 105 public static ImageReader makeImageReader(Size size, int format, int maxNumImages, 106 ImageReader.OnImageAvailableListener listener, Handler handler) { 107 ImageReader reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, 108 maxNumImages); 109 reader.setOnImageAvailableListener(listener, handler); 110 if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size); 111 return reader; 112 } 113 114 /** 115 * Close pending images and clean up an {@link android.media.ImageReader} object. 116 * @param reader an {@link android.media.ImageReader} to close. 117 */ 118 public static void closeImageReader(ImageReader reader) { 119 if (reader != null) { 120 reader.close(); 121 } 122 } 123 124 /** 125 * Dummy listener that release the image immediately once it is available. 126 * 127 * <p> 128 * It can be used for the case where we don't care the image data at all. 129 * </p> 130 */ 131 public static class ImageDropperListener implements ImageReader.OnImageAvailableListener { 132 @Override 133 public void onImageAvailable(ImageReader reader) { 134 Image image = null; 135 try { 136 image = reader.acquireNextImage(); 137 } finally { 138 if (image != null) { 139 image.close(); 140 } 141 } 142 } 143 } 144 145 /** 146 * Image listener that release the image immediately after validating the image 147 */ 148 public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener { 149 private Size mSize; 150 private int mFormat; 151 152 public ImageVerifierListener(Size sz, int format) { 153 mSize = sz; 154 mFormat = format; 155 } 156 157 @Override 158 public void onImageAvailable(ImageReader reader) { 159 Image image = null; 160 try { 161 image = reader.acquireNextImage(); 162 } finally { 163 if (image != null) { 164 validateImage(image, mSize.getWidth(), mSize.getHeight(), mFormat, null); 165 image.close(); 166 } 167 } 168 } 169 } 170 171 public static class SimpleImageReaderListener 172 implements ImageReader.OnImageAvailableListener { 173 private final LinkedBlockingQueue<Image> mQueue = 174 new LinkedBlockingQueue<Image>(); 175 176 @Override 177 public void onImageAvailable(ImageReader reader) { 178 try { 179 mQueue.put(reader.acquireNextImage()); 180 } catch (InterruptedException e) { 181 throw new UnsupportedOperationException( 182 "Can't handle InterruptedException in onImageAvailable"); 183 } 184 } 185 186 /** 187 * Get an image from the image reader. 188 * 189 * @param timeout Timeout value for the wait. 190 * @return The image from the image reader. 191 */ 192 public Image getImage(long timeout) throws InterruptedException { 193 Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 194 assertNotNull("Wait for an image timed out in " + timeout + "ms", image); 195 return image; 196 } 197 } 198 199 public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback { 200 private final LinkedBlockingQueue<CaptureResult> mQueue = 201 new LinkedBlockingQueue<CaptureResult>(); 202 203 @Override 204 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, 205 long timestamp, long frameNumber) 206 { 207 } 208 209 @Override 210 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 211 TotalCaptureResult result) { 212 try { 213 mQueue.put(result); 214 } catch (InterruptedException e) { 215 throw new UnsupportedOperationException( 216 "Can't handle InterruptedException in onCaptureCompleted"); 217 } 218 } 219 220 @Override 221 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 222 CaptureFailure failure) { 223 } 224 225 @Override 226 public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, 227 long frameNumber) { 228 } 229 230 public CaptureResult getCaptureResult(long timeout) { 231 try { 232 CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 233 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result); 234 return result; 235 } catch (InterruptedException e) { 236 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 237 } 238 } 239 240 /** 241 * Get the {@link #CaptureResult capture result} for a given 242 * {@link #CaptureRequest capture request}. 243 * 244 * @param myRequest The {@link #CaptureRequest capture request} whose 245 * corresponding {@link #CaptureResult capture result} was 246 * being waited for 247 * @param numResultsWait Number of frames to wait for the capture result 248 * before timeout. 249 * @throws TimeoutRuntimeException If more than numResultsWait results are 250 * seen before the result matching myRequest arrives, or each 251 * individual wait for result times out after 252 * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms. 253 */ 254 public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest, 255 int numResultsWait) { 256 if (numResultsWait < 0) { 257 throw new IllegalArgumentException("numResultsWait must be no less than 0"); 258 } 259 260 CaptureResult result; 261 int i = 0; 262 do { 263 result = getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); 264 if (result.getRequest().equals(myRequest)) { 265 return result; 266 } 267 } while (i++ < numResultsWait); 268 269 throw new TimeoutRuntimeException("Unable to get the expected capture result after " 270 + "waiting for " + numResultsWait + " results"); 271 } 272 273 public boolean hasMoreResults() 274 { 275 return mQueue.isEmpty(); 276 } 277 } 278 279 /** 280 * Block until the camera is opened. 281 * 282 * <p>Don't use this to test #onDisconnected/#onError since this will throw 283 * an AssertionError if it fails to open the camera device.</p> 284 * 285 * @return CameraDevice opened camera device 286 * 287 * @throws IllegalArgumentException 288 * If the handler is null, or if the handler's looper is current. 289 * @throws CameraAccessException 290 * If open fails immediately. 291 * @throws BlockingOpenException 292 * If open fails after blocking for some amount of time. 293 * @throws TimeoutRuntimeException 294 * If opening times out. Typically unrecoverable. 295 */ 296 public static CameraDevice openCamera(CameraManager manager, String cameraId, 297 CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException, 298 BlockingOpenException { 299 300 /** 301 * Although camera2 API allows 'null' Handler (it will just use the current 302 * thread's Looper), this is not what we want for CTS. 303 * 304 * In CTS the default looper is used only to process events in between test runs, 305 * so anything sent there would not be executed inside a test and the test would fail. 306 * 307 * In this case, BlockingCameraManager#openCamera performs the check for us. 308 */ 309 return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler); 310 } 311 312 313 /** 314 * Block until the camera is opened. 315 * 316 * <p>Don't use this to test #onDisconnected/#onError since this will throw 317 * an AssertionError if it fails to open the camera device.</p> 318 * 319 * @throws IllegalArgumentException 320 * If the handler is null, or if the handler's looper is current. 321 * @throws CameraAccessException 322 * If open fails immediately. 323 * @throws BlockingOpenException 324 * If open fails after blocking for some amount of time. 325 * @throws TimeoutRuntimeException 326 * If opening times out. Typically unrecoverable. 327 */ 328 public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler) 329 throws CameraAccessException, 330 BlockingOpenException { 331 return openCamera(manager, cameraId, /*listener*/null, handler); 332 } 333 334 /** 335 * Configure a new camera session with output surfaces. 336 * 337 * @param camera The CameraDevice to be configured. 338 * @param outputSurfaces The surface list that used for camera output. 339 * @param listener The callback CameraDevice will notify when capture results are available. 340 */ 341 public static CameraCaptureSession configureCameraSession(CameraDevice camera, 342 List<Surface> outputSurfaces, 343 CameraCaptureSession.StateCallback listener, Handler handler) 344 throws CameraAccessException { 345 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 346 camera.createCaptureSession(outputSurfaces, sessionListener, handler); 347 348 return sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 349 } 350 351 public static <T> void assertArrayNotEmpty(T arr, String message) { 352 assertTrue(message, arr != null && Array.getLength(arr) > 0); 353 } 354 355 /** 356 * Check if the format is a legal YUV format camera supported. 357 */ 358 public static void checkYuvFormat(int format) { 359 if ((format != ImageFormat.YUV_420_888) && 360 (format != ImageFormat.NV21) && 361 (format != ImageFormat.YV12)) { 362 fail("Wrong formats: " + format); 363 } 364 } 365 366 /** 367 * Check if image size and format match given size and format. 368 */ 369 public static void checkImage(Image image, int width, int height, int format) { 370 assertNotNull("Input image is invalid", image); 371 assertEquals("Format doesn't match", format, image.getFormat()); 372 assertEquals("Width doesn't match", width, image.getWidth()); 373 assertEquals("Height doesn't match", height, image.getHeight()); 374 } 375 376 /** 377 * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked 378 * 1-D linear byte array, such that it can be write into disk, or accessed by 379 * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input 380 * Image format.</p> 381 * 382 * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains 383 * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any 384 * (xstride = width, ystride = height for chroma and luma components).</p> 385 * 386 * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p> 387 */ 388 public static byte[] getDataFromImage(Image image) { 389 assertNotNull("Invalid image:", image); 390 int format = image.getFormat(); 391 int width = image.getWidth(); 392 int height = image.getHeight(); 393 int rowStride, pixelStride; 394 byte[] data = null; 395 396 // Read image data 397 Plane[] planes = image.getPlanes(); 398 assertTrue("Fail to get image planes", planes != null && planes.length > 0); 399 400 // Check image validity 401 checkAndroidImageFormat(image); 402 403 ByteBuffer buffer = null; 404 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 405 if (format == ImageFormat.JPEG) { 406 buffer = planes[0].getBuffer(); 407 assertNotNull("Fail to get jpeg ByteBuffer", buffer); 408 data = new byte[buffer.remaining()]; 409 buffer.get(data); 410 buffer.rewind(); 411 return data; 412 } 413 414 int offset = 0; 415 data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; 416 int maxRowSize = planes[0].getRowStride(); 417 for (int i = 0; i < planes.length; i++) { 418 if (maxRowSize < planes[i].getRowStride()) { 419 maxRowSize = planes[i].getRowStride(); 420 } 421 } 422 byte[] rowData = new byte[maxRowSize]; 423 if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes"); 424 for (int i = 0; i < planes.length; i++) { 425 buffer = planes[i].getBuffer(); 426 assertNotNull("Fail to get bytebuffer from plane", buffer); 427 rowStride = planes[i].getRowStride(); 428 pixelStride = planes[i].getPixelStride(); 429 assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0); 430 if (VERBOSE) { 431 Log.v(TAG, "pixelStride " + pixelStride); 432 Log.v(TAG, "rowStride " + rowStride); 433 Log.v(TAG, "width " + width); 434 Log.v(TAG, "height " + height); 435 } 436 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 437 int w = (i == 0) ? width : width / 2; 438 int h = (i == 0) ? height : height / 2; 439 assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w); 440 for (int row = 0; row < h; row++) { 441 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 442 if (pixelStride == bytesPerPixel) { 443 // Special case: optimized read of the entire row 444 int length = w * bytesPerPixel; 445 buffer.get(data, offset, length); 446 // Advance buffer the remainder of the row stride 447 buffer.position(buffer.position() + rowStride - length); 448 offset += length; 449 } else { 450 // Generic case: should work for any pixelStride but slower. 451 // Use intermediate buffer to avoid read byte-by-byte from 452 // DirectByteBuffer, which is very bad for performance 453 buffer.get(rowData, 0, rowStride); 454 for (int col = 0; col < w; col++) { 455 data[offset++] = rowData[col * pixelStride]; 456 } 457 } 458 } 459 if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i); 460 buffer.rewind(); 461 } 462 return data; 463 } 464 465 /** 466 * <p>Check android image format validity for an image, only support below formats:</p> 467 * 468 * <p>YUV_420_888/NV21/YV12, can add more for future</p> 469 */ 470 public static void checkAndroidImageFormat(Image image) { 471 int format = image.getFormat(); 472 Plane[] planes = image.getPlanes(); 473 switch (format) { 474 case ImageFormat.YUV_420_888: 475 case ImageFormat.NV21: 476 case ImageFormat.YV12: 477 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length); 478 break; 479 case ImageFormat.JPEG: 480 case ImageFormat.RAW_SENSOR: 481 assertEquals("Jpeg Image should have one plane", 1, planes.length); 482 break; 483 default: 484 fail("Unsupported Image Format: " + format); 485 } 486 } 487 488 public static void dumpFile(String fileName, byte[] data) { 489 FileOutputStream outStream; 490 try { 491 Log.v(TAG, "output will be saved as " + fileName); 492 outStream = new FileOutputStream(fileName); 493 } catch (IOException ioe) { 494 throw new RuntimeException("Unable to create debug output file " + fileName, ioe); 495 } 496 497 try { 498 outStream.write(data); 499 outStream.close(); 500 } catch (IOException ioe) { 501 throw new RuntimeException("failed writing data to file " + fileName, ioe); 502 } 503 } 504 505 /** 506 * Get the available output sizes for the user-defined {@code format}. 507 * 508 * <p>Note that implementation-defined/hidden formats are not supported.</p> 509 */ 510 public static Size[] getSupportedSizeForFormat(int format, String cameraId, 511 CameraManager cameraManager) throws CameraAccessException { 512 CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId); 513 assertNotNull("Can't get camera characteristics!", properties); 514 if (VERBOSE) { 515 Log.v(TAG, "get camera characteristics for camera: " + cameraId); 516 } 517 StreamConfigurationMap configMap = 518 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 519 Size[] availableSizes = configMap.getOutputSizes(format); 520 assertArrayNotEmpty(availableSizes, "availableSizes should not be empty"); 521 if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes)); 522 return availableSizes; 523 } 524 525 /** 526 * Size comparator that compares the number of pixels it covers. 527 * 528 * <p>If two the areas of two sizes are same, compare the widths.</p> 529 */ 530 public static class SizeComparator implements Comparator<Size> { 531 @Override 532 public int compare(Size lhs, Size rhs) { 533 return CameraUtils 534 .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight()); 535 } 536 } 537 538 /** 539 * Get sorted size list in descending order. Remove the sizes larger than 540 * the bound. If the bound is null, don't do the size bound filtering. 541 */ 542 static public List<Size> getSupportedPreviewSizes(String cameraId, 543 CameraManager cameraManager, Size bound) throws CameraAccessException { 544 return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound); 545 } 546 547 /** 548 * Get a sorted list of sizes from a given size list. 549 * 550 * <p> 551 * The size is compare by area it covers, if the areas are same, then 552 * compare the widths. 553 * </p> 554 * 555 * @param sizeList The input size list to be sorted 556 * @param ascending True if the order is ascending, otherwise descending order 557 * @return The ordered list of sizes 558 */ 559 static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) { 560 if (sizeList == null) { 561 throw new IllegalArgumentException("sizeList shouldn't be null"); 562 } 563 564 Comparator<Size> comparator = new SizeComparator(); 565 List<Size> sortedSizes = new ArrayList<Size>(); 566 sortedSizes.addAll(sizeList); 567 Collections.sort(sortedSizes, comparator); 568 if (!ascending) { 569 Collections.reverse(sortedSizes); 570 } 571 572 return sortedSizes; 573 } 574 575 /** 576 * Get sorted (descending order) size list for given format. Remove the sizes larger than 577 * the bound. If the bound is null, don't do the size bound filtering. 578 */ 579 static private List<Size> getSortedSizesForFormat(String cameraId, 580 CameraManager cameraManager, int format, Size bound) throws CameraAccessException { 581 Comparator<Size> comparator = new SizeComparator(); 582 Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager); 583 List<Size> sortedSizes = null; 584 if (bound != null) { 585 sortedSizes = new ArrayList<Size>(/*capacity*/1); 586 for (Size sz : sizes) { 587 if (comparator.compare(sz, bound) <= 0) { 588 sortedSizes.add(sz); 589 } 590 } 591 } else { 592 sortedSizes = Arrays.asList(sizes); 593 } 594 assertTrue("Supported size list should have at least one element", 595 sortedSizes.size() > 0); 596 597 Collections.sort(sortedSizes, comparator); 598 // Make it in descending order. 599 Collections.reverse(sortedSizes); 600 return sortedSizes; 601 } 602 603 /** 604 * Get supported video size list for a given camera device. 605 * 606 * <p> 607 * Filter out the sizes that are larger than the bound. If the bound is 608 * null, don't do the size bound filtering. 609 * </p> 610 */ 611 static public List<Size> getSupportedVideoSizes(String cameraId, 612 CameraManager cameraManager, Size bound) throws CameraAccessException { 613 return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound); 614 } 615 616 /** 617 * Get supported video size list (descending order) for a given camera device. 618 * 619 * <p> 620 * Filter out the sizes that are larger than the bound. If the bound is 621 * null, don't do the size bound filtering. 622 * </p> 623 */ 624 static public List<Size> getSupportedStillSizes(String cameraId, 625 CameraManager cameraManager, Size bound) throws CameraAccessException { 626 return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound); 627 } 628 629 static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager) 630 throws CameraAccessException { 631 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null); 632 return sizes.get(sizes.size() - 1); 633 } 634 635 /** 636 * Get max supported preview size for a camera device. 637 */ 638 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager) 639 throws CameraAccessException { 640 return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null); 641 } 642 643 /** 644 * Get max preview size for a camera device in the supported sizes that are no larger 645 * than the bound. 646 */ 647 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound) 648 throws CameraAccessException { 649 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound); 650 return sizes.get(0); 651 } 652 653 /** 654 * Get the largest size by area. 655 * 656 * @param sizes an array of sizes, must have at least 1 element 657 * 658 * @return Largest Size 659 * 660 * @throws IllegalArgumentException if sizes was null or had 0 elements 661 */ 662 public static Size getMaxSize(Size[] sizes) { 663 if (sizes == null || sizes.length == 0) { 664 throw new IllegalArgumentException("sizes was empty"); 665 } 666 667 Size sz = sizes[0]; 668 for (Size size : sizes) { 669 if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) { 670 sz = size; 671 } 672 } 673 674 return sz; 675 } 676 677 /** 678 * Get object array from byte array. 679 * 680 * @param array Input byte array to be converted 681 * @return Byte object array converted from input byte array 682 */ 683 public static Byte[] toObject(byte[] array) { 684 return convertPrimitiveArrayToObjectArray(array, Byte.class); 685 } 686 687 /** 688 * Get object array from int array. 689 * 690 * @param array Input int array to be converted 691 * @return Integer object array converted from input int array 692 */ 693 public static Integer[] toObject(int[] array) { 694 return convertPrimitiveArrayToObjectArray(array, Integer.class); 695 } 696 697 /** 698 * Get object array from float array. 699 * 700 * @param array Input float array to be converted 701 * @return Float object array converted from input float array 702 */ 703 public static Float[] toObject(float[] array) { 704 return convertPrimitiveArrayToObjectArray(array, Float.class); 705 } 706 707 /** 708 * Get object array from double array. 709 * 710 * @param array Input double array to be converted 711 * @return Double object array converted from input double array 712 */ 713 public static Double[] toObject(double[] array) { 714 return convertPrimitiveArrayToObjectArray(array, Double.class); 715 } 716 717 /** 718 * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]). 719 * 720 * @param array Input array object 721 * @param wrapperClass The boxed class it converts to 722 * @return Boxed version of primitive array 723 */ 724 private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array, 725 final Class<T> wrapperClass) { 726 // getLength does the null check and isArray check already. 727 int arrayLength = Array.getLength(array); 728 if (arrayLength == 0) { 729 throw new IllegalArgumentException("Input array shouldn't be empty"); 730 } 731 732 @SuppressWarnings("unchecked") 733 final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength); 734 for (int i = 0; i < arrayLength; i++) { 735 Array.set(result, i, Array.get(array, i)); 736 } 737 return result; 738 } 739 740 /** 741 * Validate image based on format and size. 742 * <p> 743 * Only RAW_SENSOR, YUV420_888 and JPEG formats are supported. Calling this 744 * method with other formats will cause a UnsupportedOperationException. 745 * </p> 746 * 747 * @param image The image to be validated. 748 * @param width The image width. 749 * @param height The image height. 750 * @param format The image format. 751 * @param filePath The debug dump file path, null if don't want to dump to 752 * file. 753 * @throws UnsupportedOperationException if calling with format other than 754 * RAW_SENSOR, YUV420_888 or JPEG. 755 */ 756 public static void validateImage(Image image, int width, int height, int format, 757 String filePath) { 758 checkImage(image, width, height, format); 759 760 /** 761 * TODO: validate timestamp: 762 * 1. capture result timestamp against the image timestamp (need 763 * consider frame drops) 764 * 2. timestamps should be monotonically increasing for different requests 765 */ 766 if(VERBOSE) Log.v(TAG, "validating Image"); 767 byte[] data = getDataFromImage(image); 768 assertTrue("Invalid image data", data != null && data.length > 0); 769 770 switch (format) { 771 case ImageFormat.JPEG: 772 validateJpegData(data, width, height, filePath); 773 break; 774 case ImageFormat.YUV_420_888: 775 validateYuvData(data, width, height, format, image.getTimestamp(), filePath); 776 break; 777 case ImageFormat.RAW_SENSOR: 778 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath); 779 break; 780 default: 781 throw new UnsupportedOperationException("Unsupported format for validation: " 782 + format); 783 } 784 } 785 786 /** 787 * Provide a mock for {@link CameraDevice.StateCallback}. 788 * 789 * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an 790 * abstract class.</p> 791 * 792 * <p> 793 * Use this instead of other classes when needing to verify interactions, since 794 * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra 795 * interactions which will cause false test failures. 796 * </p> 797 * 798 */ 799 public static class MockStateCallback extends CameraDevice.StateCallback { 800 801 @Override 802 public void onOpened(CameraDevice camera) { 803 } 804 805 @Override 806 public void onDisconnected(CameraDevice camera) { 807 } 808 809 @Override 810 public void onError(CameraDevice camera, int error) { 811 } 812 813 private MockStateCallback() {} 814 815 /** 816 * Create a Mockito-ready mocked StateCallback. 817 */ 818 public static MockStateCallback mock() { 819 return Mockito.spy(new MockStateCallback()); 820 } 821 } 822 823 private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) { 824 BitmapFactory.Options bmpOptions = new BitmapFactory.Options(); 825 // DecodeBound mode: only parse the frame header to get width/height. 826 // it doesn't decode the pixel. 827 bmpOptions.inJustDecodeBounds = true; 828 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions); 829 assertEquals(width, bmpOptions.outWidth); 830 assertEquals(height, bmpOptions.outHeight); 831 832 // Pixel decoding mode: decode whole image. check if the image data 833 // is decodable here. 834 assertNotNull("Decoding jpeg failed", 835 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length)); 836 if (DEBUG && filePath != null) { 837 String fileName = 838 filePath + "/" + width + "x" + height + ".jpeg"; 839 dumpFile(fileName, jpegData); 840 } 841 } 842 843 private static void validateYuvData(byte[] yuvData, int width, int height, int format, 844 long ts, String filePath) { 845 checkYuvFormat(format); 846 if (VERBOSE) Log.v(TAG, "Validating YUV data"); 847 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 848 assertEquals("Yuv data doesn't match", expectedSize, yuvData.length); 849 850 // TODO: Can add data validation for test pattern. 851 852 if (DEBUG && filePath != null) { 853 String fileName = 854 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv"; 855 dumpFile(fileName, yuvData); 856 } 857 } 858 859 private static void validateRaw16Data(byte[] rawData, int width, int height, int format, 860 long ts, String filePath) { 861 if (VERBOSE) Log.v(TAG, "Validating raw data"); 862 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 863 assertEquals("Yuv data doesn't match", expectedSize, rawData.length); 864 865 // TODO: Can add data validation for test pattern. 866 867 if (DEBUG && filePath != null) { 868 String fileName = 869 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16"; 870 dumpFile(fileName, rawData); 871 } 872 873 return; 874 } 875 876 public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) { 877 if (result == null) { 878 throw new IllegalArgumentException("Result must not be null"); 879 } 880 881 T value = result.get(key); 882 assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value); 883 return value; 884 } 885 886 /** 887 * Get a crop region for a given zoom factor and center position. 888 * <p> 889 * The center position is normalized position in range of [0, 1.0], where 890 * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right 891 * corner. The center position could limit the effective minimal zoom 892 * factor, for example, if the center position is (0.75, 0.75), the 893 * effective minimal zoom position becomes 2.0. If the requested zoom factor 894 * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned. 895 * </p> 896 * <p> 897 * The aspect ratio of the crop region is maintained the same as the aspect 898 * ratio of active array. 899 * </p> 900 * 901 * @param zoomFactor The zoom factor to generate the crop region, it must be 902 * >= 1.0 903 * @param center The normalized zoom center point that is in the range of [0, 1]. 904 * @param maxZoom The max zoom factor supported by this device. 905 * @param activeArray The active array size of this device. 906 * @return crop region for the given normalized center and zoom factor. 907 */ 908 public static Rect getCropRegionForZoom(float zoomFactor, final PointF center, 909 final float maxZoom, final Rect activeArray) { 910 if (zoomFactor < 1.0) { 911 throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0"); 912 } 913 if (center.x > 1.0 || center.x < 0) { 914 throw new IllegalArgumentException("center.x " + center.x 915 + " should be in range of [0, 1.0]"); 916 } 917 if (center.y > 1.0 || center.y < 0) { 918 throw new IllegalArgumentException("center.y " + center.y 919 + " should be in range of [0, 1.0]"); 920 } 921 if (maxZoom < 1.0) { 922 throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0"); 923 } 924 if (activeArray == null) { 925 throw new IllegalArgumentException("activeArray must not be null"); 926 } 927 928 float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x), 929 Math.min(center.y, 1.0f - center.y)); 930 float minEffectiveZoom = 0.5f / minCenterLength; 931 if (minEffectiveZoom > maxZoom) { 932 throw new IllegalArgumentException("Requested center " + center.toString() + 933 " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max" 934 + " zoom factor " + maxZoom); 935 } 936 937 if (zoomFactor < minEffectiveZoom) { 938 Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor " 939 + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom); 940 zoomFactor = minEffectiveZoom; 941 } 942 943 int cropCenterX = (int)(activeArray.width() * center.x); 944 int cropCenterY = (int)(activeArray.height() * center.y); 945 int cropWidth = (int) (activeArray.width() / zoomFactor); 946 int cropHeight = (int) (activeArray.height() / zoomFactor); 947 948 return new Rect( 949 /*left*/cropCenterX - cropWidth / 2, 950 /*top*/cropCenterY - cropHeight / 2, 951 /*right*/ cropCenterX + cropWidth / 2 - 1, 952 /*bottom*/cropCenterY + cropHeight / 2 - 1); 953 } 954 955 /** 956 * Calculate output 3A region from the intersection of input 3A region and cropped region. 957 * 958 * @param requestRegions The input 3A regions 959 * @param cropRect The cropped region 960 * @return expected 3A regions output in capture result 961 */ 962 public static MeteringRectangle[] getExpectedOutputRegion( 963 MeteringRectangle[] requestRegions, Rect cropRect){ 964 MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length]; 965 for (int i = 0; i < requestRegions.length; i++) { 966 Rect requestRect = requestRegions[i].getRect(); 967 Rect resultRect = new Rect(); 968 assertTrue("Input 3A region must intersect cropped region", 969 resultRect.setIntersect(requestRect, cropRect)); 970 resultRegions[i] = new MeteringRectangle( 971 resultRect, 972 requestRegions[i].getMeteringWeight()); 973 } 974 return resultRegions; 975 } 976 } 977