/* * Copyright (C) 2015 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.params; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.ImageFormat; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import android.util.Size; import android.view.Surface; import static com.android.internal.util.Preconditions.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A class for describing camera output, which contains a {@link Surface} and its specific * configuration for creating capture session. * *
There are several ways to instantiate, modify and use OutputConfigurations. The most common * and recommended usage patterns are summarized in the following list:
*As of {@link android.os.Build.VERSION_CODES#P Android P}, all formats can be used for * sharing, subject to device support. On prior API levels, only {@link ImageFormat#PRIVATE} * format may be used.
* * @see CameraDevice#createCaptureSessionByOutputConfigurations * */ public final class OutputConfiguration implements Parcelable { /** * Rotation constant: 0 degree rotation (no rotation) * * @hide */ @SystemApi public static final int ROTATION_0 = 0; /** * Rotation constant: 90 degree counterclockwise rotation. * * @hide */ @SystemApi public static final int ROTATION_90 = 1; /** * Rotation constant: 180 degree counterclockwise rotation. * * @hide */ @SystemApi public static final int ROTATION_180 = 2; /** * Rotation constant: 270 degree counterclockwise rotation. * * @hide */ @SystemApi public static final int ROTATION_270 = 3; /** * Invalid surface group ID. * *An {@link OutputConfiguration} with this value indicates that the included surface *doesn't belong to any surface group.
*/ public static final int SURFACE_GROUP_ID_NONE = -1; /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}. * * @param surface * A Surface for camera to output to. * *This constructor creates a default configuration, with a surface group ID of * {@value #SURFACE_GROUP_ID_NONE}.
* */ public OutputConfiguration(@NonNull Surface surface) { this(SURFACE_GROUP_ID_NONE, surface, ROTATION_0); } /** * Unknown surface source type. */ private final int SURFACE_TYPE_UNKNOWN = -1; /** * The surface is obtained from {@link android.view.SurfaceView}. */ private final int SURFACE_TYPE_SURFACE_VIEW = 0; /** * The surface is obtained from {@link android.graphics.SurfaceTexture}. */ private final int SURFACE_TYPE_SURFACE_TEXTURE = 1; /** * Maximum number of surfaces supported by one {@link OutputConfiguration}. * *The combined number of surfaces added by the constructor and * {@link OutputConfiguration#addSurface} should not exceed this value.
* */ private static final int MAX_SURFACES_COUNT = 4; /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}, * with a surface group ID. * ** A surface group ID is used to identify which surface group this output surface belongs to. A * surface group is a group of output surfaces that are not intended to receive camera output * buffer streams simultaneously. The {@link CameraDevice} may be able to share the buffers used * by all the surfaces from the same surface group, therefore may reduce the overall memory * footprint. The application should only set the same set ID for the streams that are not * simultaneously streaming. A negative ID indicates that this surface doesn't belong to any * surface group. The default value is {@value #SURFACE_GROUP_ID_NONE}.
* *For example, a video chat application that has an adaptive output resolution feature would * need two (or more) output resolutions, to switch resolutions without any output glitches. * However, at any given time, only one output is active to minimize outgoing network bandwidth * and encoding overhead. To save memory, the application should set the video outputs to have * the same non-negative group ID, so that the camera device can share the same memory region * for the alternating outputs.
* *It is not an error to include output streams with the same group ID in the same capture * request, but the resulting memory consumption may be higher than if the two streams were * not in the same surface group to begin with, especially if the outputs have substantially * different dimensions.
* * @param surfaceGroupId * A group ID for this output, used for sharing memory between multiple outputs. * @param surface * A Surface for camera to output to. * */ public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface) { this(surfaceGroupId, surface, ROTATION_0); } /** * Create a new {@link OutputConfiguration} instance. * *This constructor takes an argument for desired camera rotation
* * @param surface * A Surface for camera to output to. * @param rotation * The desired rotation to be applied on camera output. Value must be one of * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees, * application should make sure corresponding surface size has width and height * transposed relative to the width and height without rotation. For example, * if application needs camera to capture 1280x720 picture and rotate it by 90 degree, * application should set rotation to {@code ROTATION_90} and make sure the * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might * throw {@code IllegalArgumentException} if device cannot perform such rotation. * @hide */ @SystemApi public OutputConfiguration(@NonNull Surface surface, int rotation) { this(SURFACE_GROUP_ID_NONE, surface, rotation); } /** * Create a new {@link OutputConfiguration} instance, with rotation and a group ID. * *This constructor takes an argument for desired camera rotation and for the surface group * ID. See {@link #OutputConfiguration(int, Surface)} for details of the group ID.
* * @param surfaceGroupId * A group ID for this output, used for sharing memory between multiple outputs. * @param surface * A Surface for camera to output to. * @param rotation * The desired rotation to be applied on camera output. Value must be one of * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees, * application should make sure corresponding surface size has width and height * transposed relative to the width and height without rotation. For example, * if application needs camera to capture 1280x720 picture and rotate it by 90 degree, * application should set rotation to {@code ROTATION_90} and make sure the * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might * throw {@code IllegalArgumentException} if device cannot perform such rotation. * @hide */ @SystemApi public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) { checkNotNull(surface, "Surface must not be null"); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); mSurfaceGroupId = surfaceGroupId; mSurfaceType = SURFACE_TYPE_UNKNOWN; mSurfaces = new ArrayList* This constructor takes an argument for desired Surface size and the Surface source class * without providing the actual output Surface. This is used to setup an output configuration * with a deferred Surface. The application can use this output configuration to create a * session. *
** However, the actual output Surface must be set via {@link #addSurface} and the deferred * Surface configuration must be finalized via {@link * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this * Surface target. The deferred Surface can only be obtained either from {@link * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from * {@link android.graphics.SurfaceTexture} via * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). *
* * @param surfaceSize Size for the deferred surface. * @param klass a non-{@code null} {@link Class} object reference that indicates the source of * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and * {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported. * @throws IllegalArgumentException if the Surface source class is not supported, or Surface * size is zero. */ publicFor advanced use cases, a camera application may require more streams than the combination * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one * compatible surface can be attached to an OutputConfiguration so that they map to one * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing * clients should be careful when adding surface outputs that modify their input data. If such * case exists, camera clients should have an additional mechanism to synchronize read and write * access between individual consumers.
* *Two surfaces are compatible in the below cases:
* *To enable surface sharing, this function must be called before {@link * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function after * {@link CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.
* *Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration. * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, * MediaRecorder, MediaCodec, or implementation defined ImageReader.
*/ public void enableSurfaceSharing() { mIsShared = true; } /** * Set the id of the physical camera for this OutputConfiguration * *In the case one logical camera is made up of multiple physical cameras, it could be * desirable for the camera application to request streams from individual physical cameras. * This call achieves it by mapping the OutputConfiguration to the physical camera id.
* *The valid physical camera ids can be queried by {@link * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. *
* *Passing in a null physicalCameraId means that the OutputConfiguration is for a logical * stream.
* *This function must be called before {@link * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.
* *The surface belonging to a physical camera OutputConfiguration must not be used as input * or output of a reprocessing request.
*/ public void setPhysicalCameraId(@Nullable String physicalCameraId) { mPhysicalCameraId = physicalCameraId; } /** * Check if this configuration is for a physical camera. * *This returns true if the output configuration was for a physical camera making up a * logical multi camera via {@link OutputConfiguration#setPhysicalCameraId}.
* @hide */ public boolean isForPhysicalCamera() { return (mPhysicalCameraId != null); } /** * Check if this configuration has deferred configuration. * *This will return true if the output configuration was constructed with surface deferred by * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after * the deferred surface is added later by {@link OutputConfiguration#addSurface}.
* * @return true if this configuration has deferred surface. * @hide */ public boolean isDeferredConfiguration() { return mIsDeferredConfig; } /** * Add a surface to this OutputConfiguration. * *This function can be called before or after {@link * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after, * the application must finalize the capture session with * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method * after the output configurations have been finalized only in cases of enabled surface sharing * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with * {@link CameraCaptureSession#updateOutputConfiguration}.
* *If the OutputConfiguration was constructed with a deferred surface by {@link * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, * or from {@link android.graphics.SurfaceTexture} via * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
* *If the OutputConfiguration was constructed by other constructors, the added * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for * details of compatible surfaces.
* *If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must * be called before calling this function to add a new Surface.
* * @param surface The surface to be added. * @throws IllegalArgumentException if the Surface is invalid, the Surface's * dataspace/format doesn't match, or adding the Surface would exceed number of * shared surfaces supported. * @throws IllegalStateException if the Surface was already added to this OutputConfiguration, * or if the OutputConfiguration is not shared and it already has a surface associated * with it. */ public void addSurface(@NonNull Surface surface) { checkNotNull(surface, "Surface must not be null"); if (mSurfaces.contains(surface)) { throw new IllegalStateException("Surface is already added!"); } if (mSurfaces.size() == 1 && !mIsShared) { throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration"); } if (mSurfaces.size() + 1 > MAX_SURFACES_COUNT) { throw new IllegalArgumentException("Exceeds maximum number of surfaces"); } // This will throw IAE is the surface was abandoned. Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); if (!surfaceSize.equals(mConfiguredSize)) { Log.w(TAG, "Added surface size " + surfaceSize + " is different than pre-configured size " + mConfiguredSize + ", the pre-configured size will be used."); } if (mConfiguredFormat != SurfaceUtils.getSurfaceFormat(surface)) { throw new IllegalArgumentException("The format of added surface format doesn't match"); } // If the surface format is PRIVATE, do not enforce dataSpace because camera device may // override it. if (mConfiguredFormat != ImageFormat.PRIVATE && mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) { throw new IllegalArgumentException("The dataspace of added surface doesn't match"); } mSurfaces.add(surface); } /** * Remove a surface from this OutputConfiguration. * *Surfaces added via calls to {@link #addSurface} can also be removed from the * OutputConfiguration. The only notable exception is the surface associated with * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor * or was added first in the deferred case * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.
* * @param surface The surface to be removed. * * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration * (see {@link #getSurface}) or the surface didn't get added * with {@link #addSurface}. */ public void removeSurface(@NonNull Surface surface) { if (getSurface() == surface) { throw new IllegalArgumentException( "Cannot remove surface associated with this output configuration"); } if (!mSurfaces.remove(surface)) { throw new IllegalArgumentException("Surface is not part of this output configuration"); } } /** * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration} * instance. * * @param other Another {@link OutputConfiguration} instance to be copied. * * @hide */ public OutputConfiguration(@NonNull OutputConfiguration other) { if (other == null) { throw new IllegalArgumentException("OutputConfiguration shouldn't be null"); } this.mSurfaces = other.mSurfaces; this.mRotation = other.mRotation; this.mSurfaceGroupId = other.mSurfaceGroupId; this.mSurfaceType = other.mSurfaceType; this.mConfiguredDataspace = other.mConfiguredDataspace; this.mConfiguredFormat = other.mConfiguredFormat; this.mConfiguredSize = other.mConfiguredSize; this.mConfiguredGenerationId = other.mConfiguredGenerationId; this.mIsDeferredConfig = other.mIsDeferredConfig; this.mIsShared = other.mIsShared; this.mPhysicalCameraId = other.mPhysicalCameraId; } /** * Create an OutputConfiguration from Parcel. */ private OutputConfiguration(@NonNull Parcel source) { int rotation = source.readInt(); int surfaceSetId = source.readInt(); int surfaceType = source.readInt(); int width = source.readInt(); int height = source.readInt(); boolean isDeferred = source.readInt() == 1; boolean isShared = source.readInt() == 1; ArrayListTwo output configurations are only equal if and only if the underlying surfaces, surface * properties (width, height, format, dataspace) when the output configurations are created, * and all other configuration parameters are equal.
* * @return {@code true} if the objects were equal, {@code false} otherwise */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (this == obj) { return true; } else if (obj instanceof OutputConfiguration) { final OutputConfiguration other = (OutputConfiguration) obj; if (mRotation != other.mRotation || !mConfiguredSize.equals(other.mConfiguredSize) || mConfiguredFormat != other.mConfiguredFormat || mSurfaceGroupId != other.mSurfaceGroupId || mSurfaceType != other.mSurfaceType || mIsDeferredConfig != other.mIsDeferredConfig || mIsShared != other.mIsShared || mConfiguredFormat != other.mConfiguredFormat || mConfiguredDataspace != other.mConfiguredDataspace || mConfiguredGenerationId != other.mConfiguredGenerationId) return false; int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size()); for (int i = 0; i < minLen; i++) { if (mSurfaces.get(i) != other.mSurfaces.get(i)) return false; } return true; } return false; } /** * {@inheritDoc} */ @Override public int hashCode() { // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise // the deferred output configuration will be lost in the camera streammap after the deferred // surface is set. if (mIsDeferredConfig) { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } return HashCodeHelpers.hashCode( mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } private static final String TAG = "OutputConfiguration"; private ArrayList