blob: 414ddb057b22dbf334400dea03c5bc2787e6370f [file] [log] [blame]
/*
* Copyright 2019 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.core.impl;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraDevice.StateCallback;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.stabilization.StabilizationMode;
import androidx.camera.core.internal.compat.workaround.SurfaceSorter;
import com.google.auto.value.AutoValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Configurations needed for a capture session.
*
* <p>The SessionConfig contains all the {@link android.hardware.camera2} parameters that are
* required to initialize a {@link android.hardware.camera2.CameraCaptureSession} and issue a {@link
* CaptureRequest}.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class SessionConfig {
public static final int DEFAULT_SESSION_TYPE = SessionConfiguration.SESSION_REGULAR;
/** The set of {@link OutputConfig} that data from the camera will be put into. */
private final List<OutputConfig> mOutputConfigs;
/** The {@link OutputConfig} for the postview. */
private final OutputConfig mPostviewOutputConfig;
/** The state callback for a {@link CameraDevice}. */
private final List<CameraDevice.StateCallback> mDeviceStateCallbacks;
/** The state callback for a {@link CameraCaptureSession}. */
private final List<CameraCaptureSession.StateCallback> mSessionStateCallbacks;
/** The callbacks used in single requests. */
private final List<CameraCaptureCallback> mSingleCameraCaptureCallbacks;
private final List<ErrorListener> mErrorListeners;
/** The configuration for building the {@link CaptureRequest} used for repeating requests. */
private final CaptureConfig mRepeatingCaptureConfig;
/** The type of the session */
private final int mSessionType;
/**
* Immutable class to store an input configuration that is used to create a reprocessable
* capture session.
*/
@Nullable
private InputConfiguration mInputConfiguration;
/**
* The output configuration associated with the {@link DeferrableSurface} that will be used to
* create the configuration needed to open a camera session. In camera2 this will be used to
* create the corresponding {@link android.hardware.camera2.params.OutputConfiguration}.
*/
@SuppressWarnings("AutoValueImmutableFields") // avoid extra dependency for ImmutableList.
@AutoValue
public abstract static class OutputConfig {
public static final int SURFACE_GROUP_ID_NONE = -1;
/**
* Returns the surface associated with the {@link OutputConfig}.
*/
@NonNull
public abstract DeferrableSurface getSurface();
/**
* Returns the shared surfaces. If non-empty, surface sharing will be enabled and the
* shared surfaces will share the same memory buffer as the main surface returned in
* {@link #getSurface()}.
*/
@NonNull
public abstract List<DeferrableSurface> getSharedSurfaces();
/**
* Returns the physical camera ID. By default it would be null. For cameras consisting of
* multiple physical cameras, this allows output to be redirected to specific physical
* camera.
*/
@Nullable
public abstract String getPhysicalCameraId();
/**
* Returns the surface group ID. Default value is {@link #SURFACE_GROUP_ID_NONE} meaning
* it doesn't belong to any surface group. A surface group ID is used to identify which
* surface group this output surface belongs to. Output streams with the same
* non-negative group ID won't receive the camera output simultaneously therefore it
* could reduce the overall memory footprint.
*/
public abstract int getSurfaceGroupId();
/**
* Returns the dynamic range for this output configuration.
*
* <p>The dynamic range will determine the dynamic range encoding and profile for pixels in
* the surfaces associated with this output configuration.
*
* <p>If not set, this defaults to {@link DynamicRange#SDR}.
*/
@NonNull
public abstract DynamicRange getDynamicRange();
/**
* Creates the {@link Builder} instance with specified {@link DeferrableSurface}.
*/
@NonNull
public static Builder builder(@NonNull DeferrableSurface surface) {
return new AutoValue_SessionConfig_OutputConfig.Builder()
.setSurface(surface)
.setSharedSurfaces(Collections.emptyList())
.setPhysicalCameraId(null)
.setSurfaceGroupId(SURFACE_GROUP_ID_NONE)
.setDynamicRange(DynamicRange.SDR);
}
/**
* Builder to create the {@link OutputConfig}
*/
@AutoValue.Builder
public abstract static class Builder {
/**
* Sets the surface associated with the {@link OutputConfig}.
*/
@NonNull
public abstract Builder setSurface(@NonNull DeferrableSurface surface);
/**
* Sets the shared surfaces. After being set, surface sharing will be enabled and the
* shared surfaces will share the same memory buffer as the main surface returned in
* {@link #getSurface()}.
*/
@NonNull
public abstract Builder setSharedSurfaces(@NonNull List<DeferrableSurface> surface);
/**
* Sets the physical camera ID. For cameras consisting of multiple physical cameras,
* this allows output to be redirected to specific physical camera.
*/
@NonNull
public abstract Builder setPhysicalCameraId(@Nullable String cameraId);
/**
* Sets the surface group ID. A surface group ID is used to identify which surface group
* this output surface belongs to. Output streams with the same non-negative group ID
* won't receive the camera output simultaneously therefore it could be used to reduce
* the overall memory footprint.
*/
@NonNull
public abstract Builder setSurfaceGroupId(int surfaceGroupId);
/**
* Returns the dynamic range for this output configuration.
*
* <p>The dynamic range will determine the dynamic range encoding and profile for
* pixels in the surfaces associated with this output configuration.
*/
@NonNull
public abstract Builder setDynamicRange(@NonNull DynamicRange dynamicRange);
/**
* Creates the instance.
*/
@NonNull
public abstract OutputConfig build();
}
}
/**
* Private constructor for a SessionConfig.
*
* <p>In practice, the {@link SessionConfig.BaseBuilder} will be used to construct a
* SessionConfig.
*
* @param outputConfigs The list of {@link OutputConfig} where data will be put into.
* @param deviceStateCallbacks The state callbacks for a {@link CameraDevice}.
* @param sessionStateCallbacks The state callbacks for a {@link CameraCaptureSession}.
* @param repeatingCaptureConfig The configuration for building the {@link CaptureRequest}.
* @param inputConfiguration The input configuration to create a reprocessable capture
* session.
* @param sessionType The session type for the {@link CameraCaptureSession}.
*/
SessionConfig(
List<OutputConfig> outputConfigs,
List<StateCallback> deviceStateCallbacks,
List<CameraCaptureSession.StateCallback> sessionStateCallbacks,
List<CameraCaptureCallback> singleCameraCaptureCallbacks,
List<ErrorListener> errorListeners,
CaptureConfig repeatingCaptureConfig,
@Nullable InputConfiguration inputConfiguration,
int sessionType,
@Nullable OutputConfig postviewOutputConfig) {
mOutputConfigs = outputConfigs;
mDeviceStateCallbacks = Collections.unmodifiableList(deviceStateCallbacks);
mSessionStateCallbacks = Collections.unmodifiableList(sessionStateCallbacks);
mSingleCameraCaptureCallbacks =
Collections.unmodifiableList(singleCameraCaptureCallbacks);
mErrorListeners = Collections.unmodifiableList(errorListeners);
mRepeatingCaptureConfig = repeatingCaptureConfig;
mInputConfiguration = inputConfiguration;
mSessionType = sessionType;
mPostviewOutputConfig = postviewOutputConfig;
}
/** Returns an instance of a session configuration with minimal configurations. */
@NonNull
public static SessionConfig defaultEmptySessionConfig() {
return new SessionConfig(
new ArrayList<OutputConfig>(),
new ArrayList<CameraDevice.StateCallback>(0),
new ArrayList<CameraCaptureSession.StateCallback>(0),
new ArrayList<CameraCaptureCallback>(0),
new ArrayList<>(0),
new CaptureConfig.Builder().build(),
/* inputConfiguration */ null,
DEFAULT_SESSION_TYPE,
/* postviewOutputConfig */ null);
}
@Nullable
public InputConfiguration getInputConfiguration() {
return mInputConfiguration;
}
/**
* Returns all {@link DeferrableSurface}s that are used to configure the session. It includes
* both the {@link DeferrableSurface} of the all {@link OutputConfig}s and its shared
* surfaces.
*/
@NonNull
public List<DeferrableSurface> getSurfaces() {
List<DeferrableSurface> deferrableSurfaces = new ArrayList<>();
for (OutputConfig outputConfig : mOutputConfigs) {
deferrableSurfaces.add(outputConfig.getSurface());
for (DeferrableSurface sharedSurface : outputConfig.getSharedSurfaces()) {
deferrableSurfaces.add(sharedSurface);
}
}
return Collections.unmodifiableList(deferrableSurfaces);
}
@NonNull
public List<OutputConfig> getOutputConfigs() {
return mOutputConfigs;
}
@Nullable
public OutputConfig getPostviewOutputConfig() {
return mPostviewOutputConfig;
}
@NonNull
public Config getImplementationOptions() {
return mRepeatingCaptureConfig.getImplementationOptions();
}
public int getTemplateType() {
return mRepeatingCaptureConfig.getTemplateType();
}
public int getSessionType() {
return mSessionType;
}
@NonNull
public Range<Integer> getExpectedFrameRateRange() {
return mRepeatingCaptureConfig.getExpectedFrameRateRange();
}
/** Obtains all registered {@link CameraDevice.StateCallback} callbacks. */
@NonNull
public List<CameraDevice.StateCallback> getDeviceStateCallbacks() {
return mDeviceStateCallbacks;
}
/** Obtains all registered {@link CameraCaptureSession.StateCallback} callbacks. */
@NonNull
public List<CameraCaptureSession.StateCallback> getSessionStateCallbacks() {
return mSessionStateCallbacks;
}
/** Obtains all registered {@link CameraCaptureCallback} callbacks for repeating requests. */
@NonNull
public List<CameraCaptureCallback> getRepeatingCameraCaptureCallbacks() {
return mRepeatingCaptureConfig.getCameraCaptureCallbacks();
}
/** Obtains all registered {@link ErrorListener} callbacks. */
@NonNull
public List<ErrorListener> getErrorListeners() {
return mErrorListeners;
}
/** Obtains all registered {@link CameraCaptureCallback} callbacks for single requests. */
@NonNull
public List<CameraCaptureCallback> getSingleCameraCaptureCallbacks() {
return mSingleCameraCaptureCallbacks;
}
@NonNull
public CaptureConfig getRepeatingCaptureConfig() {
return mRepeatingCaptureConfig;
}
public enum SessionError {
/**
* A {@link DeferrableSurface} contained in the config needs to be reset.
*
* <p>The surface is no longer valid, for example the surface has already been closed.
*/
SESSION_ERROR_SURFACE_NEEDS_RESET,
/** An unknown error has occurred. */
SESSION_ERROR_UNKNOWN
}
/**
* Callback for errors that occur when accessing the session config.
*/
public interface ErrorListener {
/**
* Called when an error has occurred.
*
* @param sessionConfig The {@link SessionConfig} that generated the error.
* @param error The error that was generated.
*/
void onError(@NonNull SessionConfig sessionConfig, @NonNull SessionError error);
}
/**
* Interface for unpacking a configuration into a SessionConfig.Builder
*
* <p>TODO(b/120949879): This will likely be removed once SessionConfig is refactored to
* remove camera2 dependencies.
*/
public interface OptionUnpacker {
/**
* Apply the options from the config onto the builder
*
* @param resolution the suggested resolution
* @param config the set of options to apply
* @param builder the builder on which to apply the options
*/
void unpack(
@NonNull Size resolution,
@NonNull UseCaseConfig<?> config,
@NonNull SessionConfig.Builder builder);
}
/**
* Base builder for easy modification/rebuilding of a {@link SessionConfig}.
*/
static class BaseBuilder {
// Use LinkedHashSet to preserve the adding order for bug fixing and testing.
final Set<OutputConfig> mOutputConfigs = new LinkedHashSet<>();
final CaptureConfig.Builder mCaptureConfigBuilder = new CaptureConfig.Builder();
final List<CameraDevice.StateCallback> mDeviceStateCallbacks = new ArrayList<>();
final List<CameraCaptureSession.StateCallback> mSessionStateCallbacks = new ArrayList<>();
final List<ErrorListener> mErrorListeners = new ArrayList<>();
final List<CameraCaptureCallback> mSingleCameraCaptureCallbacks = new ArrayList<>();
@Nullable
InputConfiguration mInputConfiguration;
int mSessionType = DEFAULT_SESSION_TYPE;
@Nullable
OutputConfig mPostviewOutputConfig;
}
/**
* Builder for easy modification/rebuilding of a {@link SessionConfig}.
*/
public static class Builder extends BaseBuilder {
/**
* Creates a {@link Builder} from a {@link UseCaseConfig}.
*
* <p>Populates the builder with all the properties defined in the base configuration.
*/
@NonNull
public static Builder createFrom(
@NonNull UseCaseConfig<?> config,
@NonNull Size resolution) {
OptionUnpacker unpacker = config.getSessionOptionUnpacker(null);
if (unpacker == null) {
throw new IllegalStateException(
"Implementation is missing option unpacker for "
+ config.getTargetName(config.toString()));
}
Builder builder = new Builder();
// Unpack the configuration into this builder
unpacker.unpack(resolution, config, builder);
return builder;
}
/**
* Set the input configuration for reprocessable capture session.
*
* @param inputConfiguration The input configuration.
*/
@NonNull
public Builder setInputConfiguration(@Nullable InputConfiguration inputConfiguration) {
mInputConfiguration = inputConfiguration;
return this;
}
/**
* Set the template characteristics of the SessionConfig.
*
* @param templateType Template constant that must match those defined by {@link
* CameraDevice}
* <p>TODO(b/120949879): This is camera2 implementation detail that
* should be moved
*/
@NonNull
public Builder setTemplateType(int templateType) {
mCaptureConfigBuilder.setTemplateType(templateType);
return this;
}
/**
* Sets the session type.
*/
@NonNull
public Builder setSessionType(int sessionType) {
mSessionType = sessionType;
return this;
}
/**
* Set the expected frame rate range of the SessionConfig.
*
* @param expectedFrameRateRange The frame rate range calculated from the UseCases for
* {@link CameraDevice}
*/
@NonNull
public Builder setExpectedFrameRateRange(@NonNull Range<Integer> expectedFrameRateRange) {
mCaptureConfigBuilder.setExpectedFrameRateRange(expectedFrameRateRange);
return this;
}
/**
* Set the preview stabilization mode of the SessionConfig.
* @param mode {@link StabilizationMode}
*/
@NonNull
public Builder setPreviewStabilization(@StabilizationMode.Mode int mode) {
if (mode != StabilizationMode.UNSPECIFIED) {
mCaptureConfigBuilder.setPreviewStabilization(mode);
}
return this;
}
/**
* Set the video stabilization mode of the SessionConfig.
* @param mode {@link StabilizationMode}
*/
@NonNull
public Builder setVideoStabilization(@StabilizationMode.Mode int mode) {
if (mode != StabilizationMode.UNSPECIFIED) {
mCaptureConfigBuilder.setVideoStabilization(mode);
}
return this;
}
/**
* Adds a tag to the SessionConfig with a key. For tracking the source.
*/
@NonNull
public Builder addTag(@NonNull String key, @NonNull Object tag) {
mCaptureConfigBuilder.addTag(key, tag);
return this;
}
/**
* Adds a {@link CameraDevice.StateCallback} callback.
*/
// TODO(b/120949879): This is camera2 implementation detail that should be moved
@NonNull
public Builder addDeviceStateCallback(
@NonNull CameraDevice.StateCallback deviceStateCallback) {
if (mDeviceStateCallbacks.contains(deviceStateCallback)) {
return this;
}
mDeviceStateCallbacks.add(deviceStateCallback);
return this;
}
/**
* Adds all {@link CameraDevice.StateCallback} callbacks.
*/
@NonNull
public Builder addAllDeviceStateCallbacks(
@NonNull Collection<CameraDevice.StateCallback> deviceStateCallbacks) {
for (CameraDevice.StateCallback callback : deviceStateCallbacks) {
addDeviceStateCallback(callback);
}
return this;
}
/**
* Adds a {@link CameraCaptureSession.StateCallback} callback.
*/
// TODO(b/120949879): This is camera2 implementation detail that should be moved
@NonNull
public Builder addSessionStateCallback(
@NonNull CameraCaptureSession.StateCallback sessionStateCallback) {
if (mSessionStateCallbacks.contains(sessionStateCallback)) {
return this;
}
mSessionStateCallbacks.add(sessionStateCallback);
return this;
}
/**
* Adds all {@link CameraCaptureSession.StateCallback} callbacks.
*/
@NonNull
public Builder addAllSessionStateCallbacks(
@NonNull List<CameraCaptureSession.StateCallback> sessionStateCallbacks) {
for (CameraCaptureSession.StateCallback callback : sessionStateCallbacks) {
addSessionStateCallback(callback);
}
return this;
}
/**
* Adds a {@link CameraCaptureCallback} callback for repeating requests.
* <p>This callback does not call for single requests.
*/
@NonNull
public Builder addRepeatingCameraCaptureCallback(
@NonNull CameraCaptureCallback cameraCaptureCallback) {
mCaptureConfigBuilder.addCameraCaptureCallback(cameraCaptureCallback);
return this;
}
/**
* Adds all {@link CameraCaptureCallback} callbacks.
* <p>These callbacks do not call for single requests.
*/
@NonNull
public Builder addAllRepeatingCameraCaptureCallbacks(
@NonNull Collection<CameraCaptureCallback> cameraCaptureCallbacks) {
mCaptureConfigBuilder.addAllCameraCaptureCallbacks(cameraCaptureCallbacks);
return this;
}
/**
* Adds a {@link CameraCaptureCallback} callback for single and repeating requests.
* <p>Listeners added here are available in both the
* {@link #getRepeatingCameraCaptureCallbacks()} and
* {@link #getSingleCameraCaptureCallbacks()} methods.
*/
@NonNull
public Builder addCameraCaptureCallback(
@NonNull CameraCaptureCallback cameraCaptureCallback) {
mCaptureConfigBuilder.addCameraCaptureCallback(cameraCaptureCallback);
if (!mSingleCameraCaptureCallbacks.contains(cameraCaptureCallback)) {
mSingleCameraCaptureCallbacks.add(cameraCaptureCallback);
}
return this;
}
/**
* Adds all {@link CameraCaptureCallback} callbacks for single and repeating requests.
* <p>Listeners added here are available in both the
* {@link #getRepeatingCameraCaptureCallbacks()} and
* {@link #getSingleCameraCaptureCallbacks()} methods.
*/
@NonNull
public Builder addAllCameraCaptureCallbacks(
@NonNull Collection<CameraCaptureCallback> cameraCaptureCallbacks) {
for (CameraCaptureCallback c : cameraCaptureCallbacks) {
mCaptureConfigBuilder.addCameraCaptureCallback(c);
if (!mSingleCameraCaptureCallbacks.contains(c)) {
mSingleCameraCaptureCallbacks.add(c);
}
}
return this;
}
/**
* Removes a previously added {@link CameraCaptureCallback} callback for single and/or
* repeating requests.
*
* @param cameraCaptureCallback The callback to remove.
* @return {@code true} if the callback was successfully removed. {@code false} if the
* callback wasn't present in this builder.
*/
public boolean removeCameraCaptureCallback(
@NonNull CameraCaptureCallback cameraCaptureCallback) {
boolean removedFromRepeating =
mCaptureConfigBuilder.removeCameraCaptureCallback(cameraCaptureCallback);
boolean removedFromSingle =
mSingleCameraCaptureCallbacks.remove(cameraCaptureCallback);
return removedFromRepeating || removedFromSingle;
}
/** Obtain all {@link CameraCaptureCallback} callbacks for single requests. */
@NonNull
public List<CameraCaptureCallback> getSingleCameraCaptureCallbacks() {
return Collections.unmodifiableList(mSingleCameraCaptureCallbacks);
}
/**
* Adds all {@link ErrorListener} listeners repeating requests.
*/
@NonNull
public Builder addErrorListener(@NonNull ErrorListener errorListener) {
mErrorListeners.add(errorListener);
return this;
}
/**
* Add a surface to the set that the session repeatedly writes data to.
*
* <p>The dynamic range of this surface will default to {@link DynamicRange#SDR}. To
* manually set the dynamic range, use {@link #addSurface(DeferrableSurface, DynamicRange)}.
*/
@NonNull
public Builder addSurface(@NonNull DeferrableSurface surface) {
return addSurface(surface, DynamicRange.SDR, null);
}
/**
* Add a surface with the provided dynamic range to the set that the session repeatedly
* writes data to.
*/
@NonNull
public Builder addSurface(@NonNull DeferrableSurface surface,
@NonNull DynamicRange dynamicRange,
@Nullable String physicalCameraId) {
OutputConfig outputConfig = OutputConfig.builder(surface)
.setPhysicalCameraId(physicalCameraId)
.setDynamicRange(dynamicRange)
.build();
mOutputConfigs.add(outputConfig);
mCaptureConfigBuilder.addSurface(surface);
return this;
}
/**
* Adds an {@link OutputConfig} to create the capture session with. The surface set in
* the {@link OutputConfig} will be added to the repeating request.
*/
@NonNull
public Builder addOutputConfig(@NonNull OutputConfig outputConfig) {
mOutputConfigs.add(outputConfig);
mCaptureConfigBuilder.addSurface(outputConfig.getSurface());
for (DeferrableSurface sharedSurface : outputConfig.getSharedSurfaces()) {
mCaptureConfigBuilder.addSurface(sharedSurface);
}
return this;
}
/**
* Add a surface for the session which only used for single captures.
*
* <p>The dynamic range of this surface will default to {@link DynamicRange#SDR}. To
* manually set the dynamic range, use
* {@link #addNonRepeatingSurface(DeferrableSurface, DynamicRange)}.
*/
@NonNull
public Builder addNonRepeatingSurface(@NonNull DeferrableSurface surface) {
return addNonRepeatingSurface(surface, DynamicRange.SDR);
}
/**
* Add a surface with the provided dynamic range for the session which only used for
* single captures.
*/
@NonNull
public Builder addNonRepeatingSurface(@NonNull DeferrableSurface surface,
@NonNull DynamicRange dynamicRange) {
OutputConfig outputConfig = OutputConfig.builder(surface)
.setDynamicRange(dynamicRange)
.build();
mOutputConfigs.add(outputConfig);
return this;
}
/**
* Sets the postview surface.
*/
@NonNull
public Builder setPostviewSurface(@NonNull DeferrableSurface surface) {
mPostviewOutputConfig = OutputConfig.builder(surface).build();
return this;
}
/** Remove a surface from the set which the session repeatedly writes to. */
@NonNull
public Builder removeSurface(@NonNull DeferrableSurface surface) {
OutputConfig outputConfigToRemove = null;
for (OutputConfig config : mOutputConfigs) {
if (config.getSurface().equals(surface)) {
outputConfigToRemove = config;
break;
}
}
if (outputConfigToRemove != null) {
mOutputConfigs.remove(outputConfigToRemove);
}
mCaptureConfigBuilder.removeSurface(surface);
return this;
}
/** Clears all surfaces from the set which the session writes to. */
@NonNull
public Builder clearSurfaces() {
mOutputConfigs.clear();
mCaptureConfigBuilder.clearSurfaces();
return this;
}
/** Set the {@link Config} for options that are implementation specific. */
@NonNull
public Builder setImplementationOptions(@NonNull Config config) {
mCaptureConfigBuilder.setImplementationOptions(config);
return this;
}
/** Add a set of {@link Config} to the implementation specific options. */
@NonNull
public Builder addImplementationOptions(@NonNull Config config) {
mCaptureConfigBuilder.addImplementationOptions(config);
return this;
}
/**
* Builds an instance of a SessionConfig that has all the combined parameters of the
* SessionConfig that have been added to the Builder.
*/
@NonNull
public SessionConfig build() {
return new SessionConfig(
new ArrayList<>(mOutputConfigs),
new ArrayList<>(mDeviceStateCallbacks),
new ArrayList<>(mSessionStateCallbacks),
new ArrayList<>(mSingleCameraCaptureCallbacks),
new ArrayList<>(mErrorListeners),
mCaptureConfigBuilder.build(),
mInputConfiguration,
mSessionType,
mPostviewOutputConfig);
}
}
/**
* Builder for combining multiple instances of {@link SessionConfig}. This will check if all
* the parameters for the {@link SessionConfig} are compatible with each other
*/
public static final class ValidatingBuilder extends BaseBuilder {
// Current supported session template values and the bigger index in the list, the
// priority is higher.
private static final List<Integer> SUPPORTED_TEMPLATE_PRIORITY = Arrays.asList(
CameraDevice.TEMPLATE_PREVIEW,
// TODO(230673983): Based on the framework assumptions, we prioritize video capture
// and disable ZSL (fallback to regular) if both use cases are bound.
CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
CameraDevice.TEMPLATE_RECORD
);
private static final String TAG = "ValidatingBuilder";
private final SurfaceSorter mSurfaceSorter = new SurfaceSorter();
private boolean mValid = true;
private boolean mTemplateSet = false;
/**
* Add an implementation option to the ValidatingBuilder's CaptureConfigBuilder. If it
* already has an option with the same key, write it over.
*/
public <T> void addImplementationOption(@NonNull Config.Option<T> option,
@NonNull T value) {
mCaptureConfigBuilder.addImplementationOption(option, value);
}
/**
* Add the SessionConfig to the set of SessionConfig that have been aggregated by the
* ValidatingBuilder
*/
public void add(@NonNull SessionConfig sessionConfig) {
CaptureConfig captureConfig = sessionConfig.getRepeatingCaptureConfig();
// Check template
if (captureConfig.getTemplateType() != CaptureConfig.TEMPLATE_TYPE_NONE) {
mTemplateSet = true;
mCaptureConfigBuilder.setTemplateType(
selectTemplateType(captureConfig.getTemplateType(),
mCaptureConfigBuilder.getTemplateType()));
}
setOrVerifyExpectFrameRateRange(captureConfig.getExpectedFrameRateRange());
setPreviewStabilizationMode(captureConfig.getPreviewStabilizationMode());
setVideoStabilizationMode(captureConfig.getVideoStabilizationMode());
TagBundle tagBundle = sessionConfig.getRepeatingCaptureConfig().getTagBundle();
mCaptureConfigBuilder.addAllTags(tagBundle);
// Check device state callbacks
mDeviceStateCallbacks.addAll(sessionConfig.getDeviceStateCallbacks());
// Check session state callbacks
mSessionStateCallbacks.addAll(sessionConfig.getSessionStateCallbacks());
// Check camera capture callbacks for repeating requests.
mCaptureConfigBuilder.addAllCameraCaptureCallbacks(
sessionConfig.getRepeatingCameraCaptureCallbacks());
// Check camera capture callbacks for single requests.
mSingleCameraCaptureCallbacks.addAll(sessionConfig.getSingleCameraCaptureCallbacks());
mErrorListeners.addAll(sessionConfig.getErrorListeners());
// Check input configuration for reprocessable capture session.
if (sessionConfig.getInputConfiguration() != null) {
mInputConfiguration = sessionConfig.getInputConfiguration();
}
// Check surfaces
mOutputConfigs.addAll(sessionConfig.getOutputConfigs());
// Check capture request surfaces
mCaptureConfigBuilder.getSurfaces().addAll(captureConfig.getSurfaces());
if (!getSurfaces().containsAll(mCaptureConfigBuilder.getSurfaces())) {
String errorMessage =
"Invalid configuration due to capture request surfaces are not a subset "
+ "of surfaces";
Logger.d(TAG, errorMessage);
mValid = false;
}
if (sessionConfig.getSessionType() != mSessionType
&& sessionConfig.getSessionType() != DEFAULT_SESSION_TYPE
&& mSessionType != DEFAULT_SESSION_TYPE) {
String errorMessage =
"Invalid configuration due to that two non-default session types are set";
Logger.d(TAG, errorMessage);
mValid = false;
} else {
if (sessionConfig.getSessionType() != DEFAULT_SESSION_TYPE) {
mSessionType = sessionConfig.getSessionType();
}
}
if (sessionConfig.mPostviewOutputConfig != null) {
if (mPostviewOutputConfig != sessionConfig.mPostviewOutputConfig
&& mPostviewOutputConfig != null) {
String errorMessage =
"Invalid configuration due to that two different postview output "
+ "configs are set";
Logger.d(TAG, errorMessage);
mValid = false;
} else {
mPostviewOutputConfig = sessionConfig.mPostviewOutputConfig;
}
}
// The conflicting of options is handled in addImplementationOptions where it could
// throw an IllegalArgumentException if the conflict cannot be resolved.
mCaptureConfigBuilder.addImplementationOptions(
captureConfig.getImplementationOptions());
}
private void setOrVerifyExpectFrameRateRange(
@NonNull Range<Integer> expectedFrameRateRange) {
if (expectedFrameRateRange.equals(StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
return;
}
if (mCaptureConfigBuilder.getExpectedFrameRateRange().equals(
StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
mCaptureConfigBuilder.setExpectedFrameRateRange(expectedFrameRateRange);
return;
}
if (!mCaptureConfigBuilder.getExpectedFrameRateRange().equals(expectedFrameRateRange)) {
mValid = false;
Logger.d(TAG, "Different ExpectedFrameRateRange values");
}
}
private void setPreviewStabilizationMode(@StabilizationMode.Mode int mode) {
if (mode != StabilizationMode.UNSPECIFIED) {
mCaptureConfigBuilder.setPreviewStabilization(mode);
}
}
private void setVideoStabilizationMode(@StabilizationMode.Mode int mode) {
if (mode != StabilizationMode.UNSPECIFIED) {
mCaptureConfigBuilder.setVideoStabilization(mode);
}
}
private List<DeferrableSurface> getSurfaces() {
List<DeferrableSurface> surfaces = new ArrayList<>();
for (OutputConfig outputConfig : mOutputConfigs) {
surfaces.add(outputConfig.getSurface());
for (DeferrableSurface sharedSurface : outputConfig.getSharedSurfaces()) {
surfaces.add(sharedSurface);
}
}
return surfaces;
}
/** Clears all surfaces from the set which the session writes to. */
public void clearSurfaces() {
mOutputConfigs.clear();
mCaptureConfigBuilder.clearSurfaces();
}
/** Check if the set of SessionConfig that have been combined are valid */
public boolean isValid() {
return mTemplateSet && mValid;
}
/**
* Builds an instance of a SessionConfig that has all the combined parameters of the
* SessionConfig that have been added to the ValidatingBuilder.
*/
@NonNull
public SessionConfig build() {
if (!mValid) {
throw new IllegalArgumentException("Unsupported session configuration combination");
}
List<OutputConfig> outputConfigs = new ArrayList<>(mOutputConfigs);
mSurfaceSorter.sort(outputConfigs);
return new SessionConfig(
outputConfigs,
new ArrayList<>(mDeviceStateCallbacks),
new ArrayList<>(mSessionStateCallbacks),
new ArrayList<>(mSingleCameraCaptureCallbacks),
new ArrayList<>(mErrorListeners),
mCaptureConfigBuilder.build(),
mInputConfiguration,
mSessionType,
mPostviewOutputConfig);
}
private int selectTemplateType(int type1, int type2) {
return SUPPORTED_TEMPLATE_PRIORITY.indexOf(type1)
>= SUPPORTED_TEMPLATE_PRIORITY.indexOf(type2) ? type1 : type2;
}
}
}