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