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 */ 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 */ 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 */ 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 */ 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 */ 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 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 253 public synchronized int getImageCount() { 254 return mImagesDropped; 255 } 256 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 271 public ImageVerifierListener(Size sz, int format) { 272 mSize = sz; 273 mFormat = format; 274 } 275 276 @Override 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 */ 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 */ 331 public SimpleImageReaderListener(boolean asyncMode, int maxImages) { 332 mAsyncMode = asyncMode; 333 mMaxImages = maxImages; 334 } 335 336 @Override 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 */ 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 */ 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 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 394 public SimpleImageWriterListener(ImageWriter writer) { 395 if (writer == null) { 396 throw new IllegalArgumentException("writer cannot be null"); 397 } 398 mWriter = writer; 399 } 400 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 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 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 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 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 467 public long getTotalNumFrames() { 468 return mNumFramesArrived.get(); 469 } 470 471 public CaptureResult getCaptureResult(long timeout) { 472 return getTotalCaptureResult(timeout); 473 } 474 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 689 public boolean hasMoreResults() 690 { 691 return !mQueue.isEmpty(); 692 } 693 694 public boolean hasMoreFailures() 695 { 696 return !mFailureQueue.isEmpty(); 697 } 698 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 1605 public HandlerExecutor(Handler handler) { 1606 assertNotNull("handler must be valid", handler); 1607 mHandler = handler; 1608 } 1609 1610 @Override 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 1632 public void onOpened(CameraDevice camera) { 1633 } 1634 1635 @Override 1636 public void onDisconnected(CameraDevice camera) { 1637 } 1638 1639 @Override 1640 public void onError(CameraDevice camera, int error) { 1641 } 1642 1643 private MockStateCallback() {} 1644 1645 /** 1646 * Create a Mockito-ready mocked StateCallback. 1647 */ 1648 public static MockStateCallback mock() { 1649 return Mockito.spy(new MockStateCallback()); 1650 } 1651 } 1652 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 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 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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