blob: 71948b20e747992783e8b5c3b38e393b0bab8cd7 [file] [log] [blame]
/*
* Copyright 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 androidx.camera.extensions;
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraFilter;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraConfigProvider;
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
import androidx.camera.core.impl.Identifier;
import androidx.camera.core.impl.SessionProcessor;
import androidx.camera.extensions.internal.AdvancedVendorExtender;
import androidx.camera.extensions.internal.BasicVendorExtender;
import androidx.camera.extensions.internal.ExtensionVersion;
import androidx.camera.extensions.internal.ExtensionsUseCaseConfigFactory;
import androidx.camera.extensions.internal.VendorExtender;
import androidx.camera.extensions.internal.Version;
import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A class for querying extensions related information.
*
* <p>The typical usages include checking whether or not a camera exists that supports an extension
* by using {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}. Then after it has
* been determined that the extension can be enabled, a
* {@link #getExtensionCameraSelectorAndInjectCameraConfig(CameraProvider, CameraSelector, int)}
* call can be used to get the specified {@link CameraSelector} to bind use cases and enable the
* extension mode on the camera.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
final class ExtensionsInfo {
private static final String EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX = ":camera:camera"
+ "-extensions-";
private static final ExtensionDisabledValidator sExtensionDisabledValidator =
new ExtensionDisabledValidator();
/**
* Returns a {@link CameraSelector} for the specified extension mode.
*
* <p>The corresponding extension camera config provider will be injected to the
* {@link ExtendedCameraConfigProviderStore} when the function is called.
*
* @param cameraProvider The {@link CameraProvider} which will be used to bind use cases.
* @param baseCameraSelector The base {@link CameraSelector} to be applied the extension
* related configuration on.
* @param mode The target extension mode.
* @return a {@link CameraSelector} for the specified Extensions mode.
* @throws IllegalArgumentException If no camera can be found to support the specified
* extension mode, or the base {@link CameraSelector} has
* contained
* extension related configuration in it.
*/
@NonNull
static CameraSelector getExtensionCameraSelectorAndInjectCameraConfig(
@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector baseCameraSelector,
@ExtensionMode.Mode int mode) {
if (!isExtensionAvailable(cameraProvider, baseCameraSelector, mode)) {
throw new IllegalArgumentException("No camera can be found to support the specified "
+ "extensions mode! isExtensionAvailable should be checked first before "
+ "calling getExtensionEnabledCameraSelector.");
}
// Checks whether there has been Extensions related CameraConfig set in the base
// CameraSelector.
for (CameraFilter cameraFilter : baseCameraSelector.getCameraFilterSet()) {
if (cameraFilter instanceof ExtensionCameraFilter) {
throw new IllegalArgumentException(
"An extension is already applied to the base CameraSelector.");
}
}
// Injects CameraConfigProvider for the extension mode to the
// ExtendedCameraConfigProviderStore.
injectExtensionCameraConfig(mode);
CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(baseCameraSelector);
// Adds the CameraFilter that determines which cameras can support the Extensions mode
// to the CameraSelector.
builder.addCameraFilter(getFilter(mode));
return builder.build();
}
/**
* Returns true if the particular extension mode is available for the specified
* {@link CameraSelector}.
*
* @param cameraProvider The {@link CameraProvider} which will be used to bind use cases.
* @param baseCameraSelector The base {@link CameraSelector} to find a camera to use.
* @param mode The target extension mode to support.
*/
static boolean isExtensionAvailable(
@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector baseCameraSelector,
@ExtensionMode.Mode int mode) {
try {
CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(
baseCameraSelector);
builder.addCameraFilter(getFilter(mode));
builder.build().filter(cameraProvider.getAvailableCameraInfos());
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
/**
* Returns the estimated capture latency range in milliseconds for the target capture
* resolution.
*
* @param cameraProvider The {@link CameraProvider} which will be used to bind use cases.
* @param cameraSelector The {@link CameraSelector} to find a camera which supports the
* specified extension mode.
* @param mode The extension mode to check.
* @param surfaceResolution the surface resolution of the {@link ImageCapture} which will be
* used to take a picture. If the input value of this parameter is
* null or it is not included in the supported output sizes, the
* maximum capture output size is used to get the estimated range
* information.
* @return the range of estimated minimal and maximal capture latency in milliseconds.
* Returns null if no capture latency info can be provided.
* @throws IllegalArgumentException If no camera can be found to support the specified
* extension mode.
*/
@Nullable
@OptIn(markerClass = ExperimentalCamera2Interop.class)
static Range<Long> getEstimatedCaptureLatencyRange(
@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector cameraSelector,
@ExtensionMode.Mode int mode, @Nullable Size surfaceResolution) {
// Adds the filter to find a CameraInfo of the Camera which supports the specified
// extension mode. Checks this first so that the API behavior will be the same no matter
// the vendor library is above version 1.2 or not.
CameraSelector newCameraSelector = CameraSelector.Builder.fromSelector(
cameraSelector).addCameraFilter(getFilter(mode)).build();
CameraInfo extensionsCameraInfo;
try {
List<CameraInfo> cameraInfos =
newCameraSelector.filter(cameraProvider.getAvailableCameraInfos());
if (cameraInfos.isEmpty()) {
return null;
}
extensionsCameraInfo = cameraInfos.get(0);
} catch (IllegalArgumentException e) {
// No CameraInfo can be found to support the target extension mode.
throw new IllegalArgumentException(
"No camera can be found to support the specified extensions mode! "
+ "isExtensionAvailable should be checked first before calling "
+ "getEstimatedCaptureLatencyRange.");
}
// This API is only supported since version 1.2
if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_2) < 0) {
return null;
}
try {
VendorExtender vendorExtender = getVendorExtender(mode);
vendorExtender.init(extensionsCameraInfo);
return vendorExtender.getEstimatedCaptureLatencyRange(surfaceResolution);
} catch (NoSuchMethodError e) {
return null;
}
}
private static CameraFilter getFilter(@ExtensionMode.Mode int mode) {
CameraFilter filter;
String id = getExtendedCameraConfigProviderId(mode);
VendorExtender vendorExtender = getVendorExtender(mode);
filter = new ExtensionCameraFilter(id, vendorExtender);
return filter;
}
/**
* Injects {@link CameraConfigProvider} for specified extension mode to the
* {@link ExtendedCameraConfigProviderStore}.
*/
private static void injectExtensionCameraConfig(@ExtensionMode.Mode int mode) {
Identifier id = Identifier.create(getExtendedCameraConfigProviderId(mode));
if (ExtendedCameraConfigProviderStore.getConfigProvider(id) == CameraConfigProvider.EMPTY) {
ExtendedCameraConfigProviderStore.addConfig(id, (cameraInfo, context) -> {
VendorExtender vendorExtender = getVendorExtender(mode);
vendorExtender.init(cameraInfo);
ExtensionsUseCaseConfigFactory factory = new
ExtensionsUseCaseConfigFactory(mode, vendorExtender, context);
ExtensionsConfig.Builder builder = new ExtensionsConfig.Builder()
.setExtensionMode(mode)
.setUseCaseConfigFactory(factory)
.setCompatibilityId(id)
.setUseCaseCombinationRequiredRule(
CameraConfig.REQUIRED_RULE_COEXISTING_PREVIEW_AND_IMAGE_CAPTURE);
SessionProcessor sessionProcessor = vendorExtender.createSessionProcessor(context);
if (sessionProcessor != null) {
builder.setSessionProcessor(sessionProcessor);
}
return builder.build();
});
}
}
@NonNull
private static VendorExtender getVendorExtender(int mode) {
boolean isAdvancedExtenderSupported = isAdvancedExtenderSupported();
// Disable Advanced Extender until it is well tested.
if (isAdvancedExtenderSupported) {
return new DisabledVendorExtender();
}
// Force disable extension for some devices by quirk.
if (sExtensionDisabledValidator.shouldDisableExtension(isAdvancedExtenderSupported)) {
return new DisabledVendorExtender();
}
VendorExtender vendorExtender;
if (isAdvancedExtenderSupported) {
vendorExtender = new AdvancedVendorExtender(mode);
} else {
vendorExtender = new BasicVendorExtender(mode);
}
return vendorExtender;
}
private static boolean isAdvancedExtenderSupported() {
if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_2) < 0) {
return false;
}
return ExtensionVersion.isAdvancedExtenderSupported();
}
private static String getExtendedCameraConfigProviderId(@ExtensionMode.Mode int mode) {
String id;
switch (mode) {
case ExtensionMode.BOKEH:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_BOKEH";
break;
case ExtensionMode.HDR:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_HDR";
break;
case ExtensionMode.NIGHT:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NIGHT";
break;
case ExtensionMode.FACE_RETOUCH:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_FACE_RETOUCH";
break;
case ExtensionMode.AUTO:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_AUTO";
break;
case ExtensionMode.NONE:
id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NONE";
break;
default:
throw new IllegalArgumentException("Invalid extension mode!");
}
return id;
}
private ExtensionsInfo() {
}
static class DisabledVendorExtender implements VendorExtender {
@Override
public boolean isExtensionAvailable(@NonNull String cameraId,
@NonNull Map<String, CameraCharacteristics> characteristicsMap) {
return false;
}
@Override
public void init(@NonNull CameraInfo cameraInfo) {
}
@Nullable
@Override
public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size size) {
return null;
}
@NonNull
@Override
public List<Pair<Integer, Size[]>> getSupportedPreviewOutputResolutions() {
return Collections.emptyList();
}
@NonNull
@Override
public List<Pair<Integer, Size[]>> getSupportedCaptureOutputResolutions() {
return Collections.emptyList();
}
@NonNull
@Override
public Size[] getSupportedYuvAnalysisResolutions() {
return new Size[0];
}
@Nullable
@Override
public SessionProcessor createSessionProcessor(@NonNull Context context) {
return null;
}
}
}