blob: 20d9c30bb4ccef2701cef7fece1c69d75dc7989f [file] [log] [blame]
/*
* Copyright (C) 2018 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 static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat;
import static com.android.internal.util.Preconditions.*;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.ImageFormat;
import android.graphics.ImageFormat.Format;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.media.CamcorderProfile;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
/**
* Immutable class to store the available mandatory stream combination.
*
* <p>A mandatory stream combination refers to a specific entry in the documented sets of
* required stream {@link CameraDevice#createCaptureSession combinations}.
* These combinations of streams are required to be supported by the camera device.
*
* <p>The list of stream combinations is available by invoking
* {@link CameraCharacteristics#get} and passing key
* {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS}.</p>
*/
public final class MandatoryStreamCombination {
private static final String TAG = "MandatoryStreamCombination";
/**
* Immutable class to store available mandatory stream information.
*/
public static final class MandatoryStreamInformation {
private final int mFormat;
private final ArrayList<Size> mAvailableSizes = new ArrayList<Size> ();
private final boolean mIsInput;
/**
* Create a new {@link MandatoryStreamInformation}.
*
@param availableSizes List of possible stream sizes.
* @param format Image format.
*
* @throws IllegalArgumentException
* if sizes is empty or if the format was not user-defined in
* ImageFormat/PixelFormat.
* @hide
*/
public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format) {
this(availableSizes, format, /*isInput*/false);
}
/**
* Create a new {@link MandatoryStreamInformation}.
*
@param availableSizes List of possible stream sizes.
* @param format Image format.
* @param isInput Flag indicating whether this stream is input.
*
* @throws IllegalArgumentException
* if sizes is empty or if the format was not user-defined in
* ImageFormat/PixelFormat.
* @hide
*/
public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format,
boolean isInput) {
if (availableSizes.isEmpty()) {
throw new IllegalArgumentException("No available sizes");
}
mAvailableSizes.addAll(availableSizes);
mFormat = checkArgumentFormat(format);
mIsInput = isInput;
}
/**
* Confirms whether or not this is an input stream.
* @return true in case the stream is input, false otherwise.
*/
public boolean isInput() {
return mIsInput;
}
/**
* Return the list of available sizes for this mandatory stream.
*
* <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest
* resolution in the result will be tested and guaranteed to work. If clients want to use
* smaller sizes, then the resulting
* {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
* be tested either by calling {@link CameraDevice#createCaptureSession} or
* {@link CameraDevice#isSessionConfigurationSupported}.
*
* @return non-modifiable ascending list of available sizes.
*/
public @NonNull List<Size> getAvailableSizes() {
return Collections.unmodifiableList(mAvailableSizes);
}
/**
* Retrieve the mandatory stream {@code format}.
*
* @return integer format.
*/
public @Format int getFormat() {
return mFormat;
}
/**
* Check if this {@link MandatoryStreamInformation} is equal to another
* {@link MandatoryStreamInformation}.
*
* <p>Two vectors are only equal if and only if each of the respective elements is
* equal.</p>
*
* @return {@code true} if the objects were equal, {@code false} otherwise
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof MandatoryStreamInformation) {
final MandatoryStreamInformation other = (MandatoryStreamInformation) obj;
if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) ||
(mAvailableSizes.size() != other.mAvailableSizes.size())) {
return false;
}
return mAvailableSizes.equals(other.mAvailableSizes);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput),
mAvailableSizes.hashCode());
}
}
private final String mDescription;
private final boolean mIsReprocessable;
private final ArrayList<MandatoryStreamInformation> mStreamsInformation =
new ArrayList<MandatoryStreamInformation>();
/**
* Create a new {@link MandatoryStreamCombination}.
*
* @param streamsInformation list of available streams in the stream combination.
* @param description Summary of the stream combination use case.
* @param isReprocessable Flag whether the mandatory stream combination is reprocessable.
*
* @throws IllegalArgumentException
* if stream information is empty
* @hide
*/
public MandatoryStreamCombination(@NonNull List<MandatoryStreamInformation> streamsInformation,
@NonNull String description, boolean isReprocessable) {
if (streamsInformation.isEmpty()) {
throw new IllegalArgumentException("Empty stream information");
}
mStreamsInformation.addAll(streamsInformation);
mDescription = description;
mIsReprocessable = isReprocessable;
}
/**
* Get the mandatory stream combination description.
*
* @return CharSequence with the mandatory combination description.
*/
public @NonNull CharSequence getDescription() {
return mDescription;
}
/**
* Indicates whether the mandatory stream combination is reprocessable. Reprocessable is defined
* as a stream combination that contains one input stream
* ({@link MandatoryStreamInformation#isInput} return true).
*
* @return {@code true} in case the mandatory stream combination contains an input,
* {@code false} otherwise.
*/
public boolean isReprocessable() {
return mIsReprocessable;
}
/**
* Get information about each stream in the mandatory combination.
*
* @return Non-modifiable list of stream information.
*
*/
public @NonNull List<MandatoryStreamInformation> getStreamsInformation() {
return Collections.unmodifiableList(mStreamsInformation);
}
/**
* Check if this {@link MandatoryStreamCombination} is equal to another
* {@link MandatoryStreamCombination}.
*
* <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
*
* @return {@code true} if the objects were equal, {@code false} otherwise
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof MandatoryStreamCombination) {
final MandatoryStreamCombination other = (MandatoryStreamCombination) obj;
if ((mDescription != other.mDescription) ||
(mIsReprocessable != other.mIsReprocessable) ||
(mStreamsInformation.size() != other.mStreamsInformation.size())) {
return false;
}
return mStreamsInformation.equals(other.mStreamsInformation);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(),
mStreamsInformation.hashCode());
}
private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p }
private static enum ReprocessType { NONE, PRIVATE, YUV }
private static final class StreamTemplate {
public int mFormat;
public SizeThreshold mSizeThreshold;
public boolean mIsInput;
public StreamTemplate(int format, SizeThreshold sizeThreshold) {
this(format, sizeThreshold, /*isInput*/false);
}
public StreamTemplate(@Format int format, @NonNull SizeThreshold sizeThreshold,
boolean isInput) {
mFormat = format;
mSizeThreshold = sizeThreshold;
mIsInput = isInput;
}
}
private static final class StreamCombinationTemplate {
public StreamTemplate[] mStreamTemplates;
public String mDescription;
public ReprocessType mReprocessType;
public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
@NonNull String description) {
this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE);
}
public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
@NonNull String description,
ReprocessType reprocessType) {
mStreamTemplates = streamTemplates;
mReprocessType = reprocessType;
mDescription = description;
}
}
private static StreamCombinationTemplate sLegacyCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
"Simple preview, GPU video processing, or no-preview video recording"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"No-viewfinder still image capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"In-application video/image processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"Standard still imaging"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"In-app processing plus still capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
"Standard recording"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
"Preview plus in-app processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"Still capture plus in-app processing")
};
private static StreamCombinationTemplate sLimitedCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)},
"High-resolution video recording with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)},
"High-resolution in-app video processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
"Two-input in-app video processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
"High-resolution recording with video snapshot"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
"High-resolution in-app processing with video snapshot"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"Two-input in-app processing with still capture")
};
private static StreamCombinationTemplate sBurstCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
"Maximum-resolution GPU processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution in-app processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution two-input in-app processsing")
};
private static StreamCombinationTemplate sFullCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
"Maximum-resolution GPU processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution in-app processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution two-input in-app processsing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"Video recording with maximum-size video snapshot"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Standard video recording plus maximum-resolution in-app processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Preview plus two-input maximum-resolution in-app processing")
};
private static StreamCombinationTemplate sRawCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"No-preview DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Standard DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"In-app processing plus DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Video recording with DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Preview with in-app processing and DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Two-input in-app processing plus DNG capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Still capture with simultaneous JPEG and DNG"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"In-app processing with simultaneous JPEG and DNG")
};
private static StreamCombinationTemplate sLevel3Combinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"In-app viewfinder analysis with dynamic selection of output format"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"In-app viewfinder analysis with dynamic selection of output format")
};
private static StreamCombinationTemplate sLimitedPrivateReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"No-viewfinder still image reprocessing",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL(Zero-Shutter-Lag) still imaging",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL still and in-app processing imaging",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL in-app processing with still capture",
/*reprocessType*/ ReprocessType.PRIVATE),
};
private static StreamCombinationTemplate sLimitedYUVReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"No-viewfinder still image reprocessing",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL(Zero-Shutter-Lag) still imaging",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL still and in-app processing imaging",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL in-app processing with still capture",
/*reprocessType*/ ReprocessType.YUV),
};
private static StreamCombinationTemplate sFullPrivateReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
"High-resolution ZSL in-app video processing with regular preview",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution ZSL in-app processing with regular preview",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
"Maximum-resolution two-input ZSL in-app processing",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL still capture and in-app processing",
/*reprocessType*/ ReprocessType.PRIVATE),
};
private static StreamCombinationTemplate sFullYUVReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
"Maximum-resolution multi-frame image fusion in-app processing with regular "
+ "preview",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
"Maximum-resolution multi-frame image fusion two-input in-app processing",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
"High-resolution ZSL in-app video processing with regular preview",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"ZSL still capture and in-app processing",
/*reprocessType*/ ReprocessType.YUV),
};
private static StreamCombinationTemplate sRAWPrivateReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing and DNG capture",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing and preview with DNG capture",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL two-input in-app processing and DNG capture",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL still capture and preview with DNG capture",
/*reprocessType*/ ReprocessType.PRIVATE),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing with still capture and DNG capture",
/*reprocessType*/ ReprocessType.PRIVATE),
};
private static StreamCombinationTemplate sRAWYUVReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing and DNG capture",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing and preview with DNG capture",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL two-input in-app processing and DNG capture",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL still capture and preview with DNG capture",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"Mutually exclusive ZSL in-app processing with still capture and DNG capture",
/*reprocessType*/ ReprocessType.YUV),
};
private static StreamCombinationTemplate sLevel3PrivateReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
/*reprocessType*/ ReprocessType.PRIVATE),
};
private static StreamCombinationTemplate sLevel3YUVReprocCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
"In-app viewfinder analysis with ZSL and RAW",
/*reprocessType*/ ReprocessType.YUV),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
"In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
/*reprocessType*/ ReprocessType.YUV),
};
private static StreamCombinationTemplate sConcurrentStreamCombinations[] = {
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p) },
"In-app video / image processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p) },
"preview / preview to GPU"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p) },
"No view-finder still image capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p)},
"Two-input in app video / image processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p)},
"High resolution video recording with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p)},
"In-app video / image processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p)},
"In-app video / image processing with preview"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
"Standard stil image capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
"Standard still image capture"),
};
/**
* Helper builder class to generate a list of available mandatory stream combinations.
* @hide
*/
public static final class Builder {
private Size mDisplaySize;
private List<Integer> mCapabilities;
private int mHwLevel, mCameraId;
private StreamConfigurationMap mStreamConfigMap;
private boolean mIsHiddenPhysicalCamera;
private final Size kPreviewSizeBound = new Size(1920, 1088);
/**
* Helper class to be used to generate the available mandatory stream combinations.
*
* @param cameraId Current camera id.
* @param hwLevel The camera HW level as reported by android.info.supportedHardwareLevel.
* @param displaySize The device display size.
* @param capabilities The camera device capabilities.
* @param sm The camera device stream configuration map.
*/
public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
@NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) {
mCameraId = cameraId;
mDisplaySize = displaySize;
mCapabilities = capabilities;
mStreamConfigMap = sm;
mHwLevel = hwLevel;
mIsHiddenPhysicalCamera =
CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
}
/**
* Retrieve a list of all available mandatory concurrent stream combinations.
* This method should only be called for devices which are listed in combinations returned
* by CameraManager.getConcurrentCameraIds.
*
* @return a non-modifiable list of supported mandatory concurrent stream combinations.
*/
public @NonNull List<MandatoryStreamCombination>
getAvailableMandatoryConcurrentStreamCombinations() {
// Since concurrent streaming support is optional, we mandate these stream
// combinations regardless of camera device capabilities.
if (!isColorOutputSupported()) {
Log.v(TAG, "Device is not backward compatible!");
throw new IllegalArgumentException("Camera device which is not BACKWARD_COMPATIBLE"
+ " cannot have mandatory concurrent streams");
}
Size size720p = new Size(1280, 720);
Size size1440p = new Size(1920, 1440);
ArrayList<MandatoryStreamCombination> availableConcurrentStreamCombinations =
new ArrayList<MandatoryStreamCombination>();
availableConcurrentStreamCombinations.ensureCapacity(
sConcurrentStreamCombinations.length);
for (StreamCombinationTemplate combTemplate : sConcurrentStreamCombinations) {
ArrayList<MandatoryStreamInformation> streamsInfo =
new ArrayList<MandatoryStreamInformation>();
streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
for (StreamTemplate template : combTemplate.mStreamTemplates) {
MandatoryStreamInformation streamInfo;
List<Size> sizes = new ArrayList<Size>();
Size formatSize = null;
switch (template.mSizeThreshold) {
case s1440p:
formatSize = size1440p;
break;
default:
formatSize = size720p;
}
Size sizeChosen =
getMinSize(formatSize,
getMaxSize(mStreamConfigMap.getOutputSizes(template.mFormat)));
sizes.add(sizeChosen);
try {
streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
} catch (IllegalArgumentException e) {
String cause = "No available sizes found for format: " + template.mFormat
+ " size threshold: " + template.mSizeThreshold + " combination: "
+ combTemplate.mDescription;
throw new RuntimeException(cause, e);
}
streamsInfo.add(streamInfo);
}
MandatoryStreamCombination streamCombination;
try {
streamCombination = new MandatoryStreamCombination(streamsInfo,
combTemplate.mDescription, /*isReprocess*/false);
} catch (IllegalArgumentException e) {
String cause = "No stream information for mandatory combination: "
+ combTemplate.mDescription;
throw new RuntimeException(cause, e);
}
availableConcurrentStreamCombinations.add(streamCombination);
}
return Collections.unmodifiableList(availableConcurrentStreamCombinations);
}
/**
* Retrieve a list of all available mandatory stream combinations.
*
* @return a non-modifiable list of supported mandatory stream combinations or
* null in case device is not backward compatible or the method encounters
* an error.
*/
public @Nullable List<MandatoryStreamCombination>
getAvailableMandatoryStreamCombinations() {
if (!isColorOutputSupported()) {
Log.v(TAG, "Device is not backward compatible!");
return null;
}
if ((mCameraId < 0) && !isExternalCamera()) {
Log.i(TAG, "Invalid camera id");
return null;
}
ArrayList<StreamCombinationTemplate> availableTemplates =
new ArrayList<StreamCombinationTemplate> ();
if (isHardwareLevelAtLeastLegacy()) {
availableTemplates.addAll(Arrays.asList(sLegacyCombinations));
}
// External devices are identical to limited devices w.r.t. stream combinations.
if (isHardwareLevelAtLeastLimited() || isExternalCamera()) {
availableTemplates.addAll(Arrays.asList(sLimitedCombinations));
if (isPrivateReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sLimitedPrivateReprocCombinations));
}
if (isYUVReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sLimitedYUVReprocCombinations));
}
}
if (isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
availableTemplates.addAll(Arrays.asList(sBurstCombinations));
}
if (isHardwareLevelAtLeastFull()) {
availableTemplates.addAll(Arrays.asList(sFullCombinations));
if (isPrivateReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sFullPrivateReprocCombinations));
}
if (isYUVReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sFullYUVReprocCombinations));
}
}
if (isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
availableTemplates.addAll(Arrays.asList(sRawCombinations));
if (isPrivateReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sRAWPrivateReprocCombinations));
}
if (isYUVReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sRAWYUVReprocCombinations));
}
}
if (isHardwareLevelAtLeastLevel3()) {
availableTemplates.addAll(Arrays.asList(sLevel3Combinations));
if (isPrivateReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sLevel3PrivateReprocCombinations));
}
if (isYUVReprocessingSupported()) {
availableTemplates.addAll(Arrays.asList(sLevel3YUVReprocCombinations));
}
}
return generateAvailableCombinations(availableTemplates);
}
/**
* Helper method to generate the available stream combinations given the
* list of available combination templates.
*
* @param availableTemplates a list of templates supported by the camera device.
* @return a non-modifiable list of supported mandatory stream combinations or
* null in case of errors.
*/
private @Nullable List<MandatoryStreamCombination> generateAvailableCombinations(
@NonNull ArrayList<StreamCombinationTemplate> availableTemplates) {
if (availableTemplates.isEmpty()) {
Log.e(TAG, "No available stream templates!");
return null;
}
HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
enumerateAvailableSizes();
if (availableSizes == null) {
Log.e(TAG, "Available size enumeration failed!");
return null;
}
// RAW only uses MAXIMUM size threshold
Size[] rawSizes = mStreamConfigMap.getOutputSizes(ImageFormat.RAW_SENSOR);
ArrayList<Size> availableRawSizes = new ArrayList<Size>();
if (rawSizes != null) {
availableRawSizes.ensureCapacity(rawSizes.length);
availableRawSizes.addAll(Arrays.asList(rawSizes));
}
Size maxPrivateInputSize = new Size(0, 0);
if (isPrivateReprocessingSupported()) {
maxPrivateInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
ImageFormat.PRIVATE));
}
Size maxYUVInputSize = new Size(0, 0);
if (isYUVReprocessingSupported()) {
maxYUVInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
ImageFormat.YUV_420_888));
}
// Generate the available mandatory stream combinations given the supported templates
// and size ranges.
ArrayList<MandatoryStreamCombination> availableStreamCombinations =
new ArrayList<MandatoryStreamCombination>();
availableStreamCombinations.ensureCapacity(availableTemplates.size());
for (StreamCombinationTemplate combTemplate : availableTemplates) {
ArrayList<MandatoryStreamInformation> streamsInfo =
new ArrayList<MandatoryStreamInformation>();
streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
boolean isReprocessable = combTemplate.mReprocessType != ReprocessType.NONE;
if (isReprocessable) {
// The first and second streams in a reprocessable combination have the
// same size and format. The first is the input and the second is the output
// used for generating the subsequent input buffers.
ArrayList<Size> inputSize = new ArrayList<Size>();
int format;
if (combTemplate.mReprocessType == ReprocessType.PRIVATE) {
inputSize.add(maxPrivateInputSize);
format = ImageFormat.PRIVATE;
} else {
inputSize.add(maxYUVInputSize);
format = ImageFormat.YUV_420_888;
}
streamsInfo.add(new MandatoryStreamInformation(inputSize, format,
/*isInput*/true));
streamsInfo.add(new MandatoryStreamInformation(inputSize, format));
}
for (StreamTemplate template : combTemplate.mStreamTemplates) {
List<Size> sizes = null;
if (template.mFormat == ImageFormat.RAW_SENSOR) {
sizes = availableRawSizes;
} else {
Pair<SizeThreshold, Integer> pair;
pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold,
new Integer(template.mFormat));
sizes = availableSizes.get(pair);
}
MandatoryStreamInformation streamInfo;
try {
streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
} catch (IllegalArgumentException e) {
Log.e(TAG, "No available sizes found for format: " + template.mFormat +
" size threshold: " + template.mSizeThreshold + " combination: " +
combTemplate.mDescription);
return null;
}
streamsInfo.add(streamInfo);
}
MandatoryStreamCombination streamCombination;
try {
streamCombination = new MandatoryStreamCombination(streamsInfo,
combTemplate.mDescription, isReprocessable);
} catch (IllegalArgumentException e) {
Log.e(TAG, "No stream information for mandatory combination: "
+ combTemplate.mDescription);
return null;
}
availableStreamCombinations.add(streamCombination);
}
return Collections.unmodifiableList(availableStreamCombinations);
}
/**
* Helper method to enumerate all available sizes according to size threshold and format.
*/
private @Nullable HashMap<Pair<SizeThreshold, Integer>, List<Size>>
enumerateAvailableSizes() {
final int[] formats = {
ImageFormat.PRIVATE,
ImageFormat.YUV_420_888,
ImageFormat.JPEG
};
Size recordingMaxSize = new Size(0, 0);
Size previewMaxSize = new Size(0, 0);
Size vgaSize = new Size(640, 480);
// For external camera, or hidden physical camera, CamcorderProfile may not be
// available, so get maximum recording size using stream configuration map.
if (isExternalCamera() || mIsHiddenPhysicalCamera) {
recordingMaxSize = getMaxCameraRecordingSize();
} else {
recordingMaxSize = getMaxRecordingSize();
}
if (recordingMaxSize == null) {
Log.e(TAG, "Failed to find maximum recording size!");
return null;
}
HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>();
for (int format : formats) {
Integer intFormat = new Integer(format);
allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format));
}
List<Size> previewSizes = getSizesWithinBound(
allSizes.get(new Integer(ImageFormat.PRIVATE)), kPreviewSizeBound);
if ((previewSizes == null) || (previewSizes.isEmpty())) {
Log.e(TAG, "No preview sizes within preview size bound!");
return null;
}
List<Size> orderedPreviewSizes = getAscendingOrderSizes(previewSizes,
/*ascending*/false);
previewMaxSize = getMaxPreviewSize(orderedPreviewSizes);
HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
new HashMap<Pair<SizeThreshold, Integer>, List<Size>>();
for (int format : formats) {
Integer intFormat = new Integer(format);
Size[] sizes = allSizes.get(intFormat);
Pair<SizeThreshold, Integer> pair = new Pair<SizeThreshold, Integer>(
SizeThreshold.VGA, intFormat);
availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize));
pair = new Pair<SizeThreshold, Integer>(SizeThreshold.PREVIEW, intFormat);
availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize));
pair = new Pair<SizeThreshold, Integer>(SizeThreshold.RECORD, intFormat);
availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize));
pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat);
availableSizes.put(pair, Arrays.asList(sizes));
}
return availableSizes;
}
/**
* Compile a list of sizes smaller than or equal to given bound.
* Return an empty list if there is no size smaller than or equal to the bound.
*/
private static @Nullable List<Size> getSizesWithinBound(@NonNull Size[] sizes,
@NonNull Size bound) {
ArrayList<Size> ret = new ArrayList<Size>();
for (Size size : sizes) {
if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
ret.add(size);
}
}
return ret;
}
/**
* Return the lower size
*/
public static @Nullable Size getMinSize(Size a, Size b) {
if (a == null || b == null) {
throw new IllegalArgumentException("sizes was empty");
}
if (a.getWidth() * a.getHeight() < b.getHeight() * b.getWidth()) {
return a;
}
return b;
}
/**
* Get the largest size by area.
*
* @param sizes an array of sizes, must have at least 1 element
*
* @return Largest Size
*
* @throws IllegalArgumentException if sizes was null or had 0 elements
*/
public static @Nullable Size getMaxSize(@NonNull Size... sizes) {
if (sizes == null || sizes.length == 0) {
throw new IllegalArgumentException("sizes was empty");
}
Size sz = sizes[0];
for (Size size : sizes) {
if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
sz = size;
}
}
return sz;
}
/**
* Whether or not the hardware level reported by android.info.supportedHardwareLevel is
* at least the desired one (but could be higher)
*/
private boolean isHardwareLevelAtLeast(int level) {
final int[] sortedHwLevels = {
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
};
if (level == mHwLevel) {
return true;
}
for (int sortedlevel : sortedHwLevels) {
if (sortedlevel == level) {
return true;
} else if (sortedlevel == mHwLevel) {
return false;
}
}
return false;
}
/**
* Whether or not the camera is an external camera.
*
* @return {@code true} if the device is external, {@code false} otherwise.
*/
private boolean isExternalCamera() {
return mHwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
}
/**
* Whether or not the hardware level is at least legacy.
*
* @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise.
*/
private boolean isHardwareLevelAtLeastLegacy() {
return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
}
/**
* Whether or not the hardware level is at least limited.
*
* @return {@code true} if the device is {@code LIMITED} or {@code FULL},
* {@code false} otherwise (i.e. LEGACY).
*/
private boolean isHardwareLevelAtLeastLimited() {
return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
}
/**
* Whether or not the hardware level is at least full.
*
* @return {@code true} if the device is {@code FULL}, {@code false} otherwise.
*/
private boolean isHardwareLevelAtLeastFull() {
return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
}
/**
* Whether or not the hardware level is at least Level 3.
*
* @return {@code true} if the device is {@code LEVEL3}, {@code false} otherwise.
*/
private boolean isHardwareLevelAtLeastLevel3() {
return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3);
}
/**
* Determine whether the current device supports a capability or not.
*
* @param capability (non-negative)
*
* @return {@code true} if the capability is supported, {@code false} otherwise.
*
*/
private boolean isCapabilitySupported(int capability) {
return mCapabilities.contains(capability);
}
/**
* Check whether the current device is backward compatible.
*/
private boolean isColorOutputSupported() {
return isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
}
/**
* Check whether the current device supports private reprocessing.
*/
private boolean isPrivateReprocessingSupported() {
return isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
}
/**
* Check whether the current device supports YUV reprocessing.
*/
private boolean isYUVReprocessingSupported() {
return isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
}
/**
* Return the maximum supported video size using the camcorder profile information.
*
* @return Maximum supported video size.
*/
private @Nullable Size getMaxRecordingSize() {
int quality =
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P) ?
CamcorderProfile.QUALITY_2160P :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P) ?
CamcorderProfile.QUALITY_1080P :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P) ?
CamcorderProfile.QUALITY_720P :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P) ?
CamcorderProfile.QUALITY_480P :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA) ?
CamcorderProfile.QUALITY_QVGA :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_CIF) ?
CamcorderProfile.QUALITY_CIF :
CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QCIF) ?
CamcorderProfile.QUALITY_QCIF :
-1;
if (quality < 0) {
return null;
}
CamcorderProfile maxProfile = CamcorderProfile.get(mCameraId, quality);
return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
}
/**
* Return the maximum supported video size for cameras using data from
* the stream configuration map.
*
* @return Maximum supported video size.
*/
private @NonNull Size getMaxCameraRecordingSize() {
final Size FULLHD = new Size(1920, 1080);
Size[] videoSizeArr = mStreamConfigMap.getOutputSizes(
android.media.MediaRecorder.class);
List<Size> sizes = new ArrayList<Size>();
for (Size sz: videoSizeArr) {
if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
sizes.add(sz);
}
}
List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false);
for (Size sz : videoSizes) {
long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration(
android.media.MediaRecorder.class, sz);
// Give some margin for rounding error
if (minFrameDuration > (1e9 / 30.1)) {
Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz);
return sz;
}
}
Log.w(TAG, "Camera " + mCameraId + " does not support any 30fps video output");
return FULLHD; // doesn't matter what size is returned here
}
private @NonNull Size getMaxPreviewSize(List<Size> orderedPreviewSizes) {
if (orderedPreviewSizes != null) {
for (Size size : orderedPreviewSizes) {
if ((mDisplaySize.getWidth() >= size.getWidth()) &&
(mDisplaySize.getHeight() >= size.getHeight())) {
return size;
}
}
}
Log.w(TAG,"Camera " + mCameraId + " maximum preview size search failed with "
+ "display size " + mDisplaySize);
return kPreviewSizeBound;
}
/**
* Size comparison method used by size comparators.
*/
private static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
long left = widthA * (long) heightA;
long right = widthB * (long) heightB;
if (left == right) {
left = widthA;
right = widthB;
}
return (left < right) ? -1 : (left > right ? 1 : 0);
}
/**
* Size comparator that compares the number of pixels it covers.
*
* <p>If two the areas of two sizes are same, compare the widths.</p>
*/
public static class SizeComparator implements Comparator<Size> {
@Override
public int compare(@NonNull Size lhs, @NonNull Size rhs) {
return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(),
rhs.getHeight());
}
}
/**
* Get a sorted list of sizes from a given size list.
*
* <p>
* The size is compare by area it covers, if the areas are same, then
* compare the widths.
* </p>
*
* @param sizeList The input size list to be sorted
* @param ascending True if the order is ascending, otherwise descending order
* @return The ordered list of sizes
*/
private static @NonNull List<Size> getAscendingOrderSizes(
@NonNull final List<Size> sizeList, boolean ascending) {
Comparator<Size> comparator = new SizeComparator();
List<Size> sortedSizes = new ArrayList<Size>();
sortedSizes.addAll(sizeList);
Collections.sort(sortedSizes, comparator);
if (!ascending) {
Collections.reverse(sortedSizes);
}
return sortedSizes;
}
}
}