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 17package com.android.mediaframeworktest.helpers; 18 19import com.android.ex.camera2.blocking.BlockingCameraManager; 20import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 21import com.android.ex.camera2.blocking.BlockingSessionCallback; 22import com.android.ex.camera2.blocking.BlockingStateCallback; 23import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 24 25import junit.framework.Assert; 26 27import org.mockito.Mockito; 28 29import android.graphics.Bitmap; 30import android.graphics.BitmapFactory; 31import android.graphics.ImageFormat; 32import android.graphics.PointF; 33import android.graphics.Rect; 34import android.hardware.camera2.CameraAccessException; 35import android.hardware.camera2.CameraCaptureSession; 36import android.hardware.camera2.CameraCharacteristics; 37import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; 38import android.hardware.camera2.CameraDevice; 39import android.hardware.camera2.CameraManager; 40import android.hardware.camera2.CaptureFailure; 41import android.hardware.camera2.CaptureRequest; 42import android.hardware.camera2.CaptureResult; 43import android.hardware.camera2.TotalCaptureResult; 44import android.hardware.camera2.params.InputConfiguration; 45import android.hardware.camera2.params.MeteringRectangle; 46import android.hardware.camera2.params.StreamConfigurationMap; 47import android.location.Location; 48import android.location.LocationManager; 49import android.media.ExifInterface; 50import android.media.Image; 51import android.media.Image.Plane; 52import android.media.ImageReader; 53import android.media.ImageWriter; 54import android.os.Build; 55import android.os.Environment; 56import android.os.Handler; 57import android.util.Log; 58import android.util.Pair; 59import android.util.Size; 60import android.view.Display; 61import android.view.Surface; 62import android.view.WindowManager; 63 64import java.io.FileOutputStream; 65import java.io.IOException; 66import java.lang.reflect.Array; 67import java.nio.ByteBuffer; 68import java.text.ParseException; 69import java.text.SimpleDateFormat; 70import java.util.ArrayList; 71import java.util.Arrays; 72import java.util.Collections; 73import java.util.Comparator; 74import java.util.Date; 75import java.util.HashMap; 76import java.util.List; 77import java.util.concurrent.LinkedBlockingQueue; 78import java.util.concurrent.Semaphore; 79import java.util.concurrent.TimeUnit; 80import 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 */ 90public 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 */ 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 */ 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 */ 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 */ 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 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 250 public ImageVerifierListener(Size sz, int format) { 251 mSize = sz; 252 mFormat = format; 253 } 254 255 @Override 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 */ 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 */ 306 public SimpleImageReaderListener(boolean asyncMode, int maxImages) { 307 mAsyncMode = asyncMode; 308 mMaxImages = maxImages; 309 } 310 311 @Override 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 */ 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 */ 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 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 365 public SimpleImageWriterListener(ImageWriter writer) { 366 if (writer == null) { 367 throw new IllegalArgumentException("writer cannot be null"); 368 } 369 mWriter = writer; 370 } 371 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 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 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 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 425 public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, 426 long frameNumber) { 427 } 428 429 public long getTotalNumFrames() { 430 return mNumFramesArrived.get(); 431 } 432 433 public CaptureResult getCaptureResult(long timeout) { 434 return getTotalCaptureResult(timeout); 435 } 436 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 627 public boolean hasMoreResults() 628 { 629 return mQueue.isEmpty(); 630 } 631 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 1353 public void onOpened(CameraDevice camera) { 1354 } 1355 1356 @Override 1357 public void onDisconnected(CameraDevice camera) { 1358 } 1359 1360 @Override 1361 public void onError(CameraDevice camera, int error) { 1362 } 1363 1364 private MockStateCallback() {} 1365 1366 /** 1367 * Create a Mockito-ready mocked StateCallback. 1368 */ 1369 public static MockStateCallback mock() { 1370 return Mockito.spy(new MockStateCallback()); 1371 } 1372 } 1373 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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