1 /* 2 * Copyright (C) 2020 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 package android.hardware.camera2; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.graphics.ImageFormat; 26 import android.hardware.camera2.extension.IAdvancedExtenderImpl; 27 import android.hardware.camera2.extension.ICameraExtensionsProxyService; 28 import android.hardware.camera2.extension.IImageCaptureExtenderImpl; 29 import android.hardware.camera2.extension.IInitializeSessionCallback; 30 import android.hardware.camera2.extension.IPreviewExtenderImpl; 31 import android.hardware.camera2.extension.LatencyRange; 32 import android.hardware.camera2.extension.SizeList; 33 import android.hardware.camera2.impl.CameraExtensionUtils; 34 import android.hardware.camera2.impl.CameraMetadataNative; 35 import android.hardware.camera2.params.ExtensionSessionConfiguration; 36 import android.hardware.camera2.params.StreamConfigurationMap; 37 import android.os.Binder; 38 import android.os.ConditionVariable; 39 import android.os.IBinder; 40 import android.os.RemoteException; 41 import android.os.SystemProperties; 42 import android.util.Log; 43 import android.util.Pair; 44 import android.util.Range; 45 import android.util.Size; 46 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collections; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Objects; 56 import java.util.Set; 57 import java.util.concurrent.Future; 58 import java.util.concurrent.TimeUnit; 59 import java.util.concurrent.TimeoutException; 60 61 /** 62 * <p>Allows clients to query availability and supported resolutions of camera extensions.</p> 63 * 64 * <p>Camera extensions give camera clients access to device-specific algorithms and sequences that 65 * can improve the overall image quality of snapshots in various cases such as low light, selfies, 66 * portraits, and scenes that can benefit from enhanced dynamic range. Often such sophisticated 67 * processing sequences will rely on multiple camera frames as input and will produce a single 68 * output.</p> 69 * 70 * <p>Camera extensions are not guaranteed to be present on all devices so camera clients must 71 * query for their availability via {@link CameraExtensionCharacteristics#getSupportedExtensions()}. 72 * </p> 73 * 74 * <p>In order to use any available camera extension, camera clients must create a corresponding 75 * {@link CameraExtensionSession} via 76 * {@link CameraDevice#createExtensionSession(ExtensionSessionConfiguration)}</p> 77 * 78 * <p>Camera clients must be aware that device-specific camera extensions may support only a 79 * subset of the available camera resolutions and must first query 80 * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)} for supported 81 * single high-quality request output sizes and 82 * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported 83 * repeating request output sizes.</p> 84 * 85 * <p>The extension characteristics for a given device are expected to remain static under 86 * normal operating conditions.</p> 87 * 88 * @see CameraManager#getCameraExtensionCharacteristics(String) 89 */ 90 public final class CameraExtensionCharacteristics { 91 private static final String TAG = "CameraExtensionCharacteristics"; 92 93 /** 94 * Device-specific extension implementation for automatic selection of particular extension 95 * such as HDR or NIGHT depending on the current lighting and environment conditions. 96 */ 97 public static final int EXTENSION_AUTOMATIC = 0; 98 99 /** 100 * Device-specific extension implementation which tends to smooth the skin and apply other 101 * cosmetic effects to people's faces. 102 */ 103 public static final int EXTENSION_FACE_RETOUCH = 1; 104 105 /** 106 * Device-specific extension implementation which tends to smooth the skin and apply other 107 * cosmetic effects to people's faces. 108 * 109 * @deprecated Use {@link #EXTENSION_FACE_RETOUCH} instead. 110 */ 111 public @Deprecated static final int EXTENSION_BEAUTY = EXTENSION_FACE_RETOUCH; 112 113 /** 114 * Device-specific extension implementation which can blur certain regions of the final image 115 * thereby "enhancing" focus for all remaining non-blurred parts. 116 */ 117 public static final int EXTENSION_BOKEH = 2; 118 119 /** 120 * Device-specific extension implementation for enhancing the dynamic range of the 121 * final image. 122 */ 123 public static final int EXTENSION_HDR = 3; 124 125 /** 126 * Device-specific extension implementation that aims to suppress noise and improve the 127 * overall image quality under low light conditions. 128 */ 129 public static final int EXTENSION_NIGHT = 4; 130 131 /** 132 * @hide 133 */ 134 @Retention(RetentionPolicy.SOURCE) 135 @IntDef(flag = true, value = {EXTENSION_AUTOMATIC, 136 EXTENSION_FACE_RETOUCH, 137 EXTENSION_BOKEH, 138 EXTENSION_HDR, 139 EXTENSION_NIGHT}) 140 public @interface Extension { 141 } 142 143 /** 144 * Default camera output in case additional processing from CameraX extensions is not needed 145 * 146 * @hide 147 */ 148 public static final int NON_PROCESSING_INPUT_FORMAT = ImageFormat.PRIVATE; 149 150 /** 151 * CameraX extensions require YUV_420_888 as default input for processing at the moment 152 * 153 * @hide 154 */ 155 public static final int PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888; 156 157 private static final @Extension 158 int[] EXTENSION_LIST = new int[]{ 159 EXTENSION_AUTOMATIC, 160 EXTENSION_FACE_RETOUCH, 161 EXTENSION_BOKEH, 162 EXTENSION_HDR, 163 EXTENSION_NIGHT}; 164 165 private final Context mContext; 166 private final String mCameraId; 167 private final Map<String, CameraCharacteristics> mCharacteristicsMap; 168 private final Map<String, CameraMetadataNative> mCharacteristicsMapNative; 169 170 /** 171 * @hide 172 */ CameraExtensionCharacteristics(Context context, String cameraId, Map<String, CameraCharacteristics> characteristicsMap)173 public CameraExtensionCharacteristics(Context context, String cameraId, 174 Map<String, CameraCharacteristics> characteristicsMap) { 175 mContext = context; 176 mCameraId = cameraId; 177 mCharacteristicsMap = characteristicsMap; 178 mCharacteristicsMapNative = 179 CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap); 180 } 181 getSupportedSizes(List<SizeList> sizesList, Integer format)182 private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList, 183 Integer format) { 184 ArrayList<Size> ret = new ArrayList<>(); 185 if ((sizesList != null) && (!sizesList.isEmpty())) { 186 for (SizeList entry : sizesList) { 187 if ((entry.format == format) && !entry.sizes.isEmpty()) { 188 for (android.hardware.camera2.extension.Size sz : entry.sizes) { 189 ret.add(new Size(sz.width, sz.height)); 190 } 191 return ret; 192 } 193 } 194 } 195 196 return ret; 197 } 198 generateSupportedSizes(List<SizeList> sizesList, Integer format, StreamConfigurationMap streamMap)199 private static List<Size> generateSupportedSizes(List<SizeList> sizesList, 200 Integer format, 201 StreamConfigurationMap streamMap) { 202 // Per API contract it is assumed that the extension is able to support all 203 // camera advertised sizes for a given format in case it doesn't return 204 // a valid non-empty size list. 205 ArrayList<Size> ret = getSupportedSizes(sizesList, format); 206 Size[] supportedSizes = streamMap.getOutputSizes(format); 207 if ((ret.isEmpty()) && (supportedSizes != null)) { 208 ret.addAll(Arrays.asList(supportedSizes)); 209 } 210 return ret; 211 } 212 generateJpegSupportedSizes(List<SizeList> sizesList, StreamConfigurationMap streamMap)213 private static List<Size> generateJpegSupportedSizes(List<SizeList> sizesList, 214 StreamConfigurationMap streamMap) { 215 ArrayList<Size> extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888); 216 HashSet<Size> supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList( 217 streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes); 218 HashSet<Size> supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes( 219 ImageFormat.JPEG))); 220 supportedSizes.retainAll(supportedJpegSizes); 221 222 return new ArrayList<>(supportedSizes); 223 } 224 225 /** 226 * A per-process global camera extension manager instance, to track and 227 * initialize/release extensions depending on client activity. 228 */ 229 private static final class CameraExtensionManagerGlobal { 230 private static final String TAG = "CameraExtensionManagerGlobal"; 231 private static final String PROXY_PACKAGE_NAME = "com.android.cameraextensions"; 232 private static final String PROXY_SERVICE_NAME = 233 "com.android.cameraextensions.CameraExtensionsProxyService"; 234 235 // Singleton instance 236 private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER = 237 new CameraExtensionManagerGlobal(); 238 private final Object mLock = new Object(); 239 private final int PROXY_SERVICE_DELAY_MS = 2000; 240 private InitializerFuture mInitFuture = null; 241 private ServiceConnection mConnection = null; 242 private int mConnectionCount = 0; 243 private ICameraExtensionsProxyService mProxy = null; 244 private boolean mSupportsAdvancedExtensions = false; 245 246 // Singleton, don't allow construction CameraExtensionManagerGlobal()247 private CameraExtensionManagerGlobal() {} 248 get()249 public static CameraExtensionManagerGlobal get() { 250 return GLOBAL_CAMERA_MANAGER; 251 } 252 releaseProxyConnectionLocked(Context ctx)253 private void releaseProxyConnectionLocked(Context ctx) { 254 if (mConnection != null ) { 255 ctx.unbindService(mConnection); 256 mConnection = null; 257 mProxy = null; 258 mConnectionCount = 0; 259 } 260 } 261 connectToProxyLocked(Context ctx)262 private void connectToProxyLocked(Context ctx) { 263 if (mConnection == null) { 264 Intent intent = new Intent(); 265 intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME); 266 String vendorProxyPackage = SystemProperties.get( 267 "ro.vendor.camera.extensions.package"); 268 String vendorProxyService = SystemProperties.get( 269 "ro.vendor.camera.extensions.service"); 270 if (!vendorProxyPackage.isEmpty() && !vendorProxyService.isEmpty()) { 271 Log.v(TAG, 272 "Choosing the vendor camera extensions proxy package: " 273 + vendorProxyPackage); 274 Log.v(TAG, 275 "Choosing the vendor camera extensions proxy service: " 276 + vendorProxyService); 277 intent.setClassName(vendorProxyPackage, vendorProxyService); 278 } 279 mInitFuture = new InitializerFuture(); 280 mConnection = new ServiceConnection() { 281 @Override 282 public void onServiceDisconnected(ComponentName component) { 283 mConnection = null; 284 mProxy = null; 285 } 286 287 @Override 288 public void onServiceConnected(ComponentName component, IBinder binder) { 289 mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder); 290 if (mProxy == null) { 291 throw new IllegalStateException("Camera Proxy service is null"); 292 } 293 try { 294 mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported(); 295 } catch (RemoteException e) { 296 Log.e(TAG, "Remote IPC failed!"); 297 } 298 mInitFuture.setStatus(true); 299 } 300 }; 301 ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | 302 Context.BIND_ABOVE_CLIENT | Context.BIND_NOT_VISIBLE, 303 android.os.AsyncTask.THREAD_POOL_EXECUTOR, mConnection); 304 305 try { 306 mInitFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS); 307 } catch (TimeoutException e) { 308 Log.e(TAG, "Timed out while initializing proxy service!"); 309 } 310 } 311 } 312 313 private static class InitializerFuture implements Future<Boolean> { 314 private volatile Boolean mStatus; 315 ConditionVariable mCondVar = new ConditionVariable(/*opened*/false); 316 setStatus(boolean status)317 public void setStatus(boolean status) { 318 mStatus = status; 319 mCondVar.open(); 320 } 321 322 @Override cancel(boolean mayInterruptIfRunning)323 public boolean cancel(boolean mayInterruptIfRunning) { 324 return false; // don't allow canceling this task 325 } 326 327 @Override isCancelled()328 public boolean isCancelled() { 329 return false; // can never cancel this task 330 } 331 332 @Override isDone()333 public boolean isDone() { 334 return mStatus != null; 335 } 336 337 @Override get()338 public Boolean get() { 339 mCondVar.block(); 340 return mStatus; 341 } 342 343 @Override get(long timeout, TimeUnit unit)344 public Boolean get(long timeout, TimeUnit unit) throws TimeoutException { 345 long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS); 346 if (!mCondVar.block(timeoutMs)) { 347 throw new TimeoutException( 348 "Failed to receive status after " + timeout + " " + unit); 349 } 350 351 if (mStatus == null) { 352 throw new AssertionError(); 353 } 354 return mStatus; 355 } 356 } 357 registerClient(Context ctx, IBinder token)358 public boolean registerClient(Context ctx, IBinder token) { 359 synchronized (mLock) { 360 boolean ret = false; 361 connectToProxyLocked(ctx); 362 if (mProxy == null) { 363 return false; 364 } 365 mConnectionCount++; 366 367 try { 368 ret = mProxy.registerClient(token); 369 } catch (RemoteException e) { 370 Log.e(TAG, "Failed to initialize extension! Extension service does " 371 + " not respond!"); 372 } 373 if (!ret) { 374 mConnectionCount--; 375 } 376 377 if (mConnectionCount <= 0) { 378 releaseProxyConnectionLocked(ctx); 379 } 380 381 return ret; 382 } 383 } 384 unregisterClient(Context ctx, IBinder token)385 public void unregisterClient(Context ctx, IBinder token) { 386 synchronized (mLock) { 387 if (mProxy != null) { 388 try { 389 mProxy.unregisterClient(token); 390 } catch (RemoteException e) { 391 Log.e(TAG, "Failed to de-initialize extension! Extension service does" 392 + " not respond!"); 393 } finally { 394 mConnectionCount--; 395 if (mConnectionCount <= 0) { 396 releaseProxyConnectionLocked(ctx); 397 } 398 } 399 } 400 } 401 } 402 initializeSession(IInitializeSessionCallback cb)403 public void initializeSession(IInitializeSessionCallback cb) throws RemoteException { 404 synchronized (mLock) { 405 if (mProxy != null) { 406 mProxy.initializeSession(cb); 407 } 408 } 409 } 410 releaseSession()411 public void releaseSession() { 412 synchronized (mLock) { 413 if (mProxy != null) { 414 try { 415 mProxy.releaseSession(); 416 } catch (RemoteException e) { 417 Log.e(TAG, "Failed to release session! Extension service does" 418 + " not respond!"); 419 } 420 } 421 } 422 } 423 areAdvancedExtensionsSupported()424 public boolean areAdvancedExtensionsSupported() { 425 return mSupportsAdvancedExtensions; 426 } 427 initializePreviewExtension(int extensionType)428 public IPreviewExtenderImpl initializePreviewExtension(int extensionType) 429 throws RemoteException { 430 synchronized (mLock) { 431 if (mProxy != null) { 432 return mProxy.initializePreviewExtension(extensionType); 433 } else { 434 return null; 435 } 436 } 437 } 438 initializeImageExtension(int extensionType)439 public IImageCaptureExtenderImpl initializeImageExtension(int extensionType) 440 throws RemoteException { 441 synchronized (mLock) { 442 if (mProxy != null) { 443 return mProxy.initializeImageExtension(extensionType); 444 } else { 445 return null; 446 } 447 } 448 } 449 initializeAdvancedExtension(int extensionType)450 public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType) 451 throws RemoteException { 452 synchronized (mLock) { 453 if (mProxy != null) { 454 return mProxy.initializeAdvancedExtension(extensionType); 455 } else { 456 return null; 457 } 458 } 459 } 460 } 461 462 /** 463 * @hide 464 */ registerClient(Context ctx, IBinder token)465 public static boolean registerClient(Context ctx, IBinder token) { 466 return CameraExtensionManagerGlobal.get().registerClient(ctx, token); 467 } 468 469 /** 470 * @hide 471 */ unregisterClient(Context ctx, IBinder token)472 public static void unregisterClient(Context ctx, IBinder token) { 473 CameraExtensionManagerGlobal.get().unregisterClient(ctx, token); 474 } 475 476 /** 477 * @hide 478 */ initializeSession(IInitializeSessionCallback cb)479 public static void initializeSession(IInitializeSessionCallback cb) throws RemoteException { 480 CameraExtensionManagerGlobal.get().initializeSession(cb); 481 } 482 483 /** 484 * @hide 485 */ releaseSession()486 public static void releaseSession() { 487 CameraExtensionManagerGlobal.get().releaseSession(); 488 } 489 490 /** 491 * @hide 492 */ areAdvancedExtensionsSupported()493 public static boolean areAdvancedExtensionsSupported() { 494 return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(); 495 } 496 497 /** 498 * @hide 499 */ isExtensionSupported(String cameraId, int extensionType, Map<String, CameraMetadataNative> characteristicsMap)500 public static boolean isExtensionSupported(String cameraId, int extensionType, 501 Map<String, CameraMetadataNative> characteristicsMap) { 502 if (areAdvancedExtensionsSupported()) { 503 try { 504 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType); 505 return extender.isExtensionAvailable(cameraId, characteristicsMap); 506 } catch (RemoteException e) { 507 Log.e(TAG, "Failed to query extension availability! Extension service does not" 508 + " respond!"); 509 return false; 510 } 511 } else { 512 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders; 513 try { 514 extenders = initializeExtension(extensionType); 515 } catch (IllegalArgumentException e) { 516 return false; 517 } 518 519 try { 520 return extenders.first.isExtensionAvailable(cameraId, 521 characteristicsMap.get(cameraId)) 522 && extenders.second.isExtensionAvailable(cameraId, 523 characteristicsMap.get(cameraId)); 524 } catch (RemoteException e) { 525 Log.e(TAG, "Failed to query extension availability! Extension service does not" 526 + " respond!"); 527 return false; 528 } 529 } 530 } 531 532 /** 533 * @hide 534 */ initializeAdvancedExtension(@xtension int extensionType)535 public static IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType) { 536 IAdvancedExtenderImpl extender; 537 try { 538 extender = CameraExtensionManagerGlobal.get().initializeAdvancedExtension( 539 extensionType); 540 } catch (RemoteException e) { 541 throw new IllegalStateException("Failed to initialize extension: " + extensionType); 542 } 543 544 if (extender == null) { 545 throw new IllegalArgumentException("Unknown extension: " + extensionType); 546 } 547 548 return extender; 549 } 550 551 /** 552 * @hide 553 */ initializeExtension( @xtension int extensionType)554 public static Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> initializeExtension( 555 @Extension int extensionType) { 556 IPreviewExtenderImpl previewExtender; 557 IImageCaptureExtenderImpl imageExtender; 558 try { 559 previewExtender = 560 CameraExtensionManagerGlobal.get().initializePreviewExtension(extensionType); 561 imageExtender = 562 CameraExtensionManagerGlobal.get().initializeImageExtension(extensionType); 563 } catch (RemoteException e) { 564 throw new IllegalStateException("Failed to initialize extension: " + extensionType); 565 } 566 if ((imageExtender == null) || (previewExtender == null)) { 567 throw new IllegalArgumentException("Unknown extension: " + extensionType); 568 } 569 570 return new Pair<>(previewExtender, imageExtender); 571 } 572 isOutputSupportedFor(Class<T> klass)573 private static <T> boolean isOutputSupportedFor(Class<T> klass) { 574 Objects.requireNonNull(klass, "klass must not be null"); 575 576 if ((klass == android.graphics.SurfaceTexture.class) || 577 (klass == android.view.SurfaceView.class)) { 578 return true; 579 } 580 581 return false; 582 } 583 584 /** 585 * Return a list of supported device-specific extensions for a given camera device. 586 * 587 * @return non-modifiable list of available extensions 588 */ getSupportedExtensions()589 public @NonNull List<Integer> getSupportedExtensions() { 590 ArrayList<Integer> ret = new ArrayList<>(); 591 final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId); 592 boolean success = registerClient(mContext, token); 593 if (!success) { 594 return Collections.unmodifiableList(ret); 595 } 596 597 try { 598 for (int extensionType : EXTENSION_LIST) { 599 if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) { 600 ret.add(extensionType); 601 } 602 } 603 } finally { 604 unregisterClient(mContext, token); 605 } 606 607 return Collections.unmodifiableList(ret); 608 } 609 610 /** 611 * Checks for postview support of still capture. 612 * 613 * <p>A postview is a preview version of the still capture that is available before the final 614 * image. For example, it can be used as a temporary placeholder for the requested capture 615 * while the final image is being processed. The supported sizes for a still capture's postview 616 * can be retrieved using 617 * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}. 618 * The formats of the still capture and postview should be equivalent upon capture request.</p> 619 * 620 * @param extension the extension type 621 * @return {@code true} in case postview is supported, {@code false} otherwise 622 * 623 * @throws IllegalArgumentException in case the extension type is not a 624 * supported device-specific extension 625 */ isPostviewAvailable(@xtension int extension)626 public boolean isPostviewAvailable(@Extension int extension) { 627 final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId); 628 boolean success = registerClient(mContext, token); 629 if (!success) { 630 throw new IllegalArgumentException("Unsupported extensions"); 631 } 632 633 try { 634 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 635 throw new IllegalArgumentException("Unsupported extension"); 636 } 637 638 if (areAdvancedExtensionsSupported()) { 639 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 640 extender.init(mCameraId, mCharacteristicsMapNative); 641 return extender.isPostviewAvailable(); 642 } else { 643 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 644 initializeExtension(extension); 645 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 646 return extenders.second.isPostviewAvailable(); 647 } 648 } catch (RemoteException e) { 649 Log.e(TAG, "Failed to query the extension for postview availability! Extension " 650 + "service does not respond!"); 651 } finally { 652 unregisterClient(mContext, token); 653 } 654 655 return false; 656 } 657 658 /** 659 * Get a list of the postview sizes supported for a still capture, using its 660 * capture size {@code captureSize}, to use as an output for the postview request. 661 * 662 * <p>Available postview sizes will always be either equal to or less than the still 663 * capture size. When choosing the most applicable postview size for a usecase, it should 664 * be noted that lower resolution postviews will generally be available more quickly 665 * than larger resolution postviews. For example, when choosing a size for an optimized 666 * postview that will be displayed as a placeholder while the final image is processed, 667 * the resolution closest to the preview size may be most suitable.</p> 668 * 669 * <p>Note that device-specific extensions are allowed to support only a subset 670 * of the camera resolutions advertised by 671 * {@link StreamConfigurationMap#getOutputSizes}.</p> 672 * 673 * @param extension the extension type 674 * @param captureSize size of the still capture for which the postview is requested 675 * @param format device-specific extension output format of the still capture and 676 * postview 677 * @return non-modifiable list of available sizes or an empty list if the format and 678 * size is not supported. 679 * @throws IllegalArgumentException in case of unsupported extension or if postview 680 * feature is not supported by extension. 681 */ 682 @NonNull getPostviewSupportedSizes(@xtension int extension, @NonNull Size captureSize, int format)683 public List<Size> getPostviewSupportedSizes(@Extension int extension, 684 @NonNull Size captureSize, int format) { 685 final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId); 686 boolean success = registerClient(mContext, token); 687 if (!success) { 688 throw new IllegalArgumentException("Unsupported extensions"); 689 } 690 691 try { 692 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 693 throw new IllegalArgumentException("Unsupported extension"); 694 } 695 696 android.hardware.camera2.extension.Size sz = 697 new android.hardware.camera2.extension.Size(); 698 sz.width = captureSize.getWidth(); 699 sz.height = captureSize.getHeight(); 700 701 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( 702 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 703 704 if (areAdvancedExtensionsSupported()) { 705 switch(format) { 706 case ImageFormat.YUV_420_888: 707 case ImageFormat.JPEG: 708 break; 709 default: 710 throw new IllegalArgumentException("Unsupported format: " + format); 711 } 712 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 713 extender.init(mCameraId, mCharacteristicsMapNative); 714 return generateSupportedSizes(extender.getSupportedPostviewResolutions( 715 sz), format, streamMap); 716 } else { 717 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 718 initializeExtension(extension); 719 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 720 if ((extenders.second.getCaptureProcessor() == null) || 721 !isPostviewAvailable(extension)) { 722 // Extensions that don't implement any capture processor 723 // and have processing occur in the HAL don't currently support the 724 // postview feature 725 throw new IllegalArgumentException("Extension does not support " 726 + "postview feature"); 727 } 728 729 if (format == ImageFormat.YUV_420_888) { 730 return generateSupportedSizes( 731 extenders.second.getSupportedPostviewResolutions(sz), 732 format, streamMap); 733 } else if (format == ImageFormat.JPEG) { 734 // The framework will perform the additional encoding pass on the 735 // processed YUV_420 buffers. 736 return generateJpegSupportedSizes( 737 extenders.second.getSupportedPostviewResolutions(sz), 738 streamMap); 739 } else { 740 throw new IllegalArgumentException("Unsupported format: " + format); 741 } 742 } 743 } catch (RemoteException e) { 744 Log.e(TAG, "Failed to query the extension postview supported sizes! Extension " 745 + "service does not respond!"); 746 return Collections.emptyList(); 747 } finally { 748 unregisterClient(mContext, token); 749 } 750 } 751 752 /** 753 * Get a list of sizes compatible with {@code klass} to use as an output for the 754 * repeating request 755 * {@link CameraExtensionSession#setRepeatingRequest}. 756 * 757 * <p>Note that device-specific extensions are allowed to support only a subset 758 * of the camera output surfaces and resolutions. 759 * The {@link android.graphics.SurfaceTexture} class is guaranteed at least one size for 760 * backward compatible cameras whereas other output classes are not guaranteed to be supported. 761 * </p> 762 * 763 * <p>Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} 764 * {@link android.view.SurfaceView} classes are also guaranteed to be supported and include 765 * the same resolutions as {@link android.graphics.SurfaceTexture}. 766 * Clients must set the desired SurfaceView resolution by calling 767 * {@link android.view.SurfaceHolder#setFixedSize}.</p> 768 * 769 * @param extension the extension type 770 * @param klass a non-{@code null} {@link Class} object reference 771 * @return non-modifiable list of available sizes or an empty list if the Surface output is not 772 * supported 773 * @throws NullPointerException if {@code klass} was {@code null} 774 * @throws IllegalArgumentException in case of unsupported extension. 775 */ 776 @NonNull getExtensionSupportedSizes(@xtension int extension, @NonNull Class<T> klass)777 public <T> List<Size> getExtensionSupportedSizes(@Extension int extension, 778 @NonNull Class<T> klass) { 779 if (!isOutputSupportedFor(klass)) { 780 return new ArrayList<>(); 781 } 782 // TODO: Revisit this code once the Extension preview processor output format 783 // ambiguity is resolved in b/169799538. 784 785 final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); 786 boolean success = registerClient(mContext, token); 787 if (!success) { 788 throw new IllegalArgumentException("Unsupported extensions"); 789 } 790 791 try { 792 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 793 throw new IllegalArgumentException("Unsupported extension"); 794 } 795 796 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( 797 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 798 if (areAdvancedExtensionsSupported()) { 799 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 800 extender.init(mCameraId, mCharacteristicsMapNative); 801 return generateSupportedSizes( 802 extender.getSupportedPreviewOutputResolutions(mCameraId), 803 ImageFormat.PRIVATE, streamMap); 804 } else { 805 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 806 initializeExtension(extension); 807 extenders.first.init(mCameraId, 808 mCharacteristicsMapNative.get(mCameraId)); 809 return generateSupportedSizes(extenders.first.getSupportedResolutions(), 810 ImageFormat.PRIVATE, streamMap); 811 } 812 } catch (RemoteException e) { 813 Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" 814 + " not respond!"); 815 return new ArrayList<>(); 816 } finally { 817 unregisterClient(mContext, token); 818 } 819 } 820 821 /** 822 * Check whether a given extension is available and return the 823 * supported output surface resolutions that can be used for high-quality capture 824 * requests via {@link CameraExtensionSession#capture}. 825 * 826 * <p>Note that device-specific extensions are allowed to support only a subset 827 * of the camera resolutions advertised by 828 * {@link StreamConfigurationMap#getOutputSizes}.</p> 829 * 830 * <p>Device-specific extensions currently support at most two 831 * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all 832 * extensions and ImageFormat.YUV_420_888 may or may not be supported.</p> 833 * 834 * @param extension the extension type 835 * @param format device-specific extension output format 836 * @return non-modifiable list of available sizes or an empty list if the format is not 837 * supported. 838 * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG / 839 * ImageFormat.YUV_420_888; or unsupported extension. 840 */ 841 public @NonNull getExtensionSupportedSizes(@xtension int extension, int format)842 List<Size> getExtensionSupportedSizes(@Extension int extension, int format) { 843 try { 844 final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); 845 boolean success = registerClient(mContext, token); 846 if (!success) { 847 throw new IllegalArgumentException("Unsupported extensions"); 848 } 849 850 try { 851 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 852 throw new IllegalArgumentException("Unsupported extension"); 853 } 854 855 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( 856 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 857 if (areAdvancedExtensionsSupported()) { 858 switch(format) { 859 case ImageFormat.YUV_420_888: 860 case ImageFormat.JPEG: 861 break; 862 default: 863 throw new IllegalArgumentException("Unsupported format: " + format); 864 } 865 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 866 extender.init(mCameraId, mCharacteristicsMapNative); 867 return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions( 868 mCameraId), format, streamMap); 869 } else { 870 if (format == ImageFormat.YUV_420_888) { 871 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 872 initializeExtension(extension); 873 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 874 if (extenders.second.getCaptureProcessor() == null) { 875 // Extensions that don't implement any capture processor are limited to 876 // JPEG only! 877 return new ArrayList<>(); 878 } 879 return generateSupportedSizes(extenders.second.getSupportedResolutions(), 880 format, streamMap); 881 } else if (format == ImageFormat.JPEG) { 882 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 883 initializeExtension(extension); 884 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 885 if (extenders.second.getCaptureProcessor() != null) { 886 // The framework will perform the additional encoding pass on the 887 // processed YUV_420 buffers. 888 return generateJpegSupportedSizes( 889 extenders.second.getSupportedResolutions(), streamMap); 890 } else { 891 return generateSupportedSizes(null, format, streamMap); 892 } 893 } else { 894 throw new IllegalArgumentException("Unsupported format: " + format); 895 } 896 } 897 } finally { 898 unregisterClient(mContext, token); 899 } 900 } catch (RemoteException e) { 901 Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" 902 + " not respond!"); 903 return new ArrayList<>(); 904 } 905 } 906 907 /** 908 * Returns the estimated capture latency range in milliseconds for the 909 * target capture resolution during the calls to {@link CameraExtensionSession#capture}. This 910 * includes the time spent processing the multi-frame capture request along with any additional 911 * time for encoding of the processed buffer if necessary. 912 * 913 * @param extension the extension type 914 * @param captureOutputSize size of the capture output surface. If it is not in the supported 915 * output sizes, maximum capture output size is used for the estimation 916 * @param format device-specific extension output format 917 * @return the range of estimated minimal and maximal capture latency in milliseconds 918 * or null if no capture latency info can be provided 919 * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} / 920 * {@link ImageFormat#YUV_420_888}; or unsupported extension. 921 */ getEstimatedCaptureLatencyRangeMillis(@xtension int extension, @NonNull Size captureOutputSize, @ImageFormat.Format int format)922 public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension, 923 @NonNull Size captureOutputSize, @ImageFormat.Format int format) { 924 switch (format) { 925 case ImageFormat.YUV_420_888: 926 case ImageFormat.JPEG: 927 //No op 928 break; 929 default: 930 throw new IllegalArgumentException("Unsupported format: " + format); 931 } 932 933 final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId); 934 boolean success = registerClient(mContext, token); 935 if (!success) { 936 throw new IllegalArgumentException("Unsupported extensions"); 937 } 938 939 try { 940 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 941 throw new IllegalArgumentException("Unsupported extension"); 942 } 943 944 android.hardware.camera2.extension.Size sz = 945 new android.hardware.camera2.extension.Size(); 946 sz.width = captureOutputSize.getWidth(); 947 sz.height = captureOutputSize.getHeight(); 948 if (areAdvancedExtensionsSupported()) { 949 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 950 extender.init(mCameraId, mCharacteristicsMapNative); 951 LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId, 952 sz, format); 953 if (latencyRange != null) { 954 return new Range(latencyRange.min, latencyRange.max); 955 } 956 } else { 957 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 958 initializeExtension(extension); 959 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 960 if ((format == ImageFormat.YUV_420_888) && 961 (extenders.second.getCaptureProcessor() == null) ){ 962 // Extensions that don't implement any capture processor are limited to 963 // JPEG only! 964 return null; 965 } 966 if ((format == ImageFormat.JPEG) && 967 (extenders.second.getCaptureProcessor() != null)) { 968 // The framework will perform the additional encoding pass on the 969 // processed YUV_420 buffers. Latency in this case is very device 970 // specific and cannot be estimated accurately enough. 971 return null; 972 } 973 974 LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz); 975 if (latencyRange != null) { 976 return new Range(latencyRange.min, latencyRange.max); 977 } 978 } 979 } catch (RemoteException e) { 980 Log.e(TAG, "Failed to query the extension capture latency! Extension service does" 981 + " not respond!"); 982 } finally { 983 unregisterClient(mContext, token); 984 } 985 986 return null; 987 } 988 989 /** 990 * Retrieve support for capture progress callbacks via 991 * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}. 992 * 993 * @param extension the extension type 994 * @return {@code true} in case progress callbacks are supported, {@code false} otherwise 995 * 996 * @throws IllegalArgumentException in case of an unsupported extension. 997 */ isCaptureProcessProgressAvailable(@xtension int extension)998 public boolean isCaptureProcessProgressAvailable(@Extension int extension) { 999 final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId); 1000 boolean success = registerClient(mContext, token); 1001 if (!success) { 1002 throw new IllegalArgumentException("Unsupported extensions"); 1003 } 1004 1005 try { 1006 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 1007 throw new IllegalArgumentException("Unsupported extension"); 1008 } 1009 1010 if (areAdvancedExtensionsSupported()) { 1011 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 1012 extender.init(mCameraId, mCharacteristicsMapNative); 1013 return extender.isCaptureProcessProgressAvailable(); 1014 } else { 1015 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 1016 initializeExtension(extension); 1017 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 1018 return extenders.second.isCaptureProcessProgressAvailable(); 1019 } 1020 } catch (RemoteException e) { 1021 Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does" 1022 + " not respond!"); 1023 } finally { 1024 unregisterClient(mContext, token); 1025 } 1026 1027 return false; 1028 } 1029 1030 /** 1031 * Returns the set of keys supported by a {@link CaptureRequest} submitted in a 1032 * {@link CameraExtensionSession} with a given extension type. 1033 * 1034 * <p>The set returned is not modifiable, so any attempts to modify it will throw 1035 * a {@code UnsupportedOperationException}.</p> 1036 * 1037 * @param extension the extension type 1038 * 1039 * @return non-modifiable set of capture keys supported by camera extension session initialized 1040 * with the given extension type. 1041 * @throws IllegalArgumentException in case of unsupported extension. 1042 */ 1043 @NonNull getAvailableCaptureRequestKeys(@xtension int extension)1044 public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) { 1045 final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId); 1046 boolean success = registerClient(mContext, token); 1047 if (!success) { 1048 throw new IllegalArgumentException("Unsupported extensions"); 1049 } 1050 1051 HashSet<CaptureRequest.Key> ret = new HashSet<>(); 1052 1053 try { 1054 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 1055 throw new IllegalArgumentException("Unsupported extension"); 1056 } 1057 1058 CameraMetadataNative captureRequestMeta = null; 1059 if (areAdvancedExtensionsSupported()) { 1060 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 1061 extender.init(mCameraId, mCharacteristicsMapNative); 1062 captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId); 1063 } else { 1064 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 1065 initializeExtension(extension); 1066 extenders.second.onInit(token, mCameraId, 1067 mCharacteristicsMapNative.get(mCameraId)); 1068 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 1069 captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys(); 1070 extenders.second.onDeInit(token); 1071 } 1072 1073 if (captureRequestMeta != null) { 1074 int[] requestKeys = captureRequestMeta.get( 1075 CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS); 1076 if (requestKeys == null) { 1077 throw new AssertionError( 1078 "android.request.availableRequestKeys must be non-null" 1079 + " in the characteristics"); 1080 } 1081 CameraCharacteristics requestChars = new CameraCharacteristics( 1082 captureRequestMeta); 1083 1084 Object crKey = CaptureRequest.Key.class; 1085 Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>) crKey; 1086 1087 ret.addAll(requestChars.getAvailableKeyList(CaptureRequest.class, crKeyTyped, 1088 requestKeys, /*includeSynthetic*/ true)); 1089 } 1090 1091 // Jpeg quality and orientation must always be supported 1092 if (!ret.contains(CaptureRequest.JPEG_QUALITY)) { 1093 ret.add(CaptureRequest.JPEG_QUALITY); 1094 } 1095 if (!ret.contains(CaptureRequest.JPEG_ORIENTATION)) { 1096 ret.add(CaptureRequest.JPEG_ORIENTATION); 1097 } 1098 } catch (RemoteException e) { 1099 throw new IllegalStateException("Failed to query the available capture request keys!"); 1100 } finally { 1101 unregisterClient(mContext, token); 1102 } 1103 1104 return Collections.unmodifiableSet(ret); 1105 } 1106 1107 /** 1108 * Returns the set of keys supported by a {@link CaptureResult} passed as an argument to 1109 * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}. 1110 * 1111 * <p>The set returned is not modifiable, so any attempts to modify it will throw 1112 * a {@code UnsupportedOperationException}.</p> 1113 * 1114 * <p>In case the set is empty, then the extension is not able to support any capture results 1115 * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable} 1116 * callback will not be fired.</p> 1117 * 1118 * @param extension the extension type 1119 * 1120 * @return non-modifiable set of capture result keys supported by camera extension session 1121 * initialized with the given extension type. 1122 * @throws IllegalArgumentException in case of unsupported extension. 1123 */ 1124 @NonNull getAvailableCaptureResultKeys(@xtension int extension)1125 public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) { 1126 final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId); 1127 boolean success = registerClient(mContext, token); 1128 if (!success) { 1129 throw new IllegalArgumentException("Unsupported extensions"); 1130 } 1131 1132 HashSet<CaptureResult.Key> ret = new HashSet<>(); 1133 try { 1134 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { 1135 throw new IllegalArgumentException("Unsupported extension"); 1136 } 1137 1138 CameraMetadataNative captureResultMeta = null; 1139 if (areAdvancedExtensionsSupported()) { 1140 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); 1141 extender.init(mCameraId, mCharacteristicsMapNative); 1142 captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId); 1143 } else { 1144 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = 1145 initializeExtension(extension); 1146 extenders.second.onInit(token, mCameraId, 1147 mCharacteristicsMapNative.get(mCameraId)); 1148 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); 1149 captureResultMeta = extenders.second.getAvailableCaptureResultKeys(); 1150 extenders.second.onDeInit(token); 1151 } 1152 1153 if (captureResultMeta != null) { 1154 int[] resultKeys = captureResultMeta.get( 1155 CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS); 1156 if (resultKeys == null) { 1157 throw new AssertionError("android.request.availableResultKeys must be non-null " 1158 + "in the characteristics"); 1159 } 1160 CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta); 1161 Object crKey = CaptureResult.Key.class; 1162 Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey; 1163 1164 ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped, 1165 resultKeys, /*includeSynthetic*/ true)); 1166 1167 // Jpeg quality, orientation and sensor timestamp must always be supported 1168 if (!ret.contains(CaptureResult.JPEG_QUALITY)) { 1169 ret.add(CaptureResult.JPEG_QUALITY); 1170 } 1171 if (!ret.contains(CaptureResult.JPEG_ORIENTATION)) { 1172 ret.add(CaptureResult.JPEG_ORIENTATION); 1173 } 1174 if (!ret.contains(CaptureResult.SENSOR_TIMESTAMP)) { 1175 ret.add(CaptureResult.SENSOR_TIMESTAMP); 1176 } 1177 } 1178 } catch (RemoteException e) { 1179 throw new IllegalStateException("Failed to query the available capture result keys!"); 1180 } finally { 1181 unregisterClient(mContext, token); 1182 } 1183 1184 return Collections.unmodifiableSet(ret); 1185 } 1186 } 1187