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