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