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