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