1556 lines
70 KiB
Java
1556 lines
70 KiB
Java
/*
|
||
* Copyright (C) 2020 The Android Open Source Project
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
package android.hardware.camera2;
|
||
|
||
import android.annotation.FlaggedApi;
|
||
import android.annotation.IntDef;
|
||
import android.annotation.NonNull;
|
||
import android.annotation.Nullable;
|
||
import android.annotation.SuppressLint;
|
||
import android.content.ComponentName;
|
||
import android.content.ContentResolver;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.content.ServiceConnection;
|
||
import android.graphics.ImageFormat;
|
||
import android.hardware.camera2.CameraCharacteristics.Key;
|
||
import android.hardware.camera2.extension.IAdvancedExtenderImpl;
|
||
import android.hardware.camera2.extension.ICameraExtensionsProxyService;
|
||
import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
|
||
import android.hardware.camera2.extension.IInitializeSessionCallback;
|
||
import android.hardware.camera2.extension.IPreviewExtenderImpl;
|
||
import android.hardware.camera2.extension.LatencyRange;
|
||
import android.hardware.camera2.extension.SizeList;
|
||
import android.hardware.camera2.impl.CameraExtensionUtils;
|
||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||
import android.hardware.camera2.impl.ExtensionKey;
|
||
import android.hardware.camera2.impl.PublicKey;
|
||
import android.hardware.camera2.params.ExtensionSessionConfiguration;
|
||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||
import android.os.Binder;
|
||
import android.os.ConditionVariable;
|
||
import android.os.IBinder;
|
||
import android.os.RemoteException;
|
||
import android.os.SystemProperties;
|
||
import android.provider.Settings;
|
||
import android.util.IntArray;
|
||
import android.util.Log;
|
||
import android.util.Pair;
|
||
import android.util.Range;
|
||
import android.util.Size;
|
||
|
||
import com.android.internal.camera.flags.Flags;
|
||
|
||
import java.lang.annotation.Retention;
|
||
import java.lang.annotation.RetentionPolicy;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.Collections;
|
||
import java.util.HashMap;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Objects;
|
||
import java.util.Set;
|
||
import java.util.concurrent.Future;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.concurrent.TimeoutException;
|
||
|
||
/**
|
||
* <p>Allows clients to query availability and supported resolutions of camera extensions.</p>
|
||
*
|
||
* <p>Camera extensions give camera clients access to device-specific algorithms and sequences that
|
||
* can improve the overall image quality of snapshots in various cases such as low light, selfies,
|
||
* portraits, and scenes that can benefit from enhanced dynamic range. Often such sophisticated
|
||
* processing sequences will rely on multiple camera frames as input and will produce a single
|
||
* output.</p>
|
||
*
|
||
* <p>Camera extensions are not guaranteed to be present on all devices so camera clients must
|
||
* query for their availability via {@link CameraExtensionCharacteristics#getSupportedExtensions()}.
|
||
* </p>
|
||
*
|
||
* <p>In order to use any available camera extension, camera clients must create a corresponding
|
||
* {@link CameraExtensionSession} via
|
||
* {@link CameraDevice#createExtensionSession(ExtensionSessionConfiguration)}</p>
|
||
*
|
||
* <p>Camera clients must be aware that device-specific camera extensions may support only a
|
||
* subset of the available camera resolutions and must first query
|
||
* {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)} for supported
|
||
* single high-quality request output sizes and
|
||
* {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported
|
||
* repeating request output sizes.</p>
|
||
*
|
||
* <p>The extension characteristics for a given device are expected to remain static under
|
||
* normal operating conditions.</p>
|
||
*
|
||
* @see CameraManager#getCameraExtensionCharacteristics(String)
|
||
*/
|
||
public final class CameraExtensionCharacteristics {
|
||
private static final String TAG = "CameraExtensionCharacteristics";
|
||
|
||
/**
|
||
* Device-specific extension implementation for automatic selection of particular extension
|
||
* such as HDR or NIGHT depending on the current lighting and environment conditions.
|
||
*/
|
||
public static final int EXTENSION_AUTOMATIC = 0;
|
||
|
||
/**
|
||
* Device-specific extension implementation which tends to smooth the skin and apply other
|
||
* cosmetic effects to people's faces.
|
||
*/
|
||
public static final int EXTENSION_FACE_RETOUCH = 1;
|
||
|
||
/**
|
||
* Device-specific extension implementation which tends to smooth the skin and apply other
|
||
* cosmetic effects to people's faces.
|
||
*
|
||
* @deprecated Use {@link #EXTENSION_FACE_RETOUCH} instead.
|
||
*/
|
||
public @Deprecated static final int EXTENSION_BEAUTY = EXTENSION_FACE_RETOUCH;
|
||
|
||
/**
|
||
* Device-specific extension implementation which can blur certain regions of the final image
|
||
* thereby "enhancing" focus for all remaining non-blurred parts.
|
||
*/
|
||
public static final int EXTENSION_BOKEH = 2;
|
||
|
||
/**
|
||
* Device-specific extension implementation for enhancing the dynamic range of the
|
||
* final image.
|
||
*/
|
||
public static final int EXTENSION_HDR = 3;
|
||
|
||
/**
|
||
* Device-specific extension implementation that aims to suppress noise and improve the
|
||
* overall image quality under low light conditions.
|
||
*/
|
||
public static final int EXTENSION_NIGHT = 4;
|
||
|
||
/**
|
||
* An extension that aims to lock and stabilize a given region or object of interest.
|
||
*/
|
||
@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
|
||
public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
@IntDef(flag = true, value = {EXTENSION_AUTOMATIC,
|
||
EXTENSION_FACE_RETOUCH,
|
||
EXTENSION_BOKEH,
|
||
EXTENSION_HDR,
|
||
EXTENSION_NIGHT,
|
||
EXTENSION_EYES_FREE_VIDEOGRAPHY})
|
||
public @interface Extension {
|
||
}
|
||
|
||
/**
|
||
* Default camera output in case additional processing from CameraX extensions is not needed
|
||
*
|
||
* @hide
|
||
*/
|
||
public static final int NON_PROCESSING_INPUT_FORMAT = ImageFormat.PRIVATE;
|
||
|
||
/**
|
||
* CameraX extensions require YUV_420_888 as default input for processing at the moment
|
||
*
|
||
* @hide
|
||
*/
|
||
public static final int PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888;
|
||
|
||
private static final @Extension
|
||
int[] EXTENSION_LIST = new int[]{
|
||
EXTENSION_AUTOMATIC,
|
||
EXTENSION_FACE_RETOUCH,
|
||
EXTENSION_BOKEH,
|
||
EXTENSION_HDR,
|
||
EXTENSION_NIGHT};
|
||
|
||
/**
|
||
* List of synthetic CameraCharacteristics keys that are supported in the extensions.
|
||
*/
|
||
private static final List<CameraCharacteristics.Key>
|
||
SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
|
||
Arrays.asList(
|
||
CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
|
||
CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
|
||
);
|
||
|
||
private final Context mContext;
|
||
private final String mCameraId;
|
||
private final Map<String, CameraCharacteristics> mCharacteristicsMap;
|
||
private final Map<String, CameraMetadataNative> mCharacteristicsMapNative;
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public CameraExtensionCharacteristics(Context context, String cameraId,
|
||
Map<String, CameraCharacteristics> characteristicsMap) {
|
||
mContext = context;
|
||
mCameraId = cameraId;
|
||
mCharacteristicsMap = characteristicsMap;
|
||
mCharacteristicsMapNative =
|
||
CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap);
|
||
}
|
||
|
||
private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList,
|
||
Integer format) {
|
||
ArrayList<Size> ret = new ArrayList<>();
|
||
if ((sizesList != null) && (!sizesList.isEmpty())) {
|
||
for (SizeList entry : sizesList) {
|
||
if ((entry.format == format) && !entry.sizes.isEmpty()) {
|
||
for (android.hardware.camera2.extension.Size sz : entry.sizes) {
|
||
ret.add(new Size(sz.width, sz.height));
|
||
}
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
|
||
Integer format,
|
||
StreamConfigurationMap streamMap) {
|
||
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
|
||
|
||
if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888 ||
|
||
format == ImageFormat.PRIVATE) {
|
||
// Per API contract it is assumed that the extension is able to support all
|
||
// camera advertised sizes for JPEG, YUV_420_888 and PRIVATE in case it doesn't return
|
||
// a valid non-empty size list.
|
||
Size[] supportedSizes = streamMap.getOutputSizes(format);
|
||
if ((ret.isEmpty()) && (supportedSizes != null)) {
|
||
ret.addAll(Arrays.asList(supportedSizes));
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
private static List<Size> generateJpegSupportedSizes(List<SizeList> sizesList,
|
||
StreamConfigurationMap streamMap) {
|
||
ArrayList<Size> extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888);
|
||
HashSet<Size> supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList(
|
||
streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes);
|
||
HashSet<Size> supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes(
|
||
ImageFormat.JPEG)));
|
||
supportedSizes.retainAll(supportedJpegSizes);
|
||
|
||
return new ArrayList<>(supportedSizes);
|
||
}
|
||
|
||
/**
|
||
* A per-process global camera extension manager instance, to track and
|
||
* initialize/release extensions depending on client activity.
|
||
*/
|
||
private static final class CameraExtensionManagerGlobal {
|
||
private static final String TAG = "CameraExtensionManagerGlobal";
|
||
private static final String PROXY_PACKAGE_NAME = "com.android.cameraextensions";
|
||
private static final String PROXY_SERVICE_NAME =
|
||
"com.android.cameraextensions.CameraExtensionsProxyService";
|
||
|
||
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
|
||
private static final int FALLBACK_PACKAGE_NAME =
|
||
com.android.internal.R.string.config_extensionFallbackPackageName;
|
||
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
|
||
private static final int FALLBACK_SERVICE_NAME =
|
||
com.android.internal.R.string.config_extensionFallbackServiceName;
|
||
|
||
// Singleton instance
|
||
private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
|
||
new CameraExtensionManagerGlobal();
|
||
private final Object mLock = new Object();
|
||
private final int PROXY_SERVICE_DELAY_MS = 2000;
|
||
private ExtensionConnectionManager mConnectionManager = new ExtensionConnectionManager();
|
||
|
||
// Singleton, don't allow construction
|
||
private CameraExtensionManagerGlobal() {}
|
||
|
||
public static CameraExtensionManagerGlobal get() {
|
||
return GLOBAL_CAMERA_MANAGER;
|
||
}
|
||
|
||
private void releaseProxyConnectionLocked(Context ctx, int extension) {
|
||
if (mConnectionManager.getConnection(extension) != null) {
|
||
ctx.unbindService(mConnectionManager.getConnection(extension));
|
||
mConnectionManager.setConnection(extension, null);
|
||
mConnectionManager.setProxy(extension, null);
|
||
mConnectionManager.resetConnectionCount(extension);
|
||
}
|
||
}
|
||
|
||
private void connectToProxyLocked(Context ctx, int extension, boolean useFallback) {
|
||
if (mConnectionManager.getConnection(extension) == null) {
|
||
Intent intent = new Intent();
|
||
intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME);
|
||
String vendorProxyPackage = SystemProperties.get(
|
||
"ro.vendor.camera.extensions.package");
|
||
String vendorProxyService = SystemProperties.get(
|
||
"ro.vendor.camera.extensions.service");
|
||
if (!vendorProxyPackage.isEmpty() && !vendorProxyService.isEmpty()) {
|
||
Log.v(TAG,
|
||
"Choosing the vendor camera extensions proxy package: "
|
||
+ vendorProxyPackage);
|
||
Log.v(TAG,
|
||
"Choosing the vendor camera extensions proxy service: "
|
||
+ vendorProxyService);
|
||
intent.setClassName(vendorProxyPackage, vendorProxyService);
|
||
}
|
||
|
||
if (Flags.concertMode() && useFallback) {
|
||
String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME);
|
||
String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME);
|
||
|
||
if (!packageName.isEmpty() && !serviceName.isEmpty()) {
|
||
Log.v(TAG,
|
||
"Choosing the fallback software implementation package: "
|
||
+ packageName);
|
||
Log.v(TAG,
|
||
"Choosing the fallback software implementation service: "
|
||
+ serviceName);
|
||
intent.setClassName(packageName, serviceName);
|
||
}
|
||
}
|
||
|
||
InitializerFuture initFuture = new InitializerFuture();
|
||
ServiceConnection connection = new ServiceConnection() {
|
||
@Override
|
||
public void onServiceDisconnected(ComponentName component) {
|
||
mConnectionManager.setConnection(extension, null);
|
||
mConnectionManager.setProxy(extension, null);
|
||
}
|
||
|
||
@Override
|
||
public void onServiceConnected(ComponentName component, IBinder binder) {
|
||
ICameraExtensionsProxyService proxy =
|
||
ICameraExtensionsProxyService.Stub.asInterface(binder);
|
||
mConnectionManager.setProxy(extension, proxy);
|
||
if (mConnectionManager.getProxy(extension) == null) {
|
||
throw new IllegalStateException("Camera Proxy service is null");
|
||
}
|
||
try {
|
||
mConnectionManager.setAdvancedExtensionsSupported(extension,
|
||
mConnectionManager.getProxy(extension)
|
||
.advancedExtensionsSupported());
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Remote IPC failed!");
|
||
}
|
||
initFuture.setStatus(true);
|
||
}
|
||
};
|
||
ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
|
||
Context.BIND_ABOVE_CLIENT | Context.BIND_NOT_VISIBLE,
|
||
android.os.AsyncTask.THREAD_POOL_EXECUTOR, connection);
|
||
mConnectionManager.setConnection(extension, connection);
|
||
|
||
try {
|
||
initFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||
} catch (TimeoutException e) {
|
||
Log.e(TAG, "Timed out while initializing proxy service!");
|
||
}
|
||
}
|
||
}
|
||
|
||
private static class InitializerFuture implements Future<Boolean> {
|
||
private volatile Boolean mStatus;
|
||
ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
|
||
|
||
public void setStatus(boolean status) {
|
||
mStatus = status;
|
||
mCondVar.open();
|
||
}
|
||
|
||
@Override
|
||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||
return false; // don't allow canceling this task
|
||
}
|
||
|
||
@Override
|
||
public boolean isCancelled() {
|
||
return false; // can never cancel this task
|
||
}
|
||
|
||
@Override
|
||
public boolean isDone() {
|
||
return mStatus != null;
|
||
}
|
||
|
||
@Override
|
||
public Boolean get() {
|
||
mCondVar.block();
|
||
return mStatus;
|
||
}
|
||
|
||
@Override
|
||
public Boolean get(long timeout, TimeUnit unit) throws TimeoutException {
|
||
long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
|
||
if (!mCondVar.block(timeoutMs)) {
|
||
throw new TimeoutException(
|
||
"Failed to receive status after " + timeout + " " + unit);
|
||
}
|
||
|
||
if (mStatus == null) {
|
||
throw new AssertionError();
|
||
}
|
||
return mStatus;
|
||
}
|
||
}
|
||
|
||
public boolean registerClientHelper(Context ctx, IBinder token, int extension,
|
||
boolean useFallback) {
|
||
synchronized (mLock) {
|
||
boolean ret = false;
|
||
connectToProxyLocked(ctx, extension, useFallback);
|
||
if (mConnectionManager.getProxy(extension) == null) {
|
||
return false;
|
||
}
|
||
mConnectionManager.incrementConnectionCount(extension);
|
||
|
||
try {
|
||
ret = mConnectionManager.getProxy(extension).registerClient(token);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to initialize extension! Extension service does "
|
||
+ " not respond!");
|
||
}
|
||
if (!ret) {
|
||
mConnectionManager.decrementConnectionCount(extension);
|
||
}
|
||
|
||
if (mConnectionManager.getConnectionCount(extension) <= 0) {
|
||
releaseProxyConnectionLocked(ctx, extension);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
@SuppressLint("NonUserGetterCalled")
|
||
public boolean registerClient(Context ctx, IBinder token, int extension,
|
||
String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
|
||
boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
|
||
|
||
if (Flags.concertMode()) {
|
||
// Check if user enabled fallback impl
|
||
ContentResolver resolver = ctx.getContentResolver();
|
||
int userEnabled = Settings.Secure.getInt(resolver,
|
||
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1);
|
||
|
||
boolean vendorImpl = true;
|
||
if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) {
|
||
// At this point, we are connected to either CameraExtensionsProxyService or
|
||
// the vendor extension proxy service. If the vendor does not support the
|
||
// extension, unregisterClient and re-register client with the proxy service
|
||
// containing the fallback impl
|
||
vendorImpl = isExtensionSupported(cameraId, extension,
|
||
characteristicsMapNative);
|
||
}
|
||
|
||
if (!vendorImpl) {
|
||
unregisterClient(ctx, token, extension);
|
||
ret = registerClientHelper(ctx, token, extension, true /*useFallback*/);
|
||
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
public void unregisterClient(Context ctx, IBinder token, int extension) {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null) {
|
||
try {
|
||
mConnectionManager.getProxy(extension).unregisterClient(token);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to de-initialize extension! Extension service does"
|
||
+ " not respond!");
|
||
} finally {
|
||
mConnectionManager.decrementConnectionCount(extension);
|
||
if (mConnectionManager.getConnectionCount(extension) <= 0) {
|
||
releaseProxyConnectionLocked(ctx, extension);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void initializeSession(IInitializeSessionCallback cb, int extension)
|
||
throws RemoteException {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null
|
||
&& !mConnectionManager.isSessionInitialized()) {
|
||
mConnectionManager.getProxy(extension).initializeSession(cb);
|
||
mConnectionManager.setSessionInitialized(true);
|
||
} else {
|
||
cb.onFailure();
|
||
}
|
||
}
|
||
}
|
||
|
||
public void releaseSession(int extension) {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null) {
|
||
try {
|
||
mConnectionManager.getProxy(extension).releaseSession();
|
||
mConnectionManager.setSessionInitialized(false);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to release session! Extension service does"
|
||
+ " not respond!");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public boolean areAdvancedExtensionsSupported(int extension) {
|
||
return mConnectionManager.areAdvancedExtensionsSupported(extension);
|
||
}
|
||
|
||
public IPreviewExtenderImpl initializePreviewExtension(int extension)
|
||
throws RemoteException {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null) {
|
||
return mConnectionManager.getProxy(extension)
|
||
.initializePreviewExtension(extension);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public IImageCaptureExtenderImpl initializeImageExtension(int extension)
|
||
throws RemoteException {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null) {
|
||
return mConnectionManager.getProxy(extension)
|
||
.initializeImageExtension(extension);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public IAdvancedExtenderImpl initializeAdvancedExtension(int extension)
|
||
throws RemoteException {
|
||
synchronized (mLock) {
|
||
if (mConnectionManager.getProxy(extension) != null) {
|
||
return mConnectionManager.getProxy(extension)
|
||
.initializeAdvancedExtension(extension);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
private class ExtensionConnectionManager {
|
||
// Maps extension to ExtensionConnection
|
||
private Map<Integer, ExtensionConnection> mConnections = new HashMap<>();
|
||
private boolean mSessionInitialized = false;
|
||
|
||
public ExtensionConnectionManager() {
|
||
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
|
||
extensionList.addAll(EXTENSION_LIST);
|
||
if (Flags.concertModeApi()) {
|
||
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
|
||
}
|
||
|
||
for (int extensionType : extensionList.toArray()) {
|
||
mConnections.put(extensionType, new ExtensionConnection());
|
||
}
|
||
}
|
||
|
||
public ICameraExtensionsProxyService getProxy(@Extension int extension) {
|
||
return mConnections.get(extension).mProxy;
|
||
}
|
||
|
||
public ServiceConnection getConnection(@Extension int extension) {
|
||
return mConnections.get(extension).mConnection;
|
||
}
|
||
|
||
public int getConnectionCount(@Extension int extension) {
|
||
return mConnections.get(extension).mConnectionCount;
|
||
}
|
||
|
||
public boolean areAdvancedExtensionsSupported(@Extension int extension) {
|
||
return mConnections.get(extension).mSupportsAdvancedExtensions;
|
||
}
|
||
|
||
public boolean isSessionInitialized() {
|
||
return mSessionInitialized;
|
||
}
|
||
|
||
public void setProxy(@Extension int extension, ICameraExtensionsProxyService proxy) {
|
||
mConnections.get(extension).mProxy = proxy;
|
||
}
|
||
|
||
public void setConnection(@Extension int extension, ServiceConnection connection) {
|
||
mConnections.get(extension).mConnection = connection;
|
||
}
|
||
|
||
public void incrementConnectionCount(@Extension int extension) {
|
||
mConnections.get(extension).mConnectionCount++;
|
||
}
|
||
|
||
public void decrementConnectionCount(@Extension int extension) {
|
||
mConnections.get(extension).mConnectionCount--;
|
||
}
|
||
|
||
public void resetConnectionCount(@Extension int extension) {
|
||
mConnections.get(extension).mConnectionCount = 0;
|
||
}
|
||
|
||
public void setAdvancedExtensionsSupported(@Extension int extension,
|
||
boolean advancedExtSupported) {
|
||
mConnections.get(extension).mSupportsAdvancedExtensions = advancedExtSupported;
|
||
}
|
||
|
||
public void setSessionInitialized(boolean initialized) {
|
||
mSessionInitialized = initialized;
|
||
}
|
||
|
||
private class ExtensionConnection {
|
||
public ICameraExtensionsProxyService mProxy = null;
|
||
public ServiceConnection mConnection = null;
|
||
public int mConnectionCount = 0;
|
||
public boolean mSupportsAdvancedExtensions = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static boolean registerClient(Context ctx, IBinder token, int extension,
|
||
String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
|
||
return CameraExtensionManagerGlobal.get().registerClient(ctx, token, extension, cameraId,
|
||
characteristicsMapNative);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static void unregisterClient(Context ctx, IBinder token, int extension) {
|
||
CameraExtensionManagerGlobal.get().unregisterClient(ctx, token, extension);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static void initializeSession(IInitializeSessionCallback cb, int extension)
|
||
throws RemoteException {
|
||
CameraExtensionManagerGlobal.get().initializeSession(cb, extension);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static void releaseSession(int extension) {
|
||
CameraExtensionManagerGlobal.get().releaseSession(extension);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static boolean areAdvancedExtensionsSupported(int extension) {
|
||
return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(extension);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static boolean isExtensionSupported(String cameraId, int extensionType,
|
||
Map<String, CameraMetadataNative> characteristicsMap) {
|
||
if (areAdvancedExtensionsSupported(extensionType)) {
|
||
try {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType);
|
||
return extender.isExtensionAvailable(cameraId, characteristicsMap);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query extension availability! Extension service does not"
|
||
+ " respond!");
|
||
return false;
|
||
}
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders;
|
||
try {
|
||
extenders = initializeExtension(extensionType);
|
||
} catch (IllegalArgumentException e) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
return extenders.first.isExtensionAvailable(cameraId,
|
||
characteristicsMap.get(cameraId))
|
||
&& extenders.second.isExtensionAvailable(cameraId,
|
||
characteristicsMap.get(cameraId));
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query extension availability! Extension service does not"
|
||
+ " respond!");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType) {
|
||
IAdvancedExtenderImpl extender;
|
||
try {
|
||
extender = CameraExtensionManagerGlobal.get().initializeAdvancedExtension(
|
||
extensionType);
|
||
} catch (RemoteException e) {
|
||
throw new IllegalStateException("Failed to initialize extension: " + extensionType);
|
||
}
|
||
|
||
if (extender == null) {
|
||
throw new IllegalArgumentException("Unknown extension: " + extensionType);
|
||
}
|
||
|
||
return extender;
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
*/
|
||
public static Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> initializeExtension(
|
||
@Extension int extensionType) {
|
||
IPreviewExtenderImpl previewExtender;
|
||
IImageCaptureExtenderImpl imageExtender;
|
||
try {
|
||
previewExtender =
|
||
CameraExtensionManagerGlobal.get().initializePreviewExtension(extensionType);
|
||
imageExtender =
|
||
CameraExtensionManagerGlobal.get().initializeImageExtension(extensionType);
|
||
} catch (RemoteException e) {
|
||
throw new IllegalStateException("Failed to initialize extension: " + extensionType);
|
||
}
|
||
if ((imageExtender == null) || (previewExtender == null)) {
|
||
throw new IllegalArgumentException("Unknown extension: " + extensionType);
|
||
}
|
||
|
||
return new Pair<>(previewExtender, imageExtender);
|
||
}
|
||
|
||
private static <T> boolean isOutputSupportedFor(Class<T> klass) {
|
||
Objects.requireNonNull(klass, "klass must not be null");
|
||
|
||
if ((klass == android.graphics.SurfaceTexture.class) ||
|
||
(klass == android.view.SurfaceView.class)) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Return a list of supported device-specific extensions for a given camera device.
|
||
*
|
||
* @return non-modifiable list of available extensions
|
||
*/
|
||
public @NonNull List<Integer> getSupportedExtensions() {
|
||
ArrayList<Integer> ret = new ArrayList<>();
|
||
final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
|
||
|
||
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
|
||
extensionList.addAll(EXTENSION_LIST);
|
||
if (Flags.concertModeApi()) {
|
||
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
|
||
}
|
||
|
||
for (int extensionType : extensionList.toArray()) {
|
||
try {
|
||
boolean success = registerClient(mContext, token, extensionType, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (success && isExtensionSupported(mCameraId, extensionType,
|
||
mCharacteristicsMapNative)) {
|
||
ret.add(extensionType);
|
||
}
|
||
} finally {
|
||
unregisterClient(mContext, token, extensionType);
|
||
}
|
||
}
|
||
|
||
return Collections.unmodifiableList(ret);
|
||
}
|
||
|
||
/**
|
||
* Gets an extension specific camera characteristics field value.
|
||
*
|
||
* <p>An extension can have a reduced set of camera capabilities (such as limited zoom ratio
|
||
* range, available video stabilization modes, etc). This API enables applications to query for
|
||
* an extension’s specific camera characteristics. Applications are recommended to prioritize
|
||
* obtaining camera characteristics using this API when using an extension. A {@code null}
|
||
* result indicates that the extension specific characteristic is not defined or available.
|
||
*
|
||
* @param extension The extension type.
|
||
* @param key The characteristics field to read.
|
||
* @return The value of that key, or {@code null} if the field is not set.
|
||
*
|
||
* @throws IllegalArgumentException if the key is not valid or extension type is not a supported
|
||
* device-specific extension.
|
||
*/
|
||
@FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
|
||
public <T> @Nullable T get(@Extension int extension,
|
||
@NonNull CameraCharacteristics.Key<T> key) {
|
||
final IBinder token = new Binder(TAG + "#get:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
if (areAdvancedExtensionsSupported(extension) && getKeys(extension).contains(key)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
CameraMetadataNative metadata =
|
||
extender.getAvailableCharacteristicsKeyValues(mCameraId);
|
||
if (metadata == null) {
|
||
return null;
|
||
}
|
||
CameraCharacteristics characteristics = new CameraCharacteristics(metadata);
|
||
return characteristics.get(key);
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension for the specified key! Extension "
|
||
+ "service does not respond!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Returns the {@link CameraCharacteristics} keys that have extension-specific values.
|
||
*
|
||
* <p>An application can query the value from the key using
|
||
* {@link #get(int, CameraCharacteristics.Key)} API.
|
||
*
|
||
* @param extension The extension type.
|
||
* @return An unmodifiable set of keys that are extension specific.
|
||
*
|
||
* @throws IllegalArgumentException in case the extension type is not a
|
||
* supported device-specific extension
|
||
*/
|
||
@FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
|
||
public @NonNull Set<CameraCharacteristics.Key> getKeys(@Extension int extension) {
|
||
final IBinder token =
|
||
new Binder(TAG + "#getKeys:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
HashSet<CameraCharacteristics.Key> ret = new HashSet<>();
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
CameraMetadataNative metadata =
|
||
extender.getAvailableCharacteristicsKeyValues(mCameraId);
|
||
if (metadata == null) {
|
||
return Collections.emptySet();
|
||
}
|
||
|
||
int[] keys = metadata.get(
|
||
CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
|
||
if (keys == null) {
|
||
throw new AssertionError(
|
||
"android.request.availableCharacteristicsKeys must be non-null"
|
||
+ " in the characteristics");
|
||
}
|
||
CameraCharacteristics chars = new CameraCharacteristics(metadata);
|
||
|
||
Object key = CameraCharacteristics.Key.class;
|
||
Class<CameraCharacteristics.Key<?>> keyTyped =
|
||
(Class<CameraCharacteristics.Key<?>>) key;
|
||
|
||
ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
|
||
/*includeSynthetic*/ false));
|
||
|
||
// Add synthetic keys to the available key list if they are part of the supported
|
||
// synthetic camera characteristic key list
|
||
for (CameraCharacteristics.Key charKey :
|
||
SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
|
||
if (chars.get(charKey) != null) {
|
||
ret.add(charKey);
|
||
}
|
||
}
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension for all available keys! Extension "
|
||
+ "service does not respond!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
return Collections.unmodifiableSet(ret);
|
||
}
|
||
|
||
/**
|
||
* Checks for postview support of still capture.
|
||
*
|
||
* <p>A postview is a preview version of the still capture that is available before the final
|
||
* image. For example, it can be used as a temporary placeholder for the requested capture
|
||
* while the final image is being processed. The supported sizes for a still capture's postview
|
||
* can be retrieved using
|
||
* {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
|
||
*
|
||
* <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
|
||
* the formats of the still capture and postview are not required to be equivalent upon capture
|
||
* request.</p>
|
||
*
|
||
* @param extension the extension type
|
||
* @return {@code true} in case postview is supported, {@code false} otherwise
|
||
*
|
||
* @throws IllegalArgumentException in case the extension type is not a
|
||
* supported device-specific extension
|
||
*/
|
||
public boolean isPostviewAvailable(@Extension int extension) {
|
||
final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
return extender.isPostviewAvailable();
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
return extenders.second.isPostviewAvailable();
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension for postview availability! Extension "
|
||
+ "service does not respond!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Get a list of the postview sizes supported for a still capture, using its
|
||
* capture size {@code captureSize}, to use as an output for the postview request.
|
||
*
|
||
* <p>Available postview sizes will always be either equal to or less than the still
|
||
* capture size. When choosing the most applicable postview size for a usecase, it should
|
||
* be noted that lower resolution postviews will generally be available more quickly
|
||
* than larger resolution postviews. For example, when choosing a size for an optimized
|
||
* postview that will be displayed as a placeholder while the final image is processed,
|
||
* the resolution closest to the preview size may be most suitable.</p>
|
||
*
|
||
* <p>Note that device-specific extensions are allowed to support only a subset
|
||
* of the camera resolutions advertised by
|
||
* {@link StreamConfigurationMap#getOutputSizes}.</p>
|
||
*
|
||
* @param extension the extension type
|
||
* @param captureSize size of the still capture for which the postview is requested
|
||
* @param format device-specific extension output format of the postview
|
||
* @return non-modifiable list of available sizes or an empty list if the format and
|
||
* size is not supported.
|
||
* @throws IllegalArgumentException in case of unsupported extension or if postview
|
||
* feature is not supported by extension.
|
||
*/
|
||
@NonNull
|
||
public List<Size> getPostviewSupportedSizes(@Extension int extension,
|
||
@NonNull Size captureSize, int format) {
|
||
final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
android.hardware.camera2.extension.Size sz =
|
||
new android.hardware.camera2.extension.Size();
|
||
sz.width = captureSize.getWidth();
|
||
sz.height = captureSize.getHeight();
|
||
|
||
StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
|
||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
switch(format) {
|
||
case ImageFormat.YUV_420_888:
|
||
case ImageFormat.JPEG:
|
||
case ImageFormat.JPEG_R:
|
||
case ImageFormat.YCBCR_P010:
|
||
break;
|
||
default:
|
||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||
}
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
|
||
format);
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
if ((extenders.second.getCaptureProcessor() == null) ||
|
||
!isPostviewAvailable(extension)) {
|
||
// Extensions that don't implement any capture processor
|
||
// and have processing occur in the HAL don't currently support the
|
||
// postview feature
|
||
throw new IllegalArgumentException("Extension does not support "
|
||
+ "postview feature");
|
||
}
|
||
|
||
if (format == ImageFormat.YUV_420_888) {
|
||
return getSupportedSizes(
|
||
extenders.second.getSupportedPostviewResolutions(sz), format);
|
||
} else if (format == ImageFormat.JPEG) {
|
||
// The framework will perform the additional encoding pass on the
|
||
// processed YUV_420 buffers.
|
||
return getSupportedSizes(
|
||
extenders.second.getSupportedPostviewResolutions(sz), format);
|
||
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
|
||
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
|
||
// extension case
|
||
return new ArrayList<>();
|
||
} else {
|
||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||
}
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension postview supported sizes! Extension "
|
||
+ "service does not respond!");
|
||
return Collections.emptyList();
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get a list of sizes compatible with {@code klass} to use as an output for the
|
||
* repeating request
|
||
* {@link CameraExtensionSession#setRepeatingRequest}.
|
||
*
|
||
* <p>Note that device-specific extensions are allowed to support only a subset
|
||
* of the camera output surfaces and resolutions.
|
||
* The {@link android.graphics.SurfaceTexture} class is guaranteed at least one size for
|
||
* backward compatible cameras whereas other output classes are not guaranteed to be supported.
|
||
* </p>
|
||
*
|
||
* <p>Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
|
||
* {@link android.view.SurfaceView} classes are also guaranteed to be supported and include
|
||
* the same resolutions as {@link android.graphics.SurfaceTexture}.
|
||
* Clients must set the desired SurfaceView resolution by calling
|
||
* {@link android.view.SurfaceHolder#setFixedSize}.</p>
|
||
*
|
||
* @param extension the extension type
|
||
* @param klass a non-{@code null} {@link Class} object reference
|
||
* @return non-modifiable list of available sizes or an empty list if the Surface output is not
|
||
* supported
|
||
* @throws NullPointerException if {@code klass} was {@code null}
|
||
* @throws IllegalArgumentException in case of unsupported extension.
|
||
*/
|
||
@NonNull
|
||
public <T> List<Size> getExtensionSupportedSizes(@Extension int extension,
|
||
@NonNull Class<T> klass) {
|
||
if (!isOutputSupportedFor(klass)) {
|
||
return new ArrayList<>();
|
||
}
|
||
// TODO: Revisit this code once the Extension preview processor output format
|
||
// ambiguity is resolved in b/169799538.
|
||
|
||
final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
|
||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
return generateSupportedSizes(
|
||
extender.getSupportedPreviewOutputResolutions(mCameraId),
|
||
ImageFormat.PRIVATE, streamMap);
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.first.init(mCameraId,
|
||
mCharacteristicsMapNative.get(mCameraId));
|
||
return generateSupportedSizes(extenders.first.getSupportedResolutions(),
|
||
ImageFormat.PRIVATE, streamMap);
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
|
||
+ " not respond!");
|
||
return new ArrayList<>();
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Check whether a given extension is available and return the
|
||
* supported output surface resolutions that can be used for high-quality capture
|
||
* requests via {@link CameraExtensionSession#capture}.
|
||
*
|
||
* <p>Note that device-specific extensions are allowed to support only a subset
|
||
* of the camera resolutions advertised by
|
||
* {@link StreamConfigurationMap#getOutputSizes}.</p>
|
||
*
|
||
* <p>Device-specific extensions currently support at most three
|
||
* multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
|
||
* extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
|
||
* may or may not be supported.</p>
|
||
*
|
||
* @param extension the extension type
|
||
* @param format device-specific extension output format
|
||
* @return non-modifiable list of available sizes or an empty list if the format is not
|
||
* supported.
|
||
* @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
|
||
* ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
|
||
* ImageFormat.YCBCR_P010; or unsupported extension.
|
||
*/
|
||
public @NonNull
|
||
List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
|
||
try {
|
||
final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
|
||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
switch(format) {
|
||
case ImageFormat.YUV_420_888:
|
||
case ImageFormat.JPEG:
|
||
case ImageFormat.JPEG_R:
|
||
case ImageFormat.YCBCR_P010:
|
||
break;
|
||
default:
|
||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||
}
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions(
|
||
mCameraId), format, streamMap);
|
||
} else {
|
||
if (format == ImageFormat.YUV_420_888) {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
if (extenders.second.getCaptureProcessor() == null) {
|
||
// Extensions that don't implement any capture processor are limited to
|
||
// JPEG only!
|
||
return new ArrayList<>();
|
||
}
|
||
return generateSupportedSizes(extenders.second.getSupportedResolutions(),
|
||
format, streamMap);
|
||
} else if (format == ImageFormat.JPEG) {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
if (extenders.second.getCaptureProcessor() != null) {
|
||
// The framework will perform the additional encoding pass on the
|
||
// processed YUV_420 buffers.
|
||
return generateJpegSupportedSizes(
|
||
extenders.second.getSupportedResolutions(), streamMap);
|
||
} else {
|
||
return generateSupportedSizes(null, format, streamMap);
|
||
}
|
||
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
|
||
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
|
||
// basic extension case
|
||
return new ArrayList<>();
|
||
} else {
|
||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||
}
|
||
}
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
|
||
+ " not respond!");
|
||
return new ArrayList<>();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns the estimated capture latency range in milliseconds for the
|
||
* target capture resolution during the calls to {@link CameraExtensionSession#capture}. This
|
||
* includes the time spent processing the multi-frame capture request along with any additional
|
||
* time for encoding of the processed buffer if necessary.
|
||
*
|
||
* @param extension the extension type
|
||
* @param captureOutputSize size of the capture output surface. If it is not in the supported
|
||
* output sizes, maximum capture output size is used for the estimation
|
||
* @param format device-specific extension output format
|
||
* @return the range of estimated minimal and maximal capture latency in milliseconds
|
||
* or null if no capture latency info can be provided
|
||
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
|
||
* {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
|
||
* {@link ImageFormat#YCBCR_P010};
|
||
* or unsupported extension.
|
||
*/
|
||
public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
|
||
@NonNull Size captureOutputSize, @ImageFormat.Format int format) {
|
||
switch (format) {
|
||
case ImageFormat.YUV_420_888:
|
||
case ImageFormat.JPEG:
|
||
case ImageFormat.JPEG_R:
|
||
case ImageFormat.YCBCR_P010:
|
||
//No op
|
||
break;
|
||
default:
|
||
throw new IllegalArgumentException("Unsupported format: " + format);
|
||
}
|
||
|
||
final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
android.hardware.camera2.extension.Size sz =
|
||
new android.hardware.camera2.extension.Size();
|
||
sz.width = captureOutputSize.getWidth();
|
||
sz.height = captureOutputSize.getHeight();
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId,
|
||
sz, format);
|
||
if (latencyRange != null) {
|
||
return new Range(latencyRange.min, latencyRange.max);
|
||
}
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
if ((format == ImageFormat.YUV_420_888) &&
|
||
(extenders.second.getCaptureProcessor() == null) ){
|
||
// Extensions that don't implement any capture processor are limited to
|
||
// JPEG only!
|
||
return null;
|
||
}
|
||
if ((format == ImageFormat.JPEG) &&
|
||
(extenders.second.getCaptureProcessor() != null)) {
|
||
// The framework will perform the additional encoding pass on the
|
||
// processed YUV_420 buffers. Latency in this case is very device
|
||
// specific and cannot be estimated accurately enough.
|
||
return null;
|
||
}
|
||
if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
|
||
// JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
|
||
return null;
|
||
}
|
||
|
||
LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz);
|
||
if (latencyRange != null) {
|
||
return new Range(latencyRange.min, latencyRange.max);
|
||
}
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
|
||
+ " not respond!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Retrieve support for capture progress callbacks via
|
||
* {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}.
|
||
*
|
||
* @param extension the extension type
|
||
* @return {@code true} in case progress callbacks are supported, {@code false} otherwise
|
||
*
|
||
* @throws IllegalArgumentException in case of an unsupported extension.
|
||
*/
|
||
public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
|
||
final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
return extender.isCaptureProcessProgressAvailable();
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
return extenders.second.isCaptureProcessProgressAvailable();
|
||
}
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
|
||
+ " not respond!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Returns the set of keys supported by a {@link CaptureRequest} submitted in a
|
||
* {@link CameraExtensionSession} with a given extension type.
|
||
*
|
||
* <p>The set returned is not modifiable, so any attempts to modify it will throw
|
||
* a {@code UnsupportedOperationException}.</p>
|
||
*
|
||
* <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
|
||
* or newer versions are required to support {@link CaptureRequest#CONTROL_AF_MODE},
|
||
* {@link CaptureRequest#CONTROL_AF_REGIONS}, {@link CaptureRequest#CONTROL_AF_TRIGGER},
|
||
* {@link CaptureRequest#CONTROL_ZOOM_RATIO} for
|
||
* {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p>
|
||
*
|
||
* @param extension the extension type
|
||
*
|
||
* @return non-modifiable set of capture keys supported by camera extension session initialized
|
||
* with the given extension type.
|
||
* @throws IllegalArgumentException in case of unsupported extension.
|
||
*/
|
||
@NonNull
|
||
public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
|
||
final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
HashSet<CaptureRequest.Key> ret = new HashSet<>();
|
||
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
CameraMetadataNative captureRequestMeta = null;
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId);
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.onInit(token, mCameraId,
|
||
mCharacteristicsMapNative.get(mCameraId));
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
|
||
extenders.second.onDeInit(token);
|
||
}
|
||
|
||
if (captureRequestMeta != null) {
|
||
int[] requestKeys = captureRequestMeta.get(
|
||
CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS);
|
||
if (requestKeys == null) {
|
||
throw new AssertionError(
|
||
"android.request.availableRequestKeys must be non-null"
|
||
+ " in the characteristics");
|
||
}
|
||
CameraCharacteristics requestChars = new CameraCharacteristics(
|
||
captureRequestMeta);
|
||
|
||
Object crKey = CaptureRequest.Key.class;
|
||
Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>) crKey;
|
||
|
||
ret.addAll(requestChars.getAvailableKeyList(CaptureRequest.class, crKeyTyped,
|
||
requestKeys, /*includeSynthetic*/ true));
|
||
}
|
||
|
||
// Jpeg quality and orientation must always be supported
|
||
if (!ret.contains(CaptureRequest.JPEG_QUALITY)) {
|
||
ret.add(CaptureRequest.JPEG_QUALITY);
|
||
}
|
||
if (!ret.contains(CaptureRequest.JPEG_ORIENTATION)) {
|
||
ret.add(CaptureRequest.JPEG_ORIENTATION);
|
||
}
|
||
} catch (RemoteException e) {
|
||
throw new IllegalStateException("Failed to query the available capture request keys!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
|
||
return Collections.unmodifiableSet(ret);
|
||
}
|
||
|
||
/**
|
||
* Returns the set of keys supported by a {@link CaptureResult} passed as an argument to
|
||
* {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}.
|
||
*
|
||
* <p>The set returned is not modifiable, so any attempts to modify it will throw
|
||
* a {@code UnsupportedOperationException}.</p>
|
||
*
|
||
* <p>In case the set is empty, then the extension is not able to support any capture results
|
||
* and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}
|
||
* callback will not be fired.</p>
|
||
*
|
||
* <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
|
||
* or newer versions are required to support {@link CaptureResult#CONTROL_AF_MODE},
|
||
* {@link CaptureResult#CONTROL_AF_REGIONS}, {@link CaptureResult#CONTROL_AF_TRIGGER},
|
||
* {@link CaptureResult#CONTROL_AF_STATE}, {@link CaptureResult#CONTROL_ZOOM_RATIO} for
|
||
* {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p>
|
||
*
|
||
* @param extension the extension type
|
||
*
|
||
* @return non-modifiable set of capture result keys supported by camera extension session
|
||
* initialized with the given extension type.
|
||
* @throws IllegalArgumentException in case of unsupported extension.
|
||
*/
|
||
@NonNull
|
||
public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
|
||
final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
|
||
boolean success = registerClient(mContext, token, extension, mCameraId,
|
||
mCharacteristicsMapNative);
|
||
if (!success) {
|
||
throw new IllegalArgumentException("Unsupported extensions");
|
||
}
|
||
|
||
HashSet<CaptureResult.Key> ret = new HashSet<>();
|
||
try {
|
||
if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
|
||
throw new IllegalArgumentException("Unsupported extension");
|
||
}
|
||
|
||
CameraMetadataNative captureResultMeta = null;
|
||
if (areAdvancedExtensionsSupported(extension)) {
|
||
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
|
||
extender.init(mCameraId, mCharacteristicsMapNative);
|
||
captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId);
|
||
} else {
|
||
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
|
||
initializeExtension(extension);
|
||
extenders.second.onInit(token, mCameraId,
|
||
mCharacteristicsMapNative.get(mCameraId));
|
||
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
|
||
captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
|
||
extenders.second.onDeInit(token);
|
||
}
|
||
|
||
if (captureResultMeta != null) {
|
||
int[] resultKeys = captureResultMeta.get(
|
||
CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS);
|
||
if (resultKeys == null) {
|
||
throw new AssertionError("android.request.availableResultKeys must be non-null "
|
||
+ "in the characteristics");
|
||
}
|
||
CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
|
||
Object crKey = CaptureResult.Key.class;
|
||
Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
|
||
|
||
ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
|
||
resultKeys, /*includeSynthetic*/ true));
|
||
|
||
// Jpeg quality, orientation and sensor timestamp must always be supported
|
||
if (!ret.contains(CaptureResult.JPEG_QUALITY)) {
|
||
ret.add(CaptureResult.JPEG_QUALITY);
|
||
}
|
||
if (!ret.contains(CaptureResult.JPEG_ORIENTATION)) {
|
||
ret.add(CaptureResult.JPEG_ORIENTATION);
|
||
}
|
||
if (!ret.contains(CaptureResult.SENSOR_TIMESTAMP)) {
|
||
ret.add(CaptureResult.SENSOR_TIMESTAMP);
|
||
}
|
||
}
|
||
} catch (RemoteException e) {
|
||
throw new IllegalStateException("Failed to query the available capture result keys!");
|
||
} finally {
|
||
unregisterClient(mContext, token, extension);
|
||
}
|
||
|
||
return Collections.unmodifiableSet(ret);
|
||
}
|
||
|
||
|
||
/**
|
||
* <p>Minimum and maximum padding zoom factors supported by this camera device for
|
||
* {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for
|
||
* the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
|
||
* extension.</p>
|
||
* <p>The minimum and maximum padding zoom factors supported by the device for
|
||
* {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
|
||
* {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
|
||
* extension feature. This extension specific camera characteristic can be queried using
|
||
* {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.</p>
|
||
* <p><b>Units</b>: A pair of padding zoom factors in floating-points:
|
||
* (minPaddingZoomFactor, maxPaddingZoomFactor)</p>
|
||
* <p><b>Range of valid values:</b><br></p>
|
||
* <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p>
|
||
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
|
||
*/
|
||
@PublicKey
|
||
@NonNull
|
||
@ExtensionKey
|
||
@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
|
||
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
|
||
CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
|
||
}
|