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 android.graphics.Bitmap; 20 import android.graphics.BitmapFactory; 21 import android.graphics.ImageFormat; 22 import android.graphics.PointF; 23 import android.graphics.Rect; 24 import android.hardware.camera2.CameraAccessException; 25 import android.hardware.camera2.CameraCaptureSession; 26 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; 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.cts.helpers.CameraErrorCollector; 34 import android.hardware.camera2.cts.helpers.StaticMetadata; 35 import android.hardware.camera2.params.InputConfiguration; 36 import android.hardware.camera2.TotalCaptureResult; 37 import android.hardware.cts.helpers.CameraUtils; 38 import android.hardware.camera2.params.MeteringRectangle; 39 import android.hardware.camera2.params.StreamConfigurationMap; 40 import android.location.Location; 41 import android.location.LocationManager; 42 import android.media.ExifInterface; 43 import android.media.Image; 44 import android.media.ImageReader; 45 import android.media.ImageWriter; 46 import android.media.Image.Plane; 47 import android.os.Build; 48 import android.os.Environment; 49 import android.os.Handler; 50 import android.util.Log; 51 import android.util.Pair; 52 import android.util.Size; 53 import android.view.Display; 54 import android.view.Surface; 55 import android.view.WindowManager; 56 57 import com.android.ex.camera2.blocking.BlockingCameraManager; 58 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 59 import com.android.ex.camera2.blocking.BlockingSessionCallback; 60 import com.android.ex.camera2.blocking.BlockingStateCallback; 61 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 62 63 import junit.framework.Assert; 64 65 import org.mockito.Mockito; 66 67 import java.io.FileOutputStream; 68 import java.io.IOException; 69 import java.lang.reflect.Array; 70 import java.nio.ByteBuffer; 71 import java.util.ArrayList; 72 import java.util.Arrays; 73 import java.util.Collections; 74 import java.util.Comparator; 75 import java.util.Date; 76 import java.util.HashMap; 77 import java.util.List; 78 import java.util.concurrent.atomic.AtomicLong; 79 import java.util.concurrent.LinkedBlockingQueue; 80 import java.util.concurrent.Semaphore; 81 import java.util.concurrent.TimeUnit; 82 import java.text.ParseException; 83 import java.text.SimpleDateFormat; 84 85 /** 86 * A package private utility class for wrapping up the camera2 cts test common utility functions 87 */ 88 public class CameraTestUtils extends Assert { 89 private static final String TAG = "CameraTestUtils"; 90 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 91 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 92 public static final Size SIZE_BOUND_1080P = new Size(1920, 1088); 93 public static final Size SIZE_BOUND_2160P = new Size(3840, 2160); 94 // Only test the preview size that is no larger than 1080p. 95 public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P; 96 // Default timeouts for reaching various states 97 public static final int CAMERA_OPEN_TIMEOUT_MS = 3000; 98 public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000; 99 public static final int CAMERA_IDLE_TIMEOUT_MS = 3000; 100 public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000; 101 public static final int CAMERA_BUSY_TIMEOUT_MS = 1000; 102 public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000; 103 public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000; 104 public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000; 105 public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000; 106 107 public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000; 108 public static final int SESSION_CLOSE_TIMEOUT_MS = 3000; 109 public static final int SESSION_READY_TIMEOUT_MS = 3000; 110 public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000; 111 112 public static final int MAX_READER_IMAGES = 5; 113 114 private static final int EXIF_DATETIME_LENGTH = 19; 115 private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60; 116 private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f; 117 private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f; 118 private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f; 119 private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f; 120 121 private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER); 122 private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER); 123 private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER); 124 125 protected static final String DEBUG_FILE_NAME_BASE = 126 Environment.getExternalStorageDirectory().getPath(); 127 128 static { 129 sTestLocation0.setTime(1199145600L); 130 sTestLocation0.setLatitude(37.736071); 131 sTestLocation0.setLongitude(-122.441983); 132 sTestLocation0.setAltitude(21.0); 133 134 sTestLocation1.setTime(1199145601L); 135 sTestLocation1.setLatitude(0.736071); 136 sTestLocation1.setLongitude(0.441983); 137 sTestLocation1.setAltitude(1.0); 138 139 sTestLocation2.setTime(1199145602L); 140 sTestLocation2.setLatitude(-89.736071); 141 sTestLocation2.setLongitude(-179.441983); 142 sTestLocation2.setAltitude(100000.0); 143 } 144 145 // Exif test data vectors. 146 public static final ExifTestData[] EXIF_TEST_DATA = { 147 new ExifTestData( 148 /*gpsLocation*/ sTestLocation0, 149 /* orientation */90, 150 /* jpgQuality */(byte) 80, 151 /* thumbQuality */(byte) 75), 152 new ExifTestData( 153 /*gpsLocation*/ sTestLocation1, 154 /* orientation */180, 155 /* jpgQuality */(byte) 90, 156 /* thumbQuality */(byte) 85), 157 new ExifTestData( 158 /*gpsLocation*/ sTestLocation2, 159 /* orientation */270, 160 /* jpgQuality */(byte) 100, 161 /* thumbQuality */(byte) 100) 162 }; 163 164 /** 165 * Create an {@link android.media.ImageReader} object and get the surface. 166 * 167 * @param size The size of this ImageReader to be created. 168 * @param format The format of this ImageReader to be created 169 * @param maxNumImages The max number of images that can be acquired simultaneously. 170 * @param listener The listener used by this ImageReader to notify callbacks. 171 * @param handler The handler to use for any listener callbacks. 172 */ makeImageReader(Size size, int format, int maxNumImages, ImageReader.OnImageAvailableListener listener, Handler handler)173 public static ImageReader makeImageReader(Size size, int format, int maxNumImages, 174 ImageReader.OnImageAvailableListener listener, Handler handler) { 175 ImageReader reader; 176 reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, 177 maxNumImages); 178 reader.setOnImageAvailableListener(listener, handler); 179 if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size); 180 return reader; 181 } 182 183 /** 184 * Create an ImageWriter and hook up the ImageListener. 185 * 186 * @param inputSurface The input surface of the ImageWriter. 187 * @param maxImages The max number of Images that can be dequeued simultaneously. 188 * @param listener The listener used by this ImageWriter to notify callbacks 189 * @param handler The handler to post listener callbacks. 190 * @return ImageWriter object created. 191 */ makeImageWriter( Surface inputSurface, int maxImages, ImageWriter.OnImageReleasedListener listener, Handler handler)192 public static ImageWriter makeImageWriter( 193 Surface inputSurface, int maxImages, 194 ImageWriter.OnImageReleasedListener listener, Handler handler) { 195 ImageWriter writer = ImageWriter.newInstance(inputSurface, maxImages); 196 writer.setOnImageReleasedListener(listener, handler); 197 return writer; 198 } 199 200 /** 201 * Close pending images and clean up an {@link android.media.ImageReader} object. 202 * @param reader an {@link android.media.ImageReader} to close. 203 */ closeImageReader(ImageReader reader)204 public static void closeImageReader(ImageReader reader) { 205 if (reader != null) { 206 reader.close(); 207 } 208 } 209 210 /** 211 * Close pending images and clean up an {@link android.media.ImageWriter} object. 212 * @param writer an {@link android.media.ImageWriter} to close. 213 */ closeImageWriter(ImageWriter writer)214 public static void closeImageWriter(ImageWriter writer) { 215 if (writer != null) { 216 writer.close(); 217 } 218 } 219 220 /** 221 * Dummy listener that release the image immediately once it is available. 222 * 223 * <p> 224 * It can be used for the case where we don't care the image data at all. 225 * </p> 226 */ 227 public static class ImageDropperListener implements ImageReader.OnImageAvailableListener { 228 @Override onImageAvailable(ImageReader reader)229 public void onImageAvailable(ImageReader reader) { 230 Image image = null; 231 try { 232 image = reader.acquireNextImage(); 233 } finally { 234 if (image != null) { 235 image.close(); 236 } 237 } 238 } 239 } 240 241 /** 242 * Image listener that release the image immediately after validating the image 243 */ 244 public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener { 245 private Size mSize; 246 private int mFormat; 247 ImageVerifierListener(Size sz, int format)248 public ImageVerifierListener(Size sz, int format) { 249 mSize = sz; 250 mFormat = format; 251 } 252 253 @Override onImageAvailable(ImageReader reader)254 public void onImageAvailable(ImageReader reader) { 255 Image image = null; 256 try { 257 image = reader.acquireNextImage(); 258 } finally { 259 if (image != null) { 260 validateImage(image, mSize.getWidth(), mSize.getHeight(), mFormat, null); 261 image.close(); 262 } 263 } 264 } 265 } 266 267 public static class SimpleImageReaderListener 268 implements ImageReader.OnImageAvailableListener { 269 private final LinkedBlockingQueue<Image> mQueue = 270 new LinkedBlockingQueue<Image>(); 271 // Indicate whether this listener will drop images or not, 272 // when the queued images reaches the reader maxImages 273 private final boolean mAsyncMode; 274 // maxImages held by the queue in async mode. 275 private final int mMaxImages; 276 277 /** 278 * Create a synchronous SimpleImageReaderListener that queues the images 279 * automatically when they are available, no image will be dropped. If 280 * the caller doesn't call getImage(), the producer will eventually run 281 * into buffer starvation. 282 */ SimpleImageReaderListener()283 public SimpleImageReaderListener() { 284 mAsyncMode = false; 285 mMaxImages = 0; 286 } 287 288 /** 289 * Create a synchronous/asynchronous SimpleImageReaderListener that 290 * queues the images automatically when they are available. For 291 * asynchronous listener, image will be dropped if the queued images 292 * reach to maxImages queued. If the caller doesn't call getImage(), the 293 * producer will not be blocked. For synchronous listener, no image will 294 * be dropped. If the caller doesn't call getImage(), the producer will 295 * eventually run into buffer starvation. 296 * 297 * @param asyncMode If the listener is operating at asynchronous mode. 298 * @param maxImages The max number of images held by this listener. 299 */ 300 /** 301 * 302 * @param asyncMode 303 */ SimpleImageReaderListener(boolean asyncMode, int maxImages)304 public SimpleImageReaderListener(boolean asyncMode, int maxImages) { 305 mAsyncMode = asyncMode; 306 mMaxImages = maxImages; 307 } 308 309 @Override onImageAvailable(ImageReader reader)310 public void onImageAvailable(ImageReader reader) { 311 try { 312 mQueue.put(reader.acquireNextImage()); 313 if (mAsyncMode && mQueue.size() >= mMaxImages) { 314 Image img = mQueue.poll(); 315 img.close(); 316 } 317 } catch (InterruptedException e) { 318 throw new UnsupportedOperationException( 319 "Can't handle InterruptedException in onImageAvailable"); 320 } 321 } 322 323 /** 324 * Get an image from the image reader. 325 * 326 * @param timeout Timeout value for the wait. 327 * @return The image from the image reader. 328 */ getImage(long timeout)329 public Image getImage(long timeout) throws InterruptedException { 330 Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 331 assertNotNull("Wait for an image timed out in " + timeout + "ms", image); 332 return image; 333 } 334 335 /** 336 * Drain the pending images held by this listener currently. 337 * 338 */ drain()339 public void drain() { 340 for (int i = 0; i < mQueue.size(); i++) { 341 Image image = mQueue.poll(); 342 assertNotNull("Unable to get an image", image); 343 image.close(); 344 } 345 } 346 } 347 348 public static class SimpleImageWriterListener implements ImageWriter.OnImageReleasedListener { 349 private final Semaphore mImageReleasedSema = new Semaphore(0); 350 private final ImageWriter mWriter; 351 @Override onImageReleased(ImageWriter writer)352 public void onImageReleased(ImageWriter writer) { 353 if (writer != mWriter) { 354 return; 355 } 356 357 if (VERBOSE) { 358 Log.v(TAG, "Input image is released"); 359 } 360 mImageReleasedSema.release(); 361 } 362 SimpleImageWriterListener(ImageWriter writer)363 public SimpleImageWriterListener(ImageWriter writer) { 364 if (writer == null) { 365 throw new IllegalArgumentException("writer cannot be null"); 366 } 367 mWriter = writer; 368 } 369 waitForImageReleased(long timeoutMs)370 public void waitForImageReleased(long timeoutMs) throws InterruptedException { 371 if (!mImageReleasedSema.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 372 fail("wait for image available timed out after " + timeoutMs + "ms"); 373 } 374 } 375 } 376 377 public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback { 378 private final LinkedBlockingQueue<TotalCaptureResult> mQueue = 379 new LinkedBlockingQueue<TotalCaptureResult>(); 380 private final LinkedBlockingQueue<CaptureFailure> mFailureQueue = 381 new LinkedBlockingQueue<>(); 382 // Pair<CaptureRequest, Long> is a pair of capture request and timestamp. 383 private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue = 384 new LinkedBlockingQueue<>(); 385 386 private AtomicLong mNumFramesArrived = new AtomicLong(0); 387 388 @Override onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber)389 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, 390 long timestamp, long frameNumber) { 391 try { 392 mCaptureStartQueue.put(new Pair(request, timestamp)); 393 } catch (InterruptedException e) { 394 throw new UnsupportedOperationException( 395 "Can't handle InterruptedException in onCaptureStarted"); 396 } 397 } 398 399 @Override onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)400 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 401 TotalCaptureResult result) { 402 try { 403 mNumFramesArrived.incrementAndGet(); 404 mQueue.put(result); 405 } catch (InterruptedException e) { 406 throw new UnsupportedOperationException( 407 "Can't handle InterruptedException in onCaptureCompleted"); 408 } 409 } 410 411 @Override onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)412 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 413 CaptureFailure failure) { 414 try { 415 mFailureQueue.put(failure); 416 } catch (InterruptedException e) { 417 throw new UnsupportedOperationException( 418 "Can't handle InterruptedException in onCaptureFailed"); 419 } 420 } 421 422 @Override onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber)423 public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, 424 long frameNumber) { 425 } 426 getTotalNumFrames()427 public long getTotalNumFrames() { 428 return mNumFramesArrived.get(); 429 } 430 getCaptureResult(long timeout)431 public CaptureResult getCaptureResult(long timeout) { 432 return getTotalCaptureResult(timeout); 433 } 434 getCaptureResult(long timeout, long timestamp)435 public TotalCaptureResult getCaptureResult(long timeout, long timestamp) { 436 try { 437 long currentTs = -1L; 438 TotalCaptureResult result; 439 while (true) { 440 result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 441 if (result == null) { 442 throw new RuntimeException( 443 "Wait for a capture result timed out in " + timeout + "ms"); 444 } 445 currentTs = result.get(CaptureResult.SENSOR_TIMESTAMP); 446 if (currentTs == timestamp) { 447 return result; 448 } 449 } 450 451 } catch (InterruptedException e) { 452 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 453 } 454 } 455 getTotalCaptureResult(long timeout)456 public TotalCaptureResult getTotalCaptureResult(long timeout) { 457 try { 458 TotalCaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 459 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result); 460 return result; 461 } catch (InterruptedException e) { 462 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 463 } 464 } 465 466 /** 467 * Get the {@link #CaptureResult capture result} for a given 468 * {@link #CaptureRequest capture request}. 469 * 470 * @param myRequest The {@link #CaptureRequest capture request} whose 471 * corresponding {@link #CaptureResult capture result} was 472 * being waited for 473 * @param numResultsWait Number of frames to wait for the capture result 474 * before timeout. 475 * @throws TimeoutRuntimeException If more than numResultsWait results are 476 * seen before the result matching myRequest arrives, or each 477 * individual wait for result times out after 478 * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms. 479 */ getCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)480 public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest, 481 int numResultsWait) { 482 return getTotalCaptureResultForRequest(myRequest, numResultsWait); 483 } 484 485 /** 486 * Get the {@link #TotalCaptureResult total capture result} for a given 487 * {@link #CaptureRequest capture request}. 488 * 489 * @param myRequest The {@link #CaptureRequest capture request} whose 490 * corresponding {@link #TotalCaptureResult capture result} was 491 * being waited for 492 * @param numResultsWait Number of frames to wait for the capture result 493 * before timeout. 494 * @throws TimeoutRuntimeException If more than numResultsWait results are 495 * seen before the result matching myRequest arrives, or each 496 * individual wait for result times out after 497 * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms. 498 */ getTotalCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)499 public TotalCaptureResult getTotalCaptureResultForRequest(CaptureRequest myRequest, 500 int numResultsWait) { 501 ArrayList<CaptureRequest> captureRequests = new ArrayList<>(1); 502 captureRequests.add(myRequest); 503 return getTotalCaptureResultsForRequests(captureRequests, numResultsWait)[0]; 504 } 505 506 /** 507 * Get an array of {@link #TotalCaptureResult total capture results} for a given list of 508 * {@link #CaptureRequest capture requests}. This can be used when the order of results 509 * may not the same as the order of requests. 510 * 511 * @param captureRequests The list of {@link #CaptureRequest capture requests} whose 512 * corresponding {@link #TotalCaptureResult capture results} are 513 * being waited for. 514 * @param numResultsWait Number of frames to wait for the capture results 515 * before timeout. 516 * @throws TimeoutRuntimeException If more than numResultsWait results are 517 * seen before all the results matching captureRequests arrives. 518 */ getTotalCaptureResultsForRequests( List<CaptureRequest> captureRequests, int numResultsWait)519 public TotalCaptureResult[] getTotalCaptureResultsForRequests( 520 List<CaptureRequest> captureRequests, int numResultsWait) { 521 if (numResultsWait < 0) { 522 throw new IllegalArgumentException("numResultsWait must be no less than 0"); 523 } 524 if (captureRequests == null || captureRequests.size() == 0) { 525 throw new IllegalArgumentException("captureRequests must have at least 1 request."); 526 } 527 528 // Create a request -> a list of result indices map that it will wait for. 529 HashMap<CaptureRequest, ArrayList<Integer>> remainingResultIndicesMap = new HashMap<>(); 530 for (int i = 0; i < captureRequests.size(); i++) { 531 CaptureRequest request = captureRequests.get(i); 532 ArrayList<Integer> indices = remainingResultIndicesMap.get(request); 533 if (indices == null) { 534 indices = new ArrayList<>(); 535 remainingResultIndicesMap.put(request, indices); 536 } 537 indices.add(i); 538 } 539 540 TotalCaptureResult[] results = new TotalCaptureResult[captureRequests.size()]; 541 int i = 0; 542 do { 543 TotalCaptureResult result = getTotalCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); 544 CaptureRequest request = result.getRequest(); 545 ArrayList<Integer> indices = remainingResultIndicesMap.get(request); 546 if (indices != null) { 547 results[indices.get(0)] = result; 548 indices.remove(0); 549 550 // Remove the entry if all results for this request has been fulfilled. 551 if (indices.isEmpty()) { 552 remainingResultIndicesMap.remove(request); 553 } 554 } 555 556 if (remainingResultIndicesMap.isEmpty()) { 557 return results; 558 } 559 } while (i++ < numResultsWait); 560 561 throw new TimeoutRuntimeException("Unable to get the expected capture result after " 562 + "waiting for " + numResultsWait + " results"); 563 } 564 565 /** 566 * Get an array list of {@link #CaptureFailure capture failure} with maxNumFailures entries 567 * at most. If it times out before maxNumFailures failures are received, return the failures 568 * received so far. 569 * 570 * @param maxNumFailures The maximal number of failures to return. If it times out before 571 * the maximal number of failures are received, return the received 572 * failures so far. 573 * @throws UnsupportedOperationException If an error happens while waiting on the failure. 574 */ getCaptureFailures(long maxNumFailures)575 public ArrayList<CaptureFailure> getCaptureFailures(long maxNumFailures) { 576 ArrayList<CaptureFailure> failures = new ArrayList<>(); 577 try { 578 for (int i = 0; i < maxNumFailures; i++) { 579 CaptureFailure failure = mFailureQueue.poll(CAPTURE_RESULT_TIMEOUT_MS, 580 TimeUnit.MILLISECONDS); 581 if (failure == null) { 582 // If waiting on a failure times out, return the failures so far. 583 break; 584 } 585 failures.add(failure); 586 } 587 } catch (InterruptedException e) { 588 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 589 } 590 591 return failures; 592 } 593 594 /** 595 * Wait until the capture start of a request and expected timestamp arrives or it times 596 * out after a number of capture starts. 597 * 598 * @param request The request for the capture start to wait for. 599 * @param timestamp The timestamp for the capture start to wait for. 600 * @param numCaptureStartsWait The number of capture start events to wait for before timing 601 * out. 602 */ waitForCaptureStart(CaptureRequest request, Long timestamp, int numCaptureStartsWait)603 public void waitForCaptureStart(CaptureRequest request, Long timestamp, 604 int numCaptureStartsWait) throws Exception { 605 Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp); 606 607 int i = 0; 608 do { 609 Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll( 610 CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 611 612 if (shutter == null) { 613 throw new TimeoutRuntimeException("Unable to get any more capture start " + 614 "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms."); 615 } else if (expectedShutter.equals(shutter)) { 616 return; 617 } 618 619 } while (i++ < numCaptureStartsWait); 620 621 throw new TimeoutRuntimeException("Unable to get the expected capture start " + 622 "event after waiting for " + numCaptureStartsWait + " capture starts"); 623 } 624 hasMoreResults()625 public boolean hasMoreResults() 626 { 627 return mQueue.isEmpty(); 628 } 629 drain()630 public void drain() { 631 mQueue.clear(); 632 mNumFramesArrived.getAndSet(0); 633 mFailureQueue.clear(); 634 mCaptureStartQueue.clear(); 635 } 636 } 637 638 /** 639 * Block until the camera is opened. 640 * 641 * <p>Don't use this to test #onDisconnected/#onError since this will throw 642 * an AssertionError if it fails to open the camera device.</p> 643 * 644 * @return CameraDevice opened camera device 645 * 646 * @throws IllegalArgumentException 647 * If the handler is null, or if the handler's looper is current. 648 * @throws CameraAccessException 649 * If open fails immediately. 650 * @throws BlockingOpenException 651 * If open fails after blocking for some amount of time. 652 * @throws TimeoutRuntimeException 653 * If opening times out. Typically unrecoverable. 654 */ openCamera(CameraManager manager, String cameraId, CameraDevice.StateCallback listener, Handler handler)655 public static CameraDevice openCamera(CameraManager manager, String cameraId, 656 CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException, 657 BlockingOpenException { 658 659 /** 660 * Although camera2 API allows 'null' Handler (it will just use the current 661 * thread's Looper), this is not what we want for CTS. 662 * 663 * In CTS the default looper is used only to process events in between test runs, 664 * so anything sent there would not be executed inside a test and the test would fail. 665 * 666 * In this case, BlockingCameraManager#openCamera performs the check for us. 667 */ 668 return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler); 669 } 670 671 672 /** 673 * Block until the camera is opened. 674 * 675 * <p>Don't use this to test #onDisconnected/#onError since this will throw 676 * an AssertionError if it fails to open the camera device.</p> 677 * 678 * @throws IllegalArgumentException 679 * If the handler is null, or if the handler's looper is current. 680 * @throws CameraAccessException 681 * If open fails immediately. 682 * @throws BlockingOpenException 683 * If open fails after blocking for some amount of time. 684 * @throws TimeoutRuntimeException 685 * If opening times out. Typically unrecoverable. 686 */ openCamera(CameraManager manager, String cameraId, Handler handler)687 public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler) 688 throws CameraAccessException, 689 BlockingOpenException { 690 return openCamera(manager, cameraId, /*listener*/null, handler); 691 } 692 693 /** 694 * Configure a new camera session with output surfaces and type. 695 * 696 * @param camera The CameraDevice to be configured. 697 * @param outputSurfaces The surface list that used for camera output. 698 * @param listener The callback CameraDevice will notify when capture results are available. 699 */ configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, boolean isHighSpeed, CameraCaptureSession.StateCallback listener, Handler handler)700 public static CameraCaptureSession configureCameraSession(CameraDevice camera, 701 List<Surface> outputSurfaces, boolean isHighSpeed, 702 CameraCaptureSession.StateCallback listener, Handler handler) 703 throws CameraAccessException { 704 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 705 if (isHighSpeed) { 706 camera.createConstrainedHighSpeedCaptureSession(outputSurfaces, 707 sessionListener, handler); 708 } else { 709 camera.createCaptureSession(outputSurfaces, sessionListener, handler); 710 } 711 CameraCaptureSession session = 712 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 713 assertFalse("Camera session should not be a reprocessable session", 714 session.isReprocessable()); 715 String sessionType = isHighSpeed ? "High Speed" : "Normal"; 716 assertTrue("Capture session type must be " + sessionType, 717 isHighSpeed == 718 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(session.getClass())); 719 720 return session; 721 } 722 723 /** 724 * Configure a new camera session with output surfaces. 725 * 726 * @param camera The CameraDevice to be configured. 727 * @param outputSurfaces The surface list that used for camera output. 728 * @param listener The callback CameraDevice will notify when capture results are available. 729 */ configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)730 public static CameraCaptureSession configureCameraSession(CameraDevice camera, 731 List<Surface> outputSurfaces, 732 CameraCaptureSession.StateCallback listener, Handler handler) 733 throws CameraAccessException { 734 735 return configureCameraSession(camera, outputSurfaces, /*isHighSpeed*/false, 736 listener, handler); 737 } 738 configureReprocessableCameraSession(CameraDevice camera, InputConfiguration inputConfiguration, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)739 public static CameraCaptureSession configureReprocessableCameraSession(CameraDevice camera, 740 InputConfiguration inputConfiguration, List<Surface> outputSurfaces, 741 CameraCaptureSession.StateCallback listener, Handler handler) 742 throws CameraAccessException { 743 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 744 camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces, 745 sessionListener, handler); 746 747 Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY, 748 BlockingSessionCallback.SESSION_CONFIGURE_FAILED}; 749 int state = sessionListener.getStateWaiter().waitForAnyOfStates( 750 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS); 751 752 assertTrue("Creating a reprocessable session failed.", 753 state == BlockingSessionCallback.SESSION_READY); 754 755 CameraCaptureSession session = 756 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 757 assertTrue("Camera session should be a reprocessable session", session.isReprocessable()); 758 759 return session; 760 } 761 assertArrayNotEmpty(T arr, String message)762 public static <T> void assertArrayNotEmpty(T arr, String message) { 763 assertTrue(message, arr != null && Array.getLength(arr) > 0); 764 } 765 766 /** 767 * Check if the format is a legal YUV format camera supported. 768 */ checkYuvFormat(int format)769 public static void checkYuvFormat(int format) { 770 if ((format != ImageFormat.YUV_420_888) && 771 (format != ImageFormat.NV21) && 772 (format != ImageFormat.YV12)) { 773 fail("Wrong formats: " + format); 774 } 775 } 776 777 /** 778 * Check if image size and format match given size and format. 779 */ checkImage(Image image, int width, int height, int format)780 public static void checkImage(Image image, int width, int height, int format) { 781 // Image reader will wrap YV12/NV21 image by YUV_420_888 782 if (format == ImageFormat.NV21 || format == ImageFormat.YV12) { 783 format = ImageFormat.YUV_420_888; 784 } 785 assertNotNull("Input image is invalid", image); 786 assertEquals("Format doesn't match", format, image.getFormat()); 787 assertEquals("Width doesn't match", width, image.getWidth()); 788 assertEquals("Height doesn't match", height, image.getHeight()); 789 } 790 791 /** 792 * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked 793 * 1-D linear byte array, such that it can be write into disk, or accessed by 794 * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input 795 * Image format.</p> 796 * 797 * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains 798 * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any 799 * (xstride = width, ystride = height for chroma and luma components).</p> 800 * 801 * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p> 802 */ getDataFromImage(Image image)803 public static byte[] getDataFromImage(Image image) { 804 assertNotNull("Invalid image:", image); 805 int format = image.getFormat(); 806 int width = image.getWidth(); 807 int height = image.getHeight(); 808 int rowStride, pixelStride; 809 byte[] data = null; 810 811 // Read image data 812 Plane[] planes = image.getPlanes(); 813 assertTrue("Fail to get image planes", planes != null && planes.length > 0); 814 815 // Check image validity 816 checkAndroidImageFormat(image); 817 818 ByteBuffer buffer = null; 819 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 820 // Same goes for DEPTH_POINT_CLOUD 821 if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD) { 822 buffer = planes[0].getBuffer(); 823 assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer); 824 data = new byte[buffer.remaining()]; 825 buffer.get(data); 826 buffer.rewind(); 827 return data; 828 } 829 830 int offset = 0; 831 data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; 832 int maxRowSize = planes[0].getRowStride(); 833 for (int i = 0; i < planes.length; i++) { 834 if (maxRowSize < planes[i].getRowStride()) { 835 maxRowSize = planes[i].getRowStride(); 836 } 837 } 838 byte[] rowData = new byte[maxRowSize]; 839 if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes"); 840 for (int i = 0; i < planes.length; i++) { 841 buffer = planes[i].getBuffer(); 842 assertNotNull("Fail to get bytebuffer from plane", buffer); 843 rowStride = planes[i].getRowStride(); 844 pixelStride = planes[i].getPixelStride(); 845 assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0); 846 if (VERBOSE) { 847 Log.v(TAG, "pixelStride " + pixelStride); 848 Log.v(TAG, "rowStride " + rowStride); 849 Log.v(TAG, "width " + width); 850 Log.v(TAG, "height " + height); 851 } 852 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 853 int w = (i == 0) ? width : width / 2; 854 int h = (i == 0) ? height : height / 2; 855 assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w); 856 for (int row = 0; row < h; row++) { 857 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 858 int length; 859 if (pixelStride == bytesPerPixel) { 860 // Special case: optimized read of the entire row 861 length = w * bytesPerPixel; 862 buffer.get(data, offset, length); 863 offset += length; 864 } else { 865 // Generic case: should work for any pixelStride but slower. 866 // Use intermediate buffer to avoid read byte-by-byte from 867 // DirectByteBuffer, which is very bad for performance 868 length = (w - 1) * pixelStride + bytesPerPixel; 869 buffer.get(rowData, 0, length); 870 for (int col = 0; col < w; col++) { 871 data[offset++] = rowData[col * pixelStride]; 872 } 873 } 874 // Advance buffer the remainder of the row stride 875 if (row < h - 1) { 876 buffer.position(buffer.position() + rowStride - length); 877 } 878 } 879 if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i); 880 buffer.rewind(); 881 } 882 return data; 883 } 884 885 /** 886 * <p>Check android image format validity for an image, only support below formats:</p> 887 * 888 * <p>YUV_420_888/NV21/YV12, can add more for future</p> 889 */ checkAndroidImageFormat(Image image)890 public static void checkAndroidImageFormat(Image image) { 891 int format = image.getFormat(); 892 Plane[] planes = image.getPlanes(); 893 switch (format) { 894 case ImageFormat.YUV_420_888: 895 case ImageFormat.NV21: 896 case ImageFormat.YV12: 897 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length); 898 break; 899 case ImageFormat.JPEG: 900 case ImageFormat.RAW_SENSOR: 901 case ImageFormat.DEPTH16: 902 case ImageFormat.DEPTH_POINT_CLOUD: 903 assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length); 904 break; 905 default: 906 fail("Unsupported Image Format: " + format); 907 } 908 } 909 dumpFile(String fileName, Bitmap data)910 public static void dumpFile(String fileName, Bitmap data) { 911 FileOutputStream outStream; 912 try { 913 Log.v(TAG, "output will be saved as " + fileName); 914 outStream = new FileOutputStream(fileName); 915 } catch (IOException ioe) { 916 throw new RuntimeException("Unable to create debug output file " + fileName, ioe); 917 } 918 919 try { 920 data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream); 921 outStream.close(); 922 } catch (IOException ioe) { 923 throw new RuntimeException("failed writing data to file " + fileName, ioe); 924 } 925 } 926 dumpFile(String fileName, byte[] data)927 public static void dumpFile(String fileName, byte[] data) { 928 FileOutputStream outStream; 929 try { 930 Log.v(TAG, "output will be saved as " + fileName); 931 outStream = new FileOutputStream(fileName); 932 } catch (IOException ioe) { 933 throw new RuntimeException("Unable to create debug output file " + fileName, ioe); 934 } 935 936 try { 937 outStream.write(data); 938 outStream.close(); 939 } catch (IOException ioe) { 940 throw new RuntimeException("failed writing data to file " + fileName, ioe); 941 } 942 } 943 944 /** 945 * Get the available output sizes for the user-defined {@code format}. 946 * 947 * <p>Note that implementation-defined/hidden formats are not supported.</p> 948 */ getSupportedSizeForFormat(int format, String cameraId, CameraManager cameraManager)949 public static Size[] getSupportedSizeForFormat(int format, String cameraId, 950 CameraManager cameraManager) throws CameraAccessException { 951 CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId); 952 assertNotNull("Can't get camera characteristics!", properties); 953 if (VERBOSE) { 954 Log.v(TAG, "get camera characteristics for camera: " + cameraId); 955 } 956 StreamConfigurationMap configMap = 957 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 958 Size[] availableSizes = configMap.getOutputSizes(format); 959 assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for format: " 960 + format); 961 Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(format); 962 if (highResAvailableSizes != null && highResAvailableSizes.length > 0) { 963 Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length]; 964 System.arraycopy(availableSizes, 0, allSizes, 0, 965 availableSizes.length); 966 System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length, 967 highResAvailableSizes.length); 968 availableSizes = allSizes; 969 } 970 if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes)); 971 return availableSizes; 972 } 973 974 /** 975 * Get the available output sizes for the given class. 976 * 977 */ getSupportedSizeForClass(Class klass, String cameraId, CameraManager cameraManager)978 public static Size[] getSupportedSizeForClass(Class klass, String cameraId, 979 CameraManager cameraManager) throws CameraAccessException { 980 CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId); 981 assertNotNull("Can't get camera characteristics!", properties); 982 if (VERBOSE) { 983 Log.v(TAG, "get camera characteristics for camera: " + cameraId); 984 } 985 StreamConfigurationMap configMap = 986 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 987 Size[] availableSizes = configMap.getOutputSizes(klass); 988 assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for class: " 989 + klass); 990 Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(ImageFormat.PRIVATE); 991 if (highResAvailableSizes != null && highResAvailableSizes.length > 0) { 992 Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length]; 993 System.arraycopy(availableSizes, 0, allSizes, 0, 994 availableSizes.length); 995 System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length, 996 highResAvailableSizes.length); 997 availableSizes = allSizes; 998 } 999 if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes)); 1000 return availableSizes; 1001 } 1002 1003 /** 1004 * Size comparator that compares the number of pixels it covers. 1005 * 1006 * <p>If two the areas of two sizes are same, compare the widths.</p> 1007 */ 1008 public static class SizeComparator implements Comparator<Size> { 1009 @Override compare(Size lhs, Size rhs)1010 public int compare(Size lhs, Size rhs) { 1011 return CameraUtils 1012 .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight()); 1013 } 1014 } 1015 1016 /** 1017 * Get sorted size list in descending order. Remove the sizes larger than 1018 * the bound. If the bound is null, don't do the size bound filtering. 1019 */ getSupportedPreviewSizes(String cameraId, CameraManager cameraManager, Size bound)1020 static public List<Size> getSupportedPreviewSizes(String cameraId, 1021 CameraManager cameraManager, Size bound) throws CameraAccessException { 1022 1023 Size[] rawSizes = getSupportedSizeForClass(android.view.SurfaceHolder.class, cameraId, 1024 cameraManager); 1025 assertArrayNotEmpty(rawSizes, 1026 "Available sizes for SurfaceHolder class should not be empty"); 1027 if (VERBOSE) { 1028 Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes)); 1029 } 1030 1031 if (bound == null) { 1032 return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false); 1033 } 1034 1035 List<Size> sizes = new ArrayList<Size>(); 1036 for (Size sz: rawSizes) { 1037 if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) { 1038 sizes.add(sz); 1039 } 1040 } 1041 return getAscendingOrderSizes(sizes, /*ascending*/false); 1042 } 1043 1044 /** 1045 * Get a sorted list of sizes from a given size list. 1046 * 1047 * <p> 1048 * The size is compare by area it covers, if the areas are same, then 1049 * compare the widths. 1050 * </p> 1051 * 1052 * @param sizeList The input size list to be sorted 1053 * @param ascending True if the order is ascending, otherwise descending order 1054 * @return The ordered list of sizes 1055 */ getAscendingOrderSizes(final List<Size> sizeList, boolean ascending)1056 static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) { 1057 if (sizeList == null) { 1058 throw new IllegalArgumentException("sizeList shouldn't be null"); 1059 } 1060 1061 Comparator<Size> comparator = new SizeComparator(); 1062 List<Size> sortedSizes = new ArrayList<Size>(); 1063 sortedSizes.addAll(sizeList); 1064 Collections.sort(sortedSizes, comparator); 1065 if (!ascending) { 1066 Collections.reverse(sortedSizes); 1067 } 1068 1069 return sortedSizes; 1070 } 1071 1072 /** 1073 * Get sorted (descending order) size list for given format. Remove the sizes larger than 1074 * the bound. If the bound is null, don't do the size bound filtering. 1075 */ getSortedSizesForFormat(String cameraId, CameraManager cameraManager, int format, Size bound)1076 static public List<Size> getSortedSizesForFormat(String cameraId, 1077 CameraManager cameraManager, int format, Size bound) throws CameraAccessException { 1078 Comparator<Size> comparator = new SizeComparator(); 1079 Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager); 1080 List<Size> sortedSizes = null; 1081 if (bound != null) { 1082 sortedSizes = new ArrayList<Size>(/*capacity*/1); 1083 for (Size sz : sizes) { 1084 if (comparator.compare(sz, bound) <= 0) { 1085 sortedSizes.add(sz); 1086 } 1087 } 1088 } else { 1089 sortedSizes = Arrays.asList(sizes); 1090 } 1091 assertTrue("Supported size list should have at least one element", 1092 sortedSizes.size() > 0); 1093 1094 Collections.sort(sortedSizes, comparator); 1095 // Make it in descending order. 1096 Collections.reverse(sortedSizes); 1097 return sortedSizes; 1098 } 1099 1100 /** 1101 * Get supported video size list for a given camera device. 1102 * 1103 * <p> 1104 * Filter out the sizes that are larger than the bound. If the bound is 1105 * null, don't do the size bound filtering. 1106 * </p> 1107 */ getSupportedVideoSizes(String cameraId, CameraManager cameraManager, Size bound)1108 static public List<Size> getSupportedVideoSizes(String cameraId, 1109 CameraManager cameraManager, Size bound) throws CameraAccessException { 1110 1111 Size[] rawSizes = getSupportedSizeForClass(android.media.MediaRecorder.class, 1112 cameraId, cameraManager); 1113 assertArrayNotEmpty(rawSizes, 1114 "Available sizes for MediaRecorder class should not be empty"); 1115 if (VERBOSE) { 1116 Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes)); 1117 } 1118 1119 if (bound == null) { 1120 return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false); 1121 } 1122 1123 List<Size> sizes = new ArrayList<Size>(); 1124 for (Size sz: rawSizes) { 1125 if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) { 1126 sizes.add(sz); 1127 } 1128 } 1129 return getAscendingOrderSizes(sizes, /*ascending*/false); 1130 } 1131 1132 /** 1133 * Get supported video size list (descending order) for a given camera device. 1134 * 1135 * <p> 1136 * Filter out the sizes that are larger than the bound. If the bound is 1137 * null, don't do the size bound filtering. 1138 * </p> 1139 */ getSupportedStillSizes(String cameraId, CameraManager cameraManager, Size bound)1140 static public List<Size> getSupportedStillSizes(String cameraId, 1141 CameraManager cameraManager, Size bound) throws CameraAccessException { 1142 return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound); 1143 } 1144 getMinPreviewSize(String cameraId, CameraManager cameraManager)1145 static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager) 1146 throws CameraAccessException { 1147 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null); 1148 return sizes.get(sizes.size() - 1); 1149 } 1150 1151 /** 1152 * Get max supported preview size for a camera device. 1153 */ getMaxPreviewSize(String cameraId, CameraManager cameraManager)1154 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager) 1155 throws CameraAccessException { 1156 return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null); 1157 } 1158 1159 /** 1160 * Get max preview size for a camera device in the supported sizes that are no larger 1161 * than the bound. 1162 */ getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)1163 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound) 1164 throws CameraAccessException { 1165 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound); 1166 return sizes.get(0); 1167 } 1168 1169 /** 1170 * Get max depth size for a camera device. 1171 */ getMaxDepthSize(String cameraId, CameraManager cameraManager)1172 static public Size getMaxDepthSize(String cameraId, CameraManager cameraManager) 1173 throws CameraAccessException { 1174 List<Size> sizes = getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.DEPTH16, 1175 /*bound*/ null); 1176 return sizes.get(0); 1177 } 1178 1179 /** 1180 * Get the largest size by area. 1181 * 1182 * @param sizes an array of sizes, must have at least 1 element 1183 * 1184 * @return Largest Size 1185 * 1186 * @throws IllegalArgumentException if sizes was null or had 0 elements 1187 */ getMaxSize(Size... sizes)1188 public static Size getMaxSize(Size... sizes) { 1189 if (sizes == null || sizes.length == 0) { 1190 throw new IllegalArgumentException("sizes was empty"); 1191 } 1192 1193 Size sz = sizes[0]; 1194 for (Size size : sizes) { 1195 if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) { 1196 sz = size; 1197 } 1198 } 1199 1200 return sz; 1201 } 1202 1203 /** 1204 * Returns true if the given {@code array} contains the given element. 1205 * 1206 * @param array {@code array} to check for {@code elem} 1207 * @param elem {@code elem} to test for 1208 * @return {@code true} if the given element is contained 1209 */ contains(int[] array, int elem)1210 public static boolean contains(int[] array, int elem) { 1211 if (array == null) return false; 1212 for (int i = 0; i < array.length; i++) { 1213 if (elem == array[i]) return true; 1214 } 1215 return false; 1216 } 1217 1218 /** 1219 * Get object array from byte array. 1220 * 1221 * @param array Input byte array to be converted 1222 * @return Byte object array converted from input byte array 1223 */ toObject(byte[] array)1224 public static Byte[] toObject(byte[] array) { 1225 return convertPrimitiveArrayToObjectArray(array, Byte.class); 1226 } 1227 1228 /** 1229 * Get object array from int array. 1230 * 1231 * @param array Input int array to be converted 1232 * @return Integer object array converted from input int array 1233 */ toObject(int[] array)1234 public static Integer[] toObject(int[] array) { 1235 return convertPrimitiveArrayToObjectArray(array, Integer.class); 1236 } 1237 1238 /** 1239 * Get object array from float array. 1240 * 1241 * @param array Input float array to be converted 1242 * @return Float object array converted from input float array 1243 */ toObject(float[] array)1244 public static Float[] toObject(float[] array) { 1245 return convertPrimitiveArrayToObjectArray(array, Float.class); 1246 } 1247 1248 /** 1249 * Get object array from double array. 1250 * 1251 * @param array Input double array to be converted 1252 * @return Double object array converted from input double array 1253 */ toObject(double[] array)1254 public static Double[] toObject(double[] array) { 1255 return convertPrimitiveArrayToObjectArray(array, Double.class); 1256 } 1257 1258 /** 1259 * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]). 1260 * 1261 * @param array Input array object 1262 * @param wrapperClass The boxed class it converts to 1263 * @return Boxed version of primitive array 1264 */ convertPrimitiveArrayToObjectArray(final Object array, final Class<T> wrapperClass)1265 private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array, 1266 final Class<T> wrapperClass) { 1267 // getLength does the null check and isArray check already. 1268 int arrayLength = Array.getLength(array); 1269 if (arrayLength == 0) { 1270 throw new IllegalArgumentException("Input array shouldn't be empty"); 1271 } 1272 1273 @SuppressWarnings("unchecked") 1274 final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength); 1275 for (int i = 0; i < arrayLength; i++) { 1276 Array.set(result, i, Array.get(array, i)); 1277 } 1278 return result; 1279 } 1280 1281 /** 1282 * Validate image based on format and size. 1283 * 1284 * @param image The image to be validated. 1285 * @param width The image width. 1286 * @param height The image height. 1287 * @param format The image format. 1288 * @param filePath The debug dump file path, null if don't want to dump to 1289 * file. 1290 * @throws UnsupportedOperationException if calling with an unknown format 1291 */ validateImage(Image image, int width, int height, int format, String filePath)1292 public static void validateImage(Image image, int width, int height, int format, 1293 String filePath) { 1294 checkImage(image, width, height, format); 1295 1296 /** 1297 * TODO: validate timestamp: 1298 * 1. capture result timestamp against the image timestamp (need 1299 * consider frame drops) 1300 * 2. timestamps should be monotonically increasing for different requests 1301 */ 1302 if(VERBOSE) Log.v(TAG, "validating Image"); 1303 byte[] data = getDataFromImage(image); 1304 assertTrue("Invalid image data", data != null && data.length > 0); 1305 1306 switch (format) { 1307 case ImageFormat.JPEG: 1308 validateJpegData(data, width, height, filePath); 1309 break; 1310 case ImageFormat.YUV_420_888: 1311 case ImageFormat.YV12: 1312 validateYuvData(data, width, height, format, image.getTimestamp(), filePath); 1313 break; 1314 case ImageFormat.RAW_SENSOR: 1315 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath); 1316 break; 1317 case ImageFormat.DEPTH16: 1318 validateDepth16Data(data, width, height, format, image.getTimestamp(), filePath); 1319 break; 1320 case ImageFormat.DEPTH_POINT_CLOUD: 1321 validateDepthPointCloudData(data, width, height, format, image.getTimestamp(), filePath); 1322 break; 1323 default: 1324 throw new UnsupportedOperationException("Unsupported format for validation: " 1325 + format); 1326 } 1327 } 1328 1329 /** 1330 * Provide a mock for {@link CameraDevice.StateCallback}. 1331 * 1332 * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an 1333 * abstract class.</p> 1334 * 1335 * <p> 1336 * Use this instead of other classes when needing to verify interactions, since 1337 * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra 1338 * interactions which will cause false test failures. 1339 * </p> 1340 * 1341 */ 1342 public static class MockStateCallback extends CameraDevice.StateCallback { 1343 1344 @Override onOpened(CameraDevice camera)1345 public void onOpened(CameraDevice camera) { 1346 } 1347 1348 @Override onDisconnected(CameraDevice camera)1349 public void onDisconnected(CameraDevice camera) { 1350 } 1351 1352 @Override onError(CameraDevice camera, int error)1353 public void onError(CameraDevice camera, int error) { 1354 } 1355 MockStateCallback()1356 private MockStateCallback() {} 1357 1358 /** 1359 * Create a Mockito-ready mocked StateCallback. 1360 */ mock()1361 public static MockStateCallback mock() { 1362 return Mockito.spy(new MockStateCallback()); 1363 } 1364 } 1365 validateJpegData(byte[] jpegData, int width, int height, String filePath)1366 private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) { 1367 BitmapFactory.Options bmpOptions = new BitmapFactory.Options(); 1368 // DecodeBound mode: only parse the frame header to get width/height. 1369 // it doesn't decode the pixel. 1370 bmpOptions.inJustDecodeBounds = true; 1371 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions); 1372 assertEquals(width, bmpOptions.outWidth); 1373 assertEquals(height, bmpOptions.outHeight); 1374 1375 // Pixel decoding mode: decode whole image. check if the image data 1376 // is decodable here. 1377 assertNotNull("Decoding jpeg failed", 1378 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length)); 1379 if (DEBUG && filePath != null) { 1380 String fileName = 1381 filePath + "/" + width + "x" + height + ".jpeg"; 1382 dumpFile(fileName, jpegData); 1383 } 1384 } 1385 validateYuvData(byte[] yuvData, int width, int height, int format, long ts, String filePath)1386 private static void validateYuvData(byte[] yuvData, int width, int height, int format, 1387 long ts, String filePath) { 1388 checkYuvFormat(format); 1389 if (VERBOSE) Log.v(TAG, "Validating YUV data"); 1390 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1391 assertEquals("Yuv data doesn't match", expectedSize, yuvData.length); 1392 1393 // TODO: Can add data validation for test pattern. 1394 1395 if (DEBUG && filePath != null) { 1396 String fileName = 1397 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv"; 1398 dumpFile(fileName, yuvData); 1399 } 1400 } 1401 validateRaw16Data(byte[] rawData, int width, int height, int format, long ts, String filePath)1402 private static void validateRaw16Data(byte[] rawData, int width, int height, int format, 1403 long ts, String filePath) { 1404 if (VERBOSE) Log.v(TAG, "Validating raw data"); 1405 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1406 assertEquals("Raw data doesn't match", expectedSize, rawData.length); 1407 1408 // TODO: Can add data validation for test pattern. 1409 1410 if (DEBUG && filePath != null) { 1411 String fileName = 1412 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16"; 1413 dumpFile(fileName, rawData); 1414 } 1415 1416 return; 1417 } 1418 validateDepth16Data(byte[] depthData, int width, int height, int format, long ts, String filePath)1419 private static void validateDepth16Data(byte[] depthData, int width, int height, int format, 1420 long ts, String filePath) { 1421 1422 if (VERBOSE) Log.v(TAG, "Validating depth16 data"); 1423 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1424 assertEquals("Depth data doesn't match", expectedSize, depthData.length); 1425 1426 1427 if (DEBUG && filePath != null) { 1428 String fileName = 1429 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth16"; 1430 dumpFile(fileName, depthData); 1431 } 1432 1433 return; 1434 1435 } 1436 validateDepthPointCloudData(byte[] depthData, int width, int height, int format, long ts, String filePath)1437 private static void validateDepthPointCloudData(byte[] depthData, int width, int height, int format, 1438 long ts, String filePath) { 1439 1440 if (VERBOSE) Log.v(TAG, "Validating depth point cloud data"); 1441 1442 // Can't validate size since it is variable 1443 1444 if (DEBUG && filePath != null) { 1445 String fileName = 1446 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth_point_cloud"; 1447 dumpFile(fileName, depthData); 1448 } 1449 1450 return; 1451 1452 } 1453 getValueNotNull(CaptureResult result, CaptureResult.Key<T> key)1454 public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) { 1455 if (result == null) { 1456 throw new IllegalArgumentException("Result must not be null"); 1457 } 1458 1459 T value = result.get(key); 1460 assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value); 1461 return value; 1462 } 1463 getValueNotNull(CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key)1464 public static <T> T getValueNotNull(CameraCharacteristics characteristics, 1465 CameraCharacteristics.Key<T> key) { 1466 if (characteristics == null) { 1467 throw new IllegalArgumentException("Camera characteristics must not be null"); 1468 } 1469 1470 T value = characteristics.get(key); 1471 assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value); 1472 return value; 1473 } 1474 1475 /** 1476 * Get a crop region for a given zoom factor and center position. 1477 * <p> 1478 * The center position is normalized position in range of [0, 1.0], where 1479 * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right 1480 * corner. The center position could limit the effective minimal zoom 1481 * factor, for example, if the center position is (0.75, 0.75), the 1482 * effective minimal zoom position becomes 2.0. If the requested zoom factor 1483 * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned. 1484 * </p> 1485 * <p> 1486 * The aspect ratio of the crop region is maintained the same as the aspect 1487 * ratio of active array. 1488 * </p> 1489 * 1490 * @param zoomFactor The zoom factor to generate the crop region, it must be 1491 * >= 1.0 1492 * @param center The normalized zoom center point that is in the range of [0, 1]. 1493 * @param maxZoom The max zoom factor supported by this device. 1494 * @param activeArray The active array size of this device. 1495 * @return crop region for the given normalized center and zoom factor. 1496 */ getCropRegionForZoom(float zoomFactor, final PointF center, final float maxZoom, final Rect activeArray)1497 public static Rect getCropRegionForZoom(float zoomFactor, final PointF center, 1498 final float maxZoom, final Rect activeArray) { 1499 if (zoomFactor < 1.0) { 1500 throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0"); 1501 } 1502 if (center.x > 1.0 || center.x < 0) { 1503 throw new IllegalArgumentException("center.x " + center.x 1504 + " should be in range of [0, 1.0]"); 1505 } 1506 if (center.y > 1.0 || center.y < 0) { 1507 throw new IllegalArgumentException("center.y " + center.y 1508 + " should be in range of [0, 1.0]"); 1509 } 1510 if (maxZoom < 1.0) { 1511 throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0"); 1512 } 1513 if (activeArray == null) { 1514 throw new IllegalArgumentException("activeArray must not be null"); 1515 } 1516 1517 float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x), 1518 Math.min(center.y, 1.0f - center.y)); 1519 float minEffectiveZoom = 0.5f / minCenterLength; 1520 if (minEffectiveZoom > maxZoom) { 1521 throw new IllegalArgumentException("Requested center " + center.toString() + 1522 " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max" 1523 + " zoom factor " + maxZoom); 1524 } 1525 1526 if (zoomFactor < minEffectiveZoom) { 1527 Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor " 1528 + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom); 1529 zoomFactor = minEffectiveZoom; 1530 } 1531 1532 int cropCenterX = (int)(activeArray.width() * center.x); 1533 int cropCenterY = (int)(activeArray.height() * center.y); 1534 int cropWidth = (int) (activeArray.width() / zoomFactor); 1535 int cropHeight = (int) (activeArray.height() / zoomFactor); 1536 1537 return new Rect( 1538 /*left*/cropCenterX - cropWidth / 2, 1539 /*top*/cropCenterY - cropHeight / 2, 1540 /*right*/ cropCenterX + cropWidth / 2 - 1, 1541 /*bottom*/cropCenterY + cropHeight / 2 - 1); 1542 } 1543 1544 /** 1545 * Calculate output 3A region from the intersection of input 3A region and cropped region. 1546 * 1547 * @param requestRegions The input 3A regions 1548 * @param cropRect The cropped region 1549 * @return expected 3A regions output in capture result 1550 */ getExpectedOutputRegion( MeteringRectangle[] requestRegions, Rect cropRect)1551 public static MeteringRectangle[] getExpectedOutputRegion( 1552 MeteringRectangle[] requestRegions, Rect cropRect){ 1553 MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length]; 1554 for (int i = 0; i < requestRegions.length; i++) { 1555 Rect requestRect = requestRegions[i].getRect(); 1556 Rect resultRect = new Rect(); 1557 assertTrue("Input 3A region must intersect cropped region", 1558 resultRect.setIntersect(requestRect, cropRect)); 1559 resultRegions[i] = new MeteringRectangle( 1560 resultRect, 1561 requestRegions[i].getMeteringWeight()); 1562 } 1563 return resultRegions; 1564 } 1565 1566 /** 1567 * Copy source image data to destination image. 1568 * 1569 * @param src The source image to be copied from. 1570 * @param dst The destination image to be copied to. 1571 * @throws IllegalArgumentException If the source and destination images have 1572 * different format, or one of the images is not copyable. 1573 */ imageCopy(Image src, Image dst)1574 public static void imageCopy(Image src, Image dst) { 1575 if (src == null || dst == null) { 1576 throw new IllegalArgumentException("Images should be non-null"); 1577 } 1578 if (src.getFormat() != dst.getFormat()) { 1579 throw new IllegalArgumentException("Src and dst images should have the same format"); 1580 } 1581 if (src.getFormat() == ImageFormat.PRIVATE || 1582 dst.getFormat() == ImageFormat.PRIVATE) { 1583 throw new IllegalArgumentException("PRIVATE format images are not copyable"); 1584 } 1585 1586 // TODO: check the owner of the dst image, it must be from ImageWriter, other source may 1587 // not be writable. Maybe we should add an isWritable() method in image class. 1588 1589 Plane[] srcPlanes = src.getPlanes(); 1590 Plane[] dstPlanes = dst.getPlanes(); 1591 ByteBuffer srcBuffer = null; 1592 ByteBuffer dstBuffer = null; 1593 for (int i = 0; i < srcPlanes.length; i++) { 1594 srcBuffer = srcPlanes[i].getBuffer(); 1595 int srcPos = srcBuffer.position(); 1596 srcBuffer.rewind(); 1597 dstBuffer = dstPlanes[i].getBuffer(); 1598 dstBuffer.rewind(); 1599 dstBuffer.put(srcBuffer); 1600 srcBuffer.position(srcPos); 1601 dstBuffer.rewind(); 1602 } 1603 } 1604 1605 /** 1606 * <p> 1607 * Checks whether the two images are strongly equal. 1608 * </p> 1609 * <p> 1610 * Two images are strongly equal if and only if the data, formats, sizes, 1611 * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format 1612 * images, the image data is not not accessible thus the data comparison is 1613 * effectively skipped as the number of planes is zero. 1614 * </p> 1615 * <p> 1616 * Note that this method compares the pixel data even outside of the crop 1617 * region, which may not be necessary for general use case. 1618 * </p> 1619 * 1620 * @param lhsImg First image to be compared with. 1621 * @param rhsImg Second image to be compared with. 1622 * @return true if the two images are equal, false otherwise. 1623 * @throws IllegalArgumentException If either of image is null. 1624 */ isImageStronglyEqual(Image lhsImg, Image rhsImg)1625 public static boolean isImageStronglyEqual(Image lhsImg, Image rhsImg) { 1626 if (lhsImg == null || rhsImg == null) { 1627 throw new IllegalArgumentException("Images should be non-null"); 1628 } 1629 1630 if (lhsImg.getFormat() != rhsImg.getFormat()) { 1631 Log.i(TAG, "lhsImg format " + lhsImg.getFormat() + " is different with rhsImg format " 1632 + rhsImg.getFormat()); 1633 return false; 1634 } 1635 1636 if (lhsImg.getWidth() != rhsImg.getWidth()) { 1637 Log.i(TAG, "lhsImg width " + lhsImg.getWidth() + " is different with rhsImg width " 1638 + rhsImg.getWidth()); 1639 return false; 1640 } 1641 1642 if (lhsImg.getHeight() != rhsImg.getHeight()) { 1643 Log.i(TAG, "lhsImg height " + lhsImg.getHeight() + " is different with rhsImg height " 1644 + rhsImg.getHeight()); 1645 return false; 1646 } 1647 1648 if (lhsImg.getTimestamp() != rhsImg.getTimestamp()) { 1649 Log.i(TAG, "lhsImg timestamp " + lhsImg.getTimestamp() 1650 + " is different with rhsImg timestamp " + rhsImg.getTimestamp()); 1651 return false; 1652 } 1653 1654 if (!lhsImg.getCropRect().equals(rhsImg.getCropRect())) { 1655 Log.i(TAG, "lhsImg crop rect " + lhsImg.getCropRect() 1656 + " is different with rhsImg crop rect " + rhsImg.getCropRect()); 1657 return false; 1658 } 1659 1660 // Compare data inside of the image. 1661 Plane[] lhsPlanes = lhsImg.getPlanes(); 1662 Plane[] rhsPlanes = rhsImg.getPlanes(); 1663 ByteBuffer lhsBuffer = null; 1664 ByteBuffer rhsBuffer = null; 1665 for (int i = 0; i < lhsPlanes.length; i++) { 1666 lhsBuffer = lhsPlanes[i].getBuffer(); 1667 rhsBuffer = rhsPlanes[i].getBuffer(); 1668 if (!lhsBuffer.equals(rhsBuffer)) { 1669 Log.i(TAG, "byte buffers for plane " + i + " don't matach."); 1670 return false; 1671 } 1672 } 1673 1674 return true; 1675 } 1676 1677 /** 1678 * Set jpeg related keys in a capture request builder. 1679 * 1680 * @param builder The capture request builder to set the keys inl 1681 * @param exifData The exif data to set. 1682 * @param thumbnailSize The thumbnail size to set. 1683 * @param collector The camera error collector to collect errors. 1684 */ setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, Size thumbnailSize, CameraErrorCollector collector)1685 public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, 1686 Size thumbnailSize, CameraErrorCollector collector) { 1687 builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize); 1688 builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation); 1689 builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation); 1690 builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality); 1691 builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY, 1692 exifData.thumbnailQuality); 1693 1694 // Validate request set and get. 1695 collector.expectEquals("JPEG thumbnail size request set and get should match", 1696 thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE)); 1697 collector.expectTrue("GPS locations request set and get should match.", 1698 areGpsFieldsEqual(exifData.gpsLocation, 1699 builder.get(CaptureRequest.JPEG_GPS_LOCATION))); 1700 collector.expectEquals("JPEG orientation request set and get should match", 1701 exifData.jpegOrientation, 1702 builder.get(CaptureRequest.JPEG_ORIENTATION)); 1703 collector.expectEquals("JPEG quality request set and get should match", 1704 exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY)); 1705 collector.expectEquals("JPEG thumbnail quality request set and get should match", 1706 exifData.thumbnailQuality, 1707 builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY)); 1708 } 1709 1710 /** 1711 * Simple validation of JPEG image size and format. 1712 * <p> 1713 * Only validate the image object sanity. It is fast, but doesn't actually 1714 * check the buffer data. Assert is used here as it make no sense to 1715 * continue the test if the jpeg image captured has some serious failures. 1716 * </p> 1717 * 1718 * @param image The captured jpeg image 1719 * @param expectedSize Expected capture jpeg size 1720 */ basicValidateJpegImage(Image image, Size expectedSize)1721 public static void basicValidateJpegImage(Image image, Size expectedSize) { 1722 Size imageSz = new Size(image.getWidth(), image.getHeight()); 1723 assertTrue( 1724 String.format("Image size doesn't match (expected %s, actual %s) ", 1725 expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz)); 1726 assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat()); 1727 assertNotNull("Image plane shouldn't be null", image.getPlanes()); 1728 assertEquals("Image plane number should be 1", 1, image.getPlanes().length); 1729 1730 // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here. 1731 } 1732 1733 /** 1734 * Verify the JPEG EXIF and JPEG related keys in a capture result are expected. 1735 * - Capture request get values are same as were set. 1736 * - capture result's exif data is the same as was set by 1737 * the capture request. 1738 * - new tags in the result set by the camera service are 1739 * present and semantically correct. 1740 * 1741 * @param image The output JPEG image to verify. 1742 * @param captureResult The capture result to verify. 1743 * @param expectedSize The expected JPEG size. 1744 * @param expectedThumbnailSize The expected thumbnail size. 1745 * @param expectedExifData The expected EXIF data 1746 * @param staticInfo The static metadata for the camera device. 1747 * @param jpegFilename The filename to dump the jpeg to. 1748 * @param collector The camera error collector to collect errors. 1749 */ verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, CameraErrorCollector collector)1750 public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, 1751 Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, 1752 CameraErrorCollector collector) throws Exception { 1753 1754 basicValidateJpegImage(image, expectedSize); 1755 1756 byte[] jpegBuffer = getDataFromImage(image); 1757 // Have to dump into a file to be able to use ExifInterface 1758 String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg"; 1759 dumpFile(jpegFilename, jpegBuffer); 1760 ExifInterface exif = new ExifInterface(jpegFilename); 1761 1762 if (expectedThumbnailSize.equals(new Size(0,0))) { 1763 collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)", 1764 !exif.hasThumbnail()); 1765 } else { 1766 collector.expectTrue("Jpeg must have thumbnail for thumbnail size " + 1767 expectedThumbnailSize, exif.hasThumbnail()); 1768 } 1769 1770 // Validate capture result vs. request 1771 Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE); 1772 int orientationTested = expectedExifData.jpegOrientation; 1773 // Legacy shim always doesn't rotate thumbnail size 1774 if ((orientationTested == 90 || orientationTested == 270) && 1775 staticInfo.isHardwareLevelLimitedOrBetter()) { 1776 int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1777 /*defaultValue*/-1); 1778 if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) { 1779 // Device physically rotated image+thumbnail data 1780 // Expect thumbnail size to be also rotated 1781 resultThumbnailSize = new Size(resultThumbnailSize.getHeight(), 1782 resultThumbnailSize.getWidth()); 1783 } 1784 } 1785 1786 collector.expectEquals("JPEG thumbnail size result and request should match", 1787 expectedThumbnailSize, resultThumbnailSize); 1788 if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) != 1789 null) { 1790 collector.expectTrue("GPS location result and request should match.", 1791 areGpsFieldsEqual(expectedExifData.gpsLocation, 1792 captureResult.get(CaptureResult.JPEG_GPS_LOCATION))); 1793 } 1794 collector.expectEquals("JPEG orientation result and request should match", 1795 expectedExifData.jpegOrientation, 1796 captureResult.get(CaptureResult.JPEG_ORIENTATION)); 1797 collector.expectEquals("JPEG quality result and request should match", 1798 expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY)); 1799 collector.expectEquals("JPEG thumbnail quality result and request should match", 1800 expectedExifData.thumbnailQuality, 1801 captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY)); 1802 1803 // Validate other exif tags for all non-legacy devices 1804 if (!staticInfo.isHardwareLevelLegacy()) { 1805 verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector); 1806 } 1807 } 1808 1809 /** 1810 * Get the degree of an EXIF orientation. 1811 */ getExifOrientationInDegree(int exifOrientation, CameraErrorCollector collector)1812 private static int getExifOrientationInDegree(int exifOrientation, 1813 CameraErrorCollector collector) { 1814 switch (exifOrientation) { 1815 case ExifInterface.ORIENTATION_NORMAL: 1816 return 0; 1817 case ExifInterface.ORIENTATION_ROTATE_90: 1818 return 90; 1819 case ExifInterface.ORIENTATION_ROTATE_180: 1820 return 180; 1821 case ExifInterface.ORIENTATION_ROTATE_270: 1822 return 270; 1823 default: 1824 collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" + 1825 "info based on the request orientation range"); 1826 return 0; 1827 } 1828 } 1829 1830 /** 1831 * Validate and return the focal length. 1832 * 1833 * @param result Capture result to get the focal length 1834 * @return Focal length from capture result or -1 if focal length is not available. 1835 */ validateFocalLength(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)1836 private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo, 1837 CameraErrorCollector collector) { 1838 float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked(); 1839 Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH); 1840 if (collector.expectTrue("Focal length is invalid", 1841 resultFocalLength != null && resultFocalLength > 0)) { 1842 List<Float> focalLengthList = 1843 Arrays.asList(CameraTestUtils.toObject(focalLengths)); 1844 collector.expectTrue("Focal length should be one of the available focal length", 1845 focalLengthList.contains(resultFocalLength)); 1846 return resultFocalLength; 1847 } 1848 return -1; 1849 } 1850 1851 /** 1852 * Validate and return the aperture. 1853 * 1854 * @param result Capture result to get the aperture 1855 * @return Aperture from capture result or -1 if aperture is not available. 1856 */ validateAperture(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)1857 private static float validateAperture(CaptureResult result, StaticMetadata staticInfo, 1858 CameraErrorCollector collector) { 1859 float[] apertures = staticInfo.getAvailableAperturesChecked(); 1860 Float resultAperture = result.get(CaptureResult.LENS_APERTURE); 1861 if (collector.expectTrue("Capture result aperture is invalid", 1862 resultAperture != null && resultAperture > 0)) { 1863 List<Float> apertureList = 1864 Arrays.asList(CameraTestUtils.toObject(apertures)); 1865 collector.expectTrue("Aperture should be one of the available apertures", 1866 apertureList.contains(resultAperture)); 1867 return resultAperture; 1868 } 1869 return -1; 1870 } 1871 1872 /** 1873 * Return the closest value in an array of floats. 1874 */ getClosestValueInArray(float[] values, float target)1875 private static float getClosestValueInArray(float[] values, float target) { 1876 int minIdx = 0; 1877 float minDistance = Math.abs(values[0] - target); 1878 for(int i = 0; i < values.length; i++) { 1879 float distance = Math.abs(values[i] - target); 1880 if (minDistance > distance) { 1881 minDistance = distance; 1882 minIdx = i; 1883 } 1884 } 1885 1886 return values[minIdx]; 1887 } 1888 1889 /** 1890 * Return if two Location's GPS field are the same. 1891 */ areGpsFieldsEqual(Location a, Location b)1892 private static boolean areGpsFieldsEqual(Location a, Location b) { 1893 if (a == null || b == null) { 1894 return false; 1895 } 1896 1897 return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() && 1898 a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() && 1899 a.getProvider() == b.getProvider(); 1900 } 1901 1902 /** 1903 * Verify extra tags in JPEG EXIF 1904 */ verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)1905 private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, 1906 CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector) 1907 throws ParseException { 1908 /** 1909 * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION. 1910 * Orientation and exif width/height need to be tested carefully, two cases: 1911 * 1912 * 1. Device rotate the image buffer physically, then exif width/height may not match 1913 * the requested still capture size, we need swap them to check. 1914 * 1915 * 2. Device use the exif tag to record the image orientation, it doesn't rotate 1916 * the jpeg image buffer itself. In this case, the exif width/height should always match 1917 * the requested still capture size, and the exif orientation should always match the 1918 * requested orientation. 1919 * 1920 */ 1921 int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0); 1922 int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0); 1923 Size exifSize = new Size(exifWidth, exifHeight); 1924 // Orientation could be missing, which is ok, default to 0. 1925 int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1926 /*defaultValue*/-1); 1927 // Get requested orientation from result, because they should be same. 1928 if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) { 1929 int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION); 1930 final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED; 1931 final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270; 1932 boolean orientationValid = collector.expectTrue(String.format( 1933 "Exif orientation must be in range of [%d, %d]", 1934 ORIENTATION_MIN, ORIENTATION_MAX), 1935 exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX); 1936 if (orientationValid) { 1937 /** 1938 * Device captured image doesn't respect the requested orientation, 1939 * which means it rotates the image buffer physically. Then we 1940 * should swap the exif width/height accordingly to compare. 1941 */ 1942 boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED; 1943 1944 if (deviceRotatedImage) { 1945 // Case 1. 1946 boolean needSwap = (requestedOrientation % 180 == 90); 1947 if (needSwap) { 1948 exifSize = new Size(exifHeight, exifWidth); 1949 } 1950 } else { 1951 // Case 2. 1952 collector.expectEquals("Exif orientaiton should match requested orientation", 1953 requestedOrientation, getExifOrientationInDegree(exifOrientation, 1954 collector)); 1955 } 1956 } 1957 } 1958 1959 /** 1960 * Ideally, need check exifSize == jpegSize == actual buffer size. But 1961 * jpegSize == jpeg decode bounds size(from jpeg jpeg frame 1962 * header, not exif) was validated in ImageReaderTest, no need to 1963 * validate again here. 1964 */ 1965 collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize); 1966 1967 // TAG_DATETIME, it should be local time 1968 long currentTimeInMs = System.currentTimeMillis(); 1969 long currentTimeInSecond = currentTimeInMs / 1000; 1970 Date date = new Date(currentTimeInMs); 1971 String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date); 1972 String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME); 1973 if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) { 1974 collector.expectTrue("Exif TAG_DATETIME is wrong", 1975 dateTime.length() == EXIF_DATETIME_LENGTH); 1976 long exifTimeInSecond = 1977 new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000; 1978 long delta = currentTimeInSecond - exifTimeInSecond; 1979 collector.expectTrue("Capture time deviates too much from the current time", 1980 Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC); 1981 // It should be local time. 1982 collector.expectTrue("Exif date time should be local time", 1983 dateTime.startsWith(localDatetime)); 1984 } 1985 1986 // TAG_FOCAL_LENGTH. 1987 float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked(); 1988 float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1); 1989 collector.expectEquals("Focal length should match", 1990 getClosestValueInArray(focalLengths, exifFocalLength), 1991 exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN); 1992 // More checks for focal length. 1993 collector.expectEquals("Exif focal length should match capture result", 1994 validateFocalLength(result, staticInfo, collector), exifFocalLength); 1995 1996 // TAG_EXPOSURE_TIME 1997 // ExifInterface API gives exposure time value in the form of float instead of rational 1998 String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); 1999 collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime); 2000 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) { 2001 if (exposureTime != null) { 2002 double exposureTimeValue = Double.parseDouble(exposureTime); 2003 long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); 2004 double expected = expTimeResult / 1e9; 2005 double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO; 2006 tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC); 2007 collector.expectEquals("Exif exposure time doesn't match", expected, 2008 exposureTimeValue, tolerance); 2009 } 2010 } 2011 2012 // TAG_APERTURE 2013 // ExifInterface API gives aperture value in the form of float instead of rational 2014 String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE); 2015 collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture); 2016 if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) { 2017 float[] apertures = staticInfo.getAvailableAperturesChecked(); 2018 if (exifAperture != null) { 2019 float apertureValue = Float.parseFloat(exifAperture); 2020 collector.expectEquals("Aperture value should match", 2021 getClosestValueInArray(apertures, apertureValue), 2022 apertureValue, EXIF_APERTURE_ERROR_MARGIN); 2023 // More checks for aperture. 2024 collector.expectEquals("Exif aperture length should match capture result", 2025 validateAperture(result, staticInfo, collector), apertureValue); 2026 } 2027 } 2028 2029 /** 2030 * TAG_FLASH. TODO: For full devices, can check a lot more info 2031 * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash) 2032 */ 2033 String flash = exif.getAttribute(ExifInterface.TAG_FLASH); 2034 collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash); 2035 2036 /** 2037 * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we 2038 * should be able to cross-check android.sensor.referenceIlluminant. 2039 */ 2040 String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE); 2041 collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance); 2042 2043 // TAG_MAKE 2044 String make = exif.getAttribute(ExifInterface.TAG_MAKE); 2045 collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make); 2046 2047 // TAG_MODEL 2048 String model = exif.getAttribute(ExifInterface.TAG_MODEL); 2049 collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model); 2050 2051 2052 // TAG_ISO 2053 int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1); 2054 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) { 2055 int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY); 2056 collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso); 2057 } 2058 2059 // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras). 2060 String digitizedTime = exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED); 2061 collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime); 2062 if (digitizedTime != null) { 2063 String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME); 2064 collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime); 2065 if (expectedDateTime != null) { 2066 collector.expectEquals("dataTime should match digitizedTime", 2067 expectedDateTime, digitizedTime); 2068 } 2069 } 2070 2071 /** 2072 * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at 2073 * most 9 digits in ExifInterface implementation, use getAttributeInt to 2074 * sanitize it. When the default value -1 is returned, it means that 2075 * this exif tag either doesn't exist or is a non-numerical invalid 2076 * string. Same rule applies to the rest of sub second tags. 2077 */ 2078 int subSecTime = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME, /*defaultValue*/-1); 2079 collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0); 2080 2081 // TAG_SUBSEC_TIME_ORIG 2082 int subSecTimeOrig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_ORIG, 2083 /*defaultValue*/-1); 2084 collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!", 2085 subSecTimeOrig > 0); 2086 2087 // TAG_SUBSEC_TIME_DIG 2088 int subSecTimeDig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_DIG, 2089 /*defaultValue*/-1); 2090 collector.expectTrue( 2091 "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0); 2092 } 2093 2094 2095 /** 2096 * Immutable class wrapping the exif test data. 2097 */ 2098 public static class ExifTestData { 2099 public final Location gpsLocation; 2100 public final int jpegOrientation; 2101 public final byte jpegQuality; 2102 public final byte thumbnailQuality; 2103 ExifTestData(Location location, int orientation, byte jpgQuality, byte thumbQuality)2104 public ExifTestData(Location location, int orientation, 2105 byte jpgQuality, byte thumbQuality) { 2106 gpsLocation = location; 2107 jpegOrientation = orientation; 2108 jpegQuality = jpgQuality; 2109 thumbnailQuality = thumbQuality; 2110 } 2111 } 2112 getPreviewSizeBound(WindowManager windowManager, Size bound)2113 public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) { 2114 Display display = windowManager.getDefaultDisplay(); 2115 2116 int width = display.getWidth(); 2117 int height = display.getHeight(); 2118 2119 if (height > width) { 2120 height = width; 2121 width = display.getHeight(); 2122 } 2123 2124 if (bound.getWidth() <= width && 2125 bound.getHeight() <= height) 2126 return bound; 2127 else 2128 return new Size(width, height); 2129 } 2130 } 2131