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