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