releaseInternal() {
finishClose();
break;
case OPENED:
+ case CONFIGURED:
setState(InternalState.RELEASING);
//TODO(b/162314023): Avoid calling abortCapture to prevent the many test failures
// caused by shutdown(). We should consider re-enabling it once the cause is
@@ -625,9 +652,16 @@ public void onUseCaseUpdated(@NonNull UseCase useCase) {
@Override
public void onUseCaseReset(@NonNull UseCase useCase) {
Preconditions.checkNotNull(useCase);
- String useCaseId = getUseCaseId(useCase);
SessionConfig sessionConfig = useCase.getSessionConfig();
UseCaseConfig> useCaseConfig = useCase.getCurrentConfig();
+ resetUseCase(getUseCaseId(useCase), sessionConfig, useCaseConfig);
+ }
+
+ private void resetUseCase(
+ @NonNull String useCaseId,
+ @NonNull SessionConfig sessionConfig,
+ @NonNull UseCaseConfig> useCaseConfig
+ ) {
mExecutor.execute(() -> {
debugLog("Use case " + useCaseId + " RESET");
mUseCaseAttachState.updateUseCase(useCaseId, sessionConfig, useCaseConfig);
@@ -650,9 +684,8 @@ public void onUseCaseReset(@NonNull UseCase useCase) {
* This method should only be used by tests. This will post to the Camera's thread and
* block until completion.
*
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.TESTS)
+ @VisibleForTesting
boolean isUseCaseAttached(@NonNull UseCase useCase) {
try {
String useCaseId = getUseCaseId(useCase);
@@ -672,6 +705,31 @@ boolean isUseCaseAttached(@NonNull UseCase useCase) {
}
}
+ @VisibleForTesting
+ boolean isMeteringRepeatingAttached() {
+ try {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ try {
+ mExecutor.execute(() -> {
+ if (mMeteringRepeatingSession == null) {
+ completer.set(false);
+ return;
+ }
+ String id = getMeteringRepeatingId(mMeteringRepeatingSession);
+ completer.set(mUseCaseAttachState.isUseCaseAttached(id));
+ });
+ } catch (RejectedExecutionException e) {
+ completer.setException(new RuntimeException(
+ "Unable to check if MeteringRepeating is attached. Camera executor "
+ + "shut down."));
+ }
+ return "isMeteringRepeatingAttached";
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException("Unable to check if MeteringRepeating is attached.", e);
+ }
+ }
+
/**
* Sets the use case to be in the state where the capture session will be configured to handle
* capture requests from the use case.
@@ -692,7 +750,7 @@ public void attachUseCases(@NonNull Collection inputUseCases) {
* use count to recover the additional increment here.
*/
mCameraControlInternal.incrementUseCount();
- notifyStateAttachedToUseCases(new ArrayList<>(useCases));
+ notifyStateAttachedAndCameraControlReady(new ArrayList<>(useCases));
List useCaseInfos = new ArrayList<>(toUseCaseInfos(useCases));
try {
mExecutor.execute(() -> {
@@ -798,7 +856,7 @@ public CameraConfig getExtendedConfig() {
return mCameraConfig;
}
- private void notifyStateAttachedToUseCases(List useCases) {
+ private void notifyStateAttachedAndCameraControlReady(List useCases) {
for (UseCase useCase : useCases) {
String useCaseId = getUseCaseId(useCase);
if (mNotifyStateAttachedSet.contains(useCaseId)) {
@@ -807,6 +865,7 @@ private void notifyStateAttachedToUseCases(List useCases) {
mNotifyStateAttachedSet.add(useCaseId);
useCase.onStateAttached();
+ useCase.onCameraControlReady();
}
}
@@ -924,7 +983,20 @@ private void addOrRemoveMeteringRepeatingUseCase() {
if (mMeteringRepeatingSession == null) {
mMeteringRepeatingSession = new MeteringRepeatingSession(
mCameraInfoInternal.getCameraCharacteristicsCompat(),
- mDisplayInfoManager);
+ mDisplayInfoManager,
+ () -> {
+ if (!isMeteringRepeatingAttached()) {
+ return;
+ }
+
+ SessionConfig sessionConfigMeteringRepeating =
+ mMeteringRepeatingSession.getSessionConfig();
+ UseCaseConfig> useCaseConfig =
+ mMeteringRepeatingSession.getUseCaseConfig();
+
+ resetUseCase(getMeteringRepeatingId(mMeteringRepeatingSession),
+ sessionConfigMeteringRepeating, useCaseConfig);
+ });
}
addMeteringRepeating();
} else {
@@ -958,12 +1030,13 @@ private void removeMeteringRepeating() {
private void addMeteringRepeating() {
if (mMeteringRepeatingSession != null) {
+ String id = getMeteringRepeatingId(mMeteringRepeatingSession);
mUseCaseAttachState.setUseCaseAttached(
- mMeteringRepeatingSession.getName() + mMeteringRepeatingSession.hashCode(),
+ id,
mMeteringRepeatingSession.getSessionConfig(),
mMeteringRepeatingSession.getUseCaseConfig());
mUseCaseAttachState.setUseCaseActive(
- mMeteringRepeatingSession.getName() + mMeteringRepeatingSession.hashCode(),
+ id,
mMeteringRepeatingSession.getSessionConfig(),
mMeteringRepeatingSession.getUseCaseConfig());
}
@@ -976,8 +1049,8 @@ public CameraInfoInternal getCameraInfoInternal() {
return mCameraInfoInternal;
}
- /** @hide */
- @RestrictTo(RestrictTo.Scope.TESTS)
+ @NonNull
+ @VisibleForTesting
public CameraAvailability getCameraAvailability() {
return mCameraAvailability;
}
@@ -1127,14 +1200,23 @@ void openCaptureSession() {
return;
}
- if (!validatingBuilder.build().getImplementationOptions().containsOption(
- Camera2ImplConfig.STREAM_USE_CASE_OPTION)) {
- validatingBuilder.addImplementationOption(Camera2ImplConfig.STREAM_USE_CASE_OPTION,
- StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(
- mUseCaseAttachState.getAttachedUseCaseConfigs(),
- mUseCaseAttachState.getAttachedSessionConfigs()));
+ // Checks if capture session is allowed to open in concurrent camera mode.
+ if (!mCameraStateRegistry.tryOpenCaptureSession(
+ mCameraDevice.getId(),
+ mCameraCoordinator.getPairedConcurrentCameraId(mCameraDevice.getId()))) {
+ debugLog("Unable to create capture session in camera operating mode = "
+ + mCameraCoordinator.getCameraOperatingMode());
+ return;
}
+ Map streamUseCaseMap = new HashMap<>();
+ StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+ mUseCaseAttachState.getAttachedSessionConfigs(),
+ mUseCaseAttachState.getAttachedUseCaseConfigs(),
+ streamUseCaseMap);
+
+ mCaptureSession.setStreamUseCaseMap(streamUseCaseMap);
+
CaptureSessionInterface captureSession = mCaptureSession;
ListenableFuture openCaptureSession = captureSession.open(validatingBuilder.build(),
Preconditions.checkNotNull(mCameraDevice), mCaptureSessionOpenerBuilder.build());
@@ -1143,7 +1225,11 @@ void openCaptureSession() {
@Override
@ExecutedBy("mExecutor")
public void onSuccess(@Nullable Void result) {
- // Nothing to do.
+ // TODO(b/271182406): Apply the CONFIGURED state to non-concurrent mode.
+ if (mCameraCoordinator.getCameraOperatingMode() == CAMERA_OPERATING_MODE_CONCURRENT
+ && mState == InternalState.OPENED) {
+ setState(InternalState.CONFIGURED);
+ }
}
@Override
@@ -1347,6 +1433,11 @@ static String getUseCaseId(@NonNull UseCase useCase) {
return useCase.getName() + useCase.hashCode();
}
+ @NonNull
+ static String getMeteringRepeatingId(@NonNull MeteringRepeatingSession meteringRepeating) {
+ return meteringRepeating.getName() + meteringRepeating.hashCode();
+ }
+
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
void debugLog(@NonNull String msg) {
debugLog(msg, null);
@@ -1392,6 +1483,13 @@ enum InternalState {
* capture session can be active. Capture requests should be issued during this state only.
*/
OPENED,
+ /**
+ * A stable state where the camera has been opened and capture session has been configured.
+ *
+ * It is a state only used in concurrent mode to differentiate from OPENED state for
+ * capture session configuration status.
+ */
+ CONFIGURED,
/**
* A transitional state where the camera device is currently closing.
*
@@ -1465,6 +1563,9 @@ void setState(@NonNull InternalState state, @Nullable CameraState.StateError sta
case OPENED:
publicState = State.OPEN;
break;
+ case CONFIGURED:
+ publicState = State.CONFIGURED;
+ break;
case CLOSING:
publicState = State.CLOSING;
break;
@@ -1578,7 +1679,12 @@ public void onOpened(@NonNull CameraDevice cameraDevice) {
case OPENING:
case REOPENING:
setState(InternalState.OPENED);
- openCaptureSession();
+ if (mCameraStateRegistry.tryOpenCaptureSession(
+ cameraDevice.getId(),
+ mCameraCoordinator.getPairedConcurrentCameraId(
+ mCameraDevice.getId()))) {
+ openCaptureSession();
+ }
break;
default:
throw new IllegalStateException(
@@ -1640,6 +1746,7 @@ public void onError(@NonNull CameraDevice cameraDevice, int error) {
break;
case OPENING:
case OPENED:
+ case CONFIGURED:
case REOPENING:
Logger.d(TAG, String.format("CameraDevice.onError(): %s failed with %s while "
+ "in %s state. Will attempt recovering from error.",
@@ -1656,6 +1763,7 @@ public void onError(@NonNull CameraDevice cameraDevice, int error) {
private void handleErrorOnOpen(@NonNull CameraDevice cameraDevice, int error) {
Preconditions.checkState(
mState == InternalState.OPENING || mState == InternalState.OPENED
+ || mState == InternalState.CONFIGURED
|| mState == InternalState.REOPENING,
"Attempt to handle open error from non open state: " + mState);
switch (error) {
@@ -1972,6 +2080,17 @@ boolean isCameraAvailable() {
}
}
+ final class CameraConfigureAvailable
+ implements CameraStateRegistry.OnConfigureAvailableListener {
+
+ @Override
+ public void onConfigureAvailable() {
+ if (mState == InternalState.OPENED) {
+ openCaptureSession();
+ }
+ }
+ }
+
final class ControlUpdateListenerInternal implements
CameraControlInternal.ControlUpdateCallback {
diff --git a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index cb4f1cdf9..3729cf8de 100644
--- a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -26,8 +26,11 @@
import android.hardware.camera2.CameraMetadata;
import android.os.Build;
import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
import android.view.Surface;
+import androidx.annotation.FloatRange;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -36,19 +39,24 @@
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
+import androidx.camera.camera2.internal.compat.StreamConfigurationMapCompat;
+import androidx.camera.camera2.internal.compat.params.DynamicRangesCompat;
import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
+import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.camera2.internal.compat.quirk.ZslDisablerQuirk;
import androidx.camera.camera2.internal.compat.workaround.FlashAvailabilityChecker;
import androidx.camera.camera2.interop.Camera2CameraInfo;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraState;
+import androidx.camera.core.DynamicRange;
import androidx.camera.core.ExposureState;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.Logger;
import androidx.camera.core.ZoomState;
-import androidx.camera.core.impl.CamcorderProfileProvider;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraInfoInternal;
+import androidx.camera.core.impl.EncoderProfilesProvider;
import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.Timebase;
@@ -59,11 +67,15 @@
import androidx.lifecycle.Observer;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -105,7 +117,7 @@ public final class Camera2CameraInfoImpl implements CameraInfoInternal {
@NonNull
private final Quirks mCameraQuirks;
@NonNull
- private final CamcorderProfileProvider mCamera2CamcorderProfileProvider;
+ private final EncoderProfilesProvider mCamera2EncoderProfilesProvider;
@NonNull
private final CameraManagerCompat mCameraManager;
@@ -113,7 +125,7 @@ public final class Camera2CameraInfoImpl implements CameraInfoInternal {
* Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
* called, camera control related API (torch/exposure/zoom) will return default values.
*/
- Camera2CameraInfoImpl(@NonNull String cameraId,
+ public Camera2CameraInfoImpl(@NonNull String cameraId,
@NonNull CameraManagerCompat cameraManager) throws CameraAccessExceptionCompat {
mCameraId = Preconditions.checkNotNull(cameraId);
mCameraManager = cameraManager;
@@ -121,8 +133,7 @@ public final class Camera2CameraInfoImpl implements CameraInfoInternal {
mCameraCharacteristicsCompat = cameraManager.getCameraCharacteristicsCompat(mCameraId);
mCamera2CameraInfo = new Camera2CameraInfo(this);
mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat);
- mCamera2CamcorderProfileProvider = new Camera2CamcorderProfileProvider(cameraId,
- mCameraCharacteristicsCompat);
+ mCamera2EncoderProfilesProvider = new Camera2EncoderProfilesProvider(cameraId);
mCameraStateLiveData = new RedirectableLiveData<>(
CameraState.create(CameraState.Type.CLOSED));
}
@@ -178,19 +189,13 @@ public CameraCharacteristicsCompat getCameraCharacteristicsCompat() {
return mCameraCharacteristicsCompat;
}
- @Nullable
+ @CameraSelector.LensFacing
@Override
- public Integer getLensFacing() {
+ public int getLensFacing() {
Integer lensFacing = mCameraCharacteristicsCompat.get(CameraCharacteristics.LENS_FACING);
- Preconditions.checkNotNull(lensFacing);
- switch (lensFacing) {
- case CameraCharacteristics.LENS_FACING_FRONT:
- return CameraSelector.LENS_FACING_FRONT;
- case CameraCharacteristics.LENS_FACING_BACK:
- return CameraSelector.LENS_FACING_BACK;
- default:
- return null;
- }
+ Preconditions.checkArgument(lensFacing != null, "Unable to get the lens facing of the "
+ + "camera.");
+ return LensFacingUtil.getCameraSelectorLensFacing(lensFacing);
}
@Override
@@ -201,9 +206,8 @@ public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
// Currently this assumes that a back-facing camera is always opposite to the screen.
// This may not be the case for all devices, so in the future we may need to handle that
// scenario.
- final Integer lensFacing = getLensFacing();
- boolean isOppositeFacingScreen =
- (lensFacing != null && CameraSelector.LENS_FACING_BACK == lensFacing);
+ final int lensFacing = getLensFacing();
+ boolean isOppositeFacingScreen = CameraSelector.LENS_FACING_BACK == lensFacing;
return CameraOrientationUtil.getRelativeImageRotation(
relativeRotationDegrees,
sensorOrientation,
@@ -264,7 +268,7 @@ private void logDeviceLevel() {
@Override
public boolean hasFlashUnit() {
- return FlashAvailabilityChecker.isFlashAvailable(mCameraCharacteristicsCompat);
+ return FlashAvailabilityChecker.isFlashAvailable(mCameraCharacteristicsCompat::get);
}
@NonNull
@@ -345,6 +349,33 @@ public String getImplementationType() {
? IMPLEMENTATION_TYPE_CAMERA2_LEGACY : IMPLEMENTATION_TYPE_CAMERA2;
}
+ @FloatRange(from = 0, fromInclusive = false)
+ @Override
+ public float getIntrinsicZoomRatio() {
+ final Integer lensFacing =
+ mCameraCharacteristicsCompat.get(CameraCharacteristics.LENS_FACING);
+ if (lensFacing == null) {
+ return INTRINSIC_ZOOM_RATIO_UNKNOWN;
+ }
+
+ int fovDegrees;
+ int defaultFovDegrees;
+ try {
+ fovDegrees =
+ FovUtil.focalLengthToViewAngleDegrees(
+ FovUtil.getDefaultFocalLength(mCameraCharacteristicsCompat),
+ FovUtil.getSensorHorizontalLength(mCameraCharacteristicsCompat));
+ defaultFovDegrees = FovUtil.getDeviceDefaultViewAngleDegrees(mCameraManager,
+ lensFacing);
+ } catch (Exception e) {
+ Logger.e(TAG, "The camera is unable to provide necessary information to resolve its "
+ + "intrinsic zoom ratio with error: " + e);
+ return INTRINSIC_ZOOM_RATIO_UNKNOWN;
+ }
+
+ return ((float) defaultFovDegrees) / fovDegrees;
+ }
+
@Override
public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
synchronized (mLock) {
@@ -358,7 +389,8 @@ public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
@Override
public boolean isZslSupported() {
- return Build.VERSION.SDK_INT >= 23 && isPrivateReprocessingSupported();
+ return Build.VERSION.SDK_INT >= 23 && isPrivateReprocessingSupported()
+ && (DeviceQuirks.get(ZslDisablerQuirk.class) == null);
}
@Override
@@ -370,8 +402,8 @@ public boolean isPrivateReprocessingSupported() {
/** {@inheritDoc} */
@NonNull
@Override
- public CamcorderProfileProvider getCamcorderProfileProvider() {
- return mCamera2CamcorderProfileProvider;
+ public EncoderProfilesProvider getEncoderProfilesProvider() {
+ return mCamera2EncoderProfilesProvider;
}
@NonNull
@@ -389,6 +421,33 @@ public Timebase getTimebase() {
}
}
+ @NonNull
+ @Override
+ public List getSupportedResolutions(int format) {
+ StreamConfigurationMapCompat mapCompat =
+ mCameraCharacteristicsCompat.getStreamConfigurationMapCompat();
+ Size[] size = mapCompat.getOutputSizes(format);
+ return size != null ? Arrays.asList(size) : Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List getSupportedHighResolutions(int format) {
+ StreamConfigurationMapCompat mapCompat =
+ mCameraCharacteristicsCompat.getStreamConfigurationMapCompat();
+ Size[] size = mapCompat.getHighResolutionOutputSizes(format);
+ return size != null ? Arrays.asList(size) : Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Set getSupportedDynamicRanges() {
+ DynamicRangesCompat dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(
+ mCameraCharacteristicsCompat);
+
+ return dynamicRangesCompat.getSupportedDynamicRanges();
+ }
+
@Override
public void addSessionCaptureCallback(@NonNull Executor executor,
@NonNull CameraCaptureCallback callback) {
@@ -433,6 +492,19 @@ public Quirks getCameraQuirks() {
return mCameraQuirks;
}
+ @NonNull
+ @Override
+ public Set> getSupportedFrameRateRanges() {
+ Range[] availableTargetFpsRanges =
+ mCameraCharacteristicsCompat.get(
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+ if (availableTargetFpsRanges != null) {
+ return new HashSet<>(Arrays.asList(availableTargetFpsRanges));
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
/**
* Gets the implementation of {@link Camera2CameraInfo}.
*/
diff --git a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
index 65b43a2cf..7da2f8280 100644
--- a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
+++ b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
@@ -43,6 +43,7 @@
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.internal.compat.workaround.FlashAvailabilityChecker;
import androidx.camera.camera2.internal.compat.workaround.OverrideAeModeForStillCapture;
import androidx.camera.camera2.internal.compat.workaround.UseTorchAsFlash;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
@@ -127,6 +128,8 @@ class Camera2CapturePipeline {
@NonNull
private final UseTorchAsFlash mUseTorchAsFlash;
+ private final boolean mHasFlashUnit;
+
@NonNull
private final Quirks mCameraQuirk;
@@ -153,6 +156,7 @@ class Camera2CapturePipeline {
mExecutor = executor;
mCameraQuirk = cameraQuirks;
mUseTorchAsFlash = new UseTorchAsFlash(cameraQuirks);
+ mHasFlashUnit = FlashAvailabilityChecker.isFlashAvailable(cameraCharacteristics::get);
}
@ExecutedBy("mExecutor")
@@ -183,11 +187,13 @@ public ListenableFuture> submitStillCaptures(
pipeline.addTask(new AfTask(mCameraControl));
}
- if (isTorchAsFlash(flashType)) {
- pipeline.addTask(new TorchTask(mCameraControl, flashMode, mExecutor));
- } else {
- pipeline.addTask(new AePreCaptureTask(mCameraControl, flashMode, aeQuirk));
- }
+ if (mHasFlashUnit) {
+ if (isTorchAsFlash(flashType)) {
+ pipeline.addTask(new TorchTask(mCameraControl, flashMode, mExecutor));
+ } else {
+ pipeline.addTask(new AePreCaptureTask(mCameraControl, flashMode, aeQuirk));
+ }
+ } // If there is no flash unit, skip the flash related task instead of failing the pipeline.
return Futures.nonCancellationPropagating(
pipeline.executeCapture(captureConfigs, flashMode));
diff --git a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
index c1de11329..60c74f072 100644
--- a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
+++ b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
@@ -28,6 +28,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.interop.CaptureRequestOptions;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.Logger;
@@ -35,6 +36,7 @@
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.StreamSpec;
import java.util.ArrayList;
import java.util.List;
@@ -94,6 +96,21 @@ private static void applyImplementationOptionToCaptureBuilder(
}
}
+ @OptIn(markerClass = ExperimentalCamera2Interop.class)
+ private static void applyAeFpsRange(@NonNull CaptureConfig captureConfig,
+ @NonNull CaptureRequest.Builder builder) {
+ boolean containsTargetFpsRange = CaptureRequestOptions.Builder.from(
+ captureConfig.getImplementationOptions()).build().containsOption(
+ Camera2ImplConfig.createCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE));
+ if (!containsTargetFpsRange && !captureConfig.getExpectedFrameRateRange().equals(
+ StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
+ builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+ captureConfig.getExpectedFrameRateRange());
+ }
+
+ }
+
/**
* Builds a {@link CaptureRequest} from a {@link CaptureConfig} and a {@link CameraDevice}.
@@ -136,6 +153,8 @@ public static CaptureRequest build(@NonNull CaptureConfig captureConfig,
applyImplementationOptionToCaptureBuilder(builder,
captureConfig.getImplementationOptions());
+ applyAeFpsRange(captureConfig, builder);
+
if (captureConfig.getImplementationOptions().containsOption(
CaptureConfig.OPTION_ROTATION)) {
builder.set(CaptureRequest.JPEG_ORIENTATION,
diff --git a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
index 98e8758e7..7c1d95ee9 100644
--- a/camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
+++ b/camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
@@ -17,7 +17,9 @@
package androidx.camera.camera2.internal;
import android.content.Context;
+import android.hardware.camera2.CameraDevice;
import android.media.CamcorderProfile;
+import android.util.Pair;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -29,6 +31,8 @@
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.impl.AttachedSurfaceInfo;
import androidx.camera.core.impl.CameraDeviceSurfaceManager;
+import androidx.camera.core.impl.CameraMode;
+import androidx.camera.core.impl.StreamSpec;
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.core.util.Preconditions;
@@ -42,7 +46,7 @@
* Camera device manager to provide the guaranteed supported stream capabilities related info for
* all camera devices
*
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession} defines the default
+ *
{@link CameraDevice#createCaptureSession} defines the default
* guaranteed stream combinations for different hardware level devices. It defines what combination
* of surface configuration type and size pairs can be supported for different hardware level camera
* devices. This structure is used to store the guaranteed supported stream capabilities related
@@ -57,8 +61,6 @@ public final class Camera2DeviceSurfaceManager implements CameraDeviceSurfaceMan
/**
* Creates a new, initialized Camera2DeviceSurfaceManager.
- *
- * @hide
*/
@RestrictTo(Scope.LIBRARY)
public Camera2DeviceSurfaceManager(@NonNull Context context,
@@ -111,36 +113,10 @@ private void init(@NonNull Context context, @NonNull CameraManagerCompat cameraM
}
}
- /**
- * Check whether the input surface configuration list is under the capability of any combination
- * of this object.
- *
- * @param cameraId the camera id of the camera device to be compared
- * @param surfaceConfigList the surface configuration list to be compared
- * @return the check result that whether it could be supported
- * @throws IllegalStateException if not initialized
- */
- @Override
- public boolean checkSupported(
- @NonNull String cameraId, @Nullable List surfaceConfigList) {
- if (surfaceConfigList == null || surfaceConfigList.isEmpty()) {
- return true;
- }
-
- SupportedSurfaceCombination supportedSurfaceCombination =
- mCameraSupportedSurfaceCombinationMap.get(cameraId);
-
- boolean isSupported = false;
- if (supportedSurfaceCombination != null) {
- isSupported = supportedSurfaceCombination.checkSupported(surfaceConfigList);
- }
-
- return isSupported;
- }
-
/**
* Transform to a SurfaceConfig object with cameraId, image format and size info
*
+ * @param cameraMode the working camera mode.
* @param cameraId the camera id of the camera device to transform the object
* @param imageFormat the image format info for the surface configuration object
* @param size the size info for the surface configuration object
@@ -149,7 +125,10 @@ public boolean checkSupported(
*/
@Nullable
@Override
- public SurfaceConfig transformSurfaceConfig(@NonNull String cameraId, int imageFormat,
+ public SurfaceConfig transformSurfaceConfig(
+ @CameraMode.Mode int cameraMode,
+ @NonNull String cameraId,
+ int imageFormat,
@NonNull Size size) {
SupportedSurfaceCombination supportedSurfaceCombination =
mCameraSupportedSurfaceCombinationMap.get(cameraId);
@@ -157,21 +136,28 @@ public SurfaceConfig transformSurfaceConfig(@NonNull String cameraId, int imageF
SurfaceConfig surfaceConfig = null;
if (supportedSurfaceCombination != null) {
surfaceConfig =
- supportedSurfaceCombination.transformSurfaceConfig(imageFormat, size);
+ supportedSurfaceCombination.transformSurfaceConfig(
+ cameraMode,
+ imageFormat,
+ size);
}
return surfaceConfig;
}
/**
- * Retrieves a map of suggested resolutions for the given list of use cases.
+ * Retrieves a map of suggested stream specifications for the given list of use cases.
*
- * @param cameraId the camera id of the camera device used by the use cases
- * @param existingSurfaces list of surfaces already configured and used by the camera. The
- * resolutions for these surface can not change.
- * @param newUseCaseConfigs list of configurations of the use cases that will be given a
- * suggested resolution
- * @return map of suggested resolutions for given use cases
+ * @param cameraMode the working camera mode.
+ * @param cameraId the camera id of the camera device used by the
+ * use cases
+ * @param existingSurfaces list of surfaces already configured and used by
+ * the camera. The stream specifications for these
+ * surface can not change.
+ * @param newUseCaseConfigsSupportedSizeMap map of configurations of the use cases to the
+ * supported sizes list that will be given a
+ * suggested stream specification
+ * @return map of suggested stream specifications for given use cases
* @throws IllegalStateException if not initialized
* @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
* there isn't a supported combination of surfaces
@@ -180,11 +166,14 @@ public SurfaceConfig transformSurfaceConfig(@NonNull String cameraId, int imageF
*/
@NonNull
@Override
- public Map, Size> getSuggestedResolutions(
+ public Pair