availableSizes, int format) {
+ if (availableSizes.isEmpty()) {
+ throw new IllegalArgumentException("No available sizes");
+ }
+ mAvailableSizes.addAll(availableSizes);
+ mFormat = checkArgumentFormat(format);
+ }
+
+ /**
+ * Return the list of available sizes for this mandatory stream.
+ *
+ * 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 List getAvailableSizes() {
+ return Collections.unmodifiableList(mAvailableSizes);
+ }
+
+ /**
+ * Retrieve the mandatory stream {@code format}.
+ *
+ * @return integer format.
+ */
+ public int getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Check if this {@link MandatoryStreamInformation} is equal to another
+ * {@link MandatoryStreamInformation}.
+ *
+ * Two vectors are only equal if and only if each of the respective elements is
+ * equal.
+ *
+ * @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) ||
+ (mAvailableSizes.size() != other.mAvailableSizes.size())) {
+ return false;
+ }
+
+ return mAvailableSizes.equals(other.mAvailableSizes);
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeHelpers.hashCode(mFormat, mAvailableSizes.hashCode());
+ }
+ }
+
+ private final String mDesciption;
+ private final ArrayList mStreamsInformation =
+ new ArrayList();
+ /**
+ * Create a new {@link MandatoryStreamCombination}.
+ *
+ * @param streamsInformation list of available streams in the stream combination.
+ * @param description Summary of the stream combination use case.
+ *
+ * @throws IllegalArgumentException
+ * if stream information is empty
+ * @hide
+ */
+ public MandatoryStreamCombination(@NonNull List streamsInformation,
+ String description) {
+ if (streamsInformation.isEmpty()) {
+ throw new IllegalArgumentException("Empty stream information");
+ }
+ mStreamsInformation.addAll(streamsInformation);
+ mDesciption = description;
+ }
+
+ /**
+ * Get the mandatory stream combination description.
+ *
+ * @return String with the mandatory combination description.
+ */
+ public String getDescription() {
+ return mDesciption;
+ }
+
+ /**
+ * Get information about each stream in the mandatory combination.
+ *
+ * @return Non-modifiable list of stream information.
+ *
+ */
+ public List getStreamsInformation() {
+ return Collections.unmodifiableList(mStreamsInformation);
+ }
+
+ /**
+ * Check if this {@link MandatoryStreamCombination} is equal to another
+ * {@link MandatoryStreamCombination}.
+ *
+ * Two vectors are only equal if and only if each of the respective elements is equal.
+ *
+ * @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 ((mDesciption != other.mDesciption) ||
+ (mStreamsInformation.size() != other.mStreamsInformation.size())) {
+ return false;
+ }
+
+ for (int i = 0; i < mStreamsInformation.size(); i++) {
+ if (!mStreamsInformation.get(i).equals(other.mStreamsInformation.get(i))){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeHelpers.hashCode(mDesciption.hashCode(), mStreamsInformation.hashCode());
+ }
+
+ private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM }
+ private static final class StreamTemplate {
+ public int mFormat;
+ public SizeThreshold mSizeThreshold;
+ public StreamTemplate(int format, SizeThreshold sizeThreshold) {
+ mFormat = format;
+ mSizeThreshold = sizeThreshold;
+ }
+ }
+
+ private static final class StreamCombinationTemplate {
+ public StreamTemplate[] mStreamTemplates;
+ public String mDescription;
+
+ public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description) {
+ mStreamTemplates = streamTemplates;
+ 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")
+ };
+
+ /**
+ * Helper builder class to generate a list of available mandatory stream combinations.
+ * @hide
+ */
+ public static final class Builder {
+ private Size mDisplaySize;
+ private List mCapabilities;
+ private int mHwLevel, mCameraId;
+ private StreamConfigurationMap mStreamConfigMap;
+
+ 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 capabilities, @NonNull StreamConfigurationMap sm) {
+ mCameraId = cameraId;
+ mDisplaySize = displaySize;
+ mCapabilities = capabilities;
+ mStreamConfigMap = sm;
+ mHwLevel = hwLevel;
+ }
+
+ /**
+ * 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 List 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 availableTemplates =
+ new ArrayList ();
+ 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 (isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+ availableTemplates.addAll(Arrays.asList(sBurstCombinations));
+ }
+
+ if (isHardwareLevelAtLeastFull()) {
+ availableTemplates.addAll(Arrays.asList(sFullCombinations));
+ }
+
+ if (isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ availableTemplates.addAll(Arrays.asList(sRawCombinations));
+ }
+
+ if (isHardwareLevelAtLeastLevel3()) {
+ availableTemplates.addAll(Arrays.asList(sLevel3Combinations));
+ }
+
+ 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 List generateAvailableCombinations(
+ ArrayList availableTemplates) {
+ if (availableTemplates.isEmpty()) {
+ Log.e(TAG, "No available stream templates!");
+ return null;
+ }
+
+ HashMap, List> 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 availableRawSizes = new ArrayList();
+ if (rawSizes != null) {
+ availableRawSizes.ensureCapacity(rawSizes.length);
+ availableRawSizes.addAll(Arrays.asList(rawSizes));
+ }
+
+ // Generate the available mandatory stream combinations given the supported templates
+ // and size ranges.
+ ArrayList availableStreamCombinations =
+ new ArrayList();
+ availableStreamCombinations.ensureCapacity(availableTemplates.size());
+ for (StreamCombinationTemplate combTemplate : availableTemplates) {
+ ArrayList streamsInfo =
+ new ArrayList();
+ streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
+ for (StreamTemplate template : combTemplate.mStreamTemplates) {
+ List sizes = null;
+ if (template.mFormat == ImageFormat.RAW_SENSOR) {
+ sizes = availableRawSizes;
+ } else {
+ Pair pair;
+ pair = new Pair(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);
+ } 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 HashMap, List> 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);
+ if (isExternalCamera()) {
+ recordingMaxSize = getMaxExternalRecordingSize();
+ } else {
+ recordingMaxSize = getMaxRecordingSize();
+ }
+ if (recordingMaxSize == null) {
+ Log.e(TAG, "Failed to find maximum recording size!");
+ return null;
+ }
+
+ HashMap allSizes = new HashMap();
+ for (int format : formats) {
+ Integer intFormat = new Integer(format);
+ allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format));
+ }
+
+ List 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 orderedPreviewSizes = getAscendingOrderSizes(previewSizes,
+ /*ascending*/false);
+ previewMaxSize = getMaxPreviewSize(orderedPreviewSizes);
+
+ HashMap, List> availableSizes =
+ new HashMap, List>();
+
+ for (int format : formats) {
+ Integer intFormat = new Integer(format);
+ Size[] sizes = allSizes.get(intFormat);
+ Pair pair = new Pair(
+ SizeThreshold.VGA, intFormat);
+ availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize));
+
+ pair = new Pair(SizeThreshold.PREVIEW, intFormat);
+ availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize));
+
+ pair = new Pair(SizeThreshold.RECORD, intFormat);
+ availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize));
+
+ pair = new Pair(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 List getSizesWithinBound(Size[] sizes, Size bound) {
+ if (sizes == null || sizes.length == 0) {
+ Log.e(TAG, "Empty or invalid size array!");
+ return null;
+ }
+
+ ArrayList ret = new ArrayList();
+ for (Size size : sizes) {
+ if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
+ ret.add(size);
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Return the maximum supported video size using the camcorder profile information.
+ *
+ * @return Maximum supported video size.
+ */
+ private 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 external cameras using data from
+ * the stream configuration map.
+ *
+ * @return Maximum supported video size.
+ */
+ private Size getMaxExternalRecordingSize() {
+ final Size FULLHD = new Size(1920, 1080);
+
+ Size[] videoSizeArr = mStreamConfigMap.getOutputSizes(
+ android.media.MediaRecorder.class);
+ List sizes = new ArrayList();
+ for (Size sz: videoSizeArr) {
+ if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
+ sizes.add(sz);
+ }
+ }
+ List 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 Size getMaxPreviewSize(List orderedPreviewSizes) {
+ if (orderedPreviewSizes != null) {
+ for (Size size : orderedPreviewSizes) {
+ if ((mDisplaySize.getWidth() >= size.getWidth()) &&
+ (mDisplaySize.getWidth() >= 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.
+ *
+ * If two the areas of two sizes are same, compare the widths.
+ */
+ public static class SizeComparator implements Comparator {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(),
+ rhs.getHeight());
+ }
+ }
+
+ /**
+ * Get a sorted list of sizes from a given size list.
+ *
+ *
+ * The size is compare by area it covers, if the areas are same, then
+ * compare the widths.
+ *
+ *
+ * @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 List getAscendingOrderSizes(final List sizeList,
+ boolean ascending) {
+ if (sizeList == null) {
+ return null;
+ }
+
+ Comparator comparator = new SizeComparator();
+ List sortedSizes = new ArrayList();
+ sortedSizes.addAll(sizeList);
+ Collections.sort(sortedSizes, comparator);
+ if (!ascending) {
+ Collections.reverse(sortedSizes);
+ }
+
+ return sortedSizes;
+ }
+ }
+}
From 275da0ba0d761cfe80b29ea603b4dc902fcf487c Mon Sep 17 00:00:00 2001
From: Emilian Peev
Date: Thu, 15 Nov 2018 18:20:24 +0000
Subject: [PATCH 2/3] Camera: Add reprocessable mandatory stream combinations
Mandatory reprocessable stream combinations must include
at least one input stream and one ZSL output with the same
format and size.
Bug: 111593096
Test: Camera CTS
Change-Id: I4bdfb8b540ccce583b01ea200d8c7e252dd72b12
---
api/current.txt | 2 +
.../params/MandatoryStreamCombination.java | 376 +++++++++++++++++-
2 files changed, 361 insertions(+), 17 deletions(-)
diff --git a/api/current.txt b/api/current.txt
index 9ef1003b9d45f..693248ad6f7eb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17103,11 +17103,13 @@ package android.hardware.camera2.params {
public final class MandatoryStreamCombination {
method public java.lang.String getDescription();
method public java.util.List getStreamsInformation();
+ method public boolean isReprocessable();
}
public static final class MandatoryStreamCombination.MandatoryStreamInformation {
method public java.util.List getAvailableSizes();
method public int getFormat();
+ method public boolean isInput();
}
public final class MeteringRectangle {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index fda563ed3d7f1..b8e2d7adcf828 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -59,11 +59,12 @@ public final class MandatoryStreamCombination {
public static final class MandatoryStreamInformation {
private final int mFormat;
private final ArrayList mAvailableSizes = new ArrayList ();
+ private final boolean mIsInput;
/**
* Create a new {@link MandatoryStreamInformation}.
*
- @param sizes List of possible stream sizes.
+ @param availableSizes List of possible stream sizes.
* @param format Image format.
*
* @throws IllegalArgumentException
@@ -72,11 +73,37 @@ public final class MandatoryStreamCombination {
* @hide
*/
public MandatoryStreamInformation(@NonNull List 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 availableSizes, 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;
}
/**
@@ -123,7 +150,7 @@ public final class MandatoryStreamCombination {
}
if (obj instanceof MandatoryStreamInformation) {
final MandatoryStreamInformation other = (MandatoryStreamInformation) obj;
- if ((mFormat != other.mFormat) ||
+ if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) ||
(mAvailableSizes.size() != other.mAvailableSizes.size())) {
return false;
}
@@ -139,11 +166,13 @@ public final class MandatoryStreamCombination {
*/
@Override
public int hashCode() {
- return HashCodeHelpers.hashCode(mFormat, mAvailableSizes.hashCode());
+ return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput),
+ mAvailableSizes.hashCode());
}
}
- private final String mDesciption;
+ private final String mDescription;
+ private final boolean mIsReprocessable;
private final ArrayList mStreamsInformation =
new ArrayList();
/**
@@ -151,18 +180,20 @@ public final class 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 streamsInformation,
- String description) {
+ String description, boolean isReprocessable) {
if (streamsInformation.isEmpty()) {
throw new IllegalArgumentException("Empty stream information");
}
mStreamsInformation.addAll(streamsInformation);
- mDesciption = description;
+ mDescription = description;
+ mIsReprocessable = isReprocessable;
}
/**
@@ -171,7 +202,17 @@ public final class MandatoryStreamCombination {
* @return String with the mandatory combination description.
*/
public String getDescription() {
- return mDesciption;
+ return mDescription;
+ }
+
+ /**
+ * Indicates whether the mandatory stream combination is reprocessable.
+ *
+ * @return {@code true} in case the mandatory stream combination contains an input,
+ * {@code false} otherwise.
+ */
+ public boolean isReprocessable() {
+ return mIsReprocessable;
}
/**
@@ -202,18 +243,13 @@ public final class MandatoryStreamCombination {
}
if (obj instanceof MandatoryStreamCombination) {
final MandatoryStreamCombination other = (MandatoryStreamCombination) obj;
- if ((mDesciption != other.mDesciption) ||
+ if ((mDescription != other.mDescription) ||
+ (mIsReprocessable != other.mIsReprocessable) ||
(mStreamsInformation.size() != other.mStreamsInformation.size())) {
return false;
}
- for (int i = 0; i < mStreamsInformation.size(); i++) {
- if (!mStreamsInformation.get(i).equals(other.mStreamsInformation.get(i))){
- return false;
- }
- }
-
- return true;
+ return mStreamsInformation.equals(other.mStreamsInformation);
}
return false;
@@ -224,25 +260,40 @@ public final class MandatoryStreamCombination {
*/
@Override
public int hashCode() {
- return HashCodeHelpers.hashCode(mDesciption.hashCode(), mStreamsInformation.hashCode());
+ return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(),
+ mStreamsInformation.hashCode());
}
private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM }
+ 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(int format, 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(StreamTemplate[] streamTemplates, String description) {
+ this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE);
+ }
+
+ public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description,
+ ReprocessType reprocessType) {
mStreamTemplates = streamTemplates;
+ mReprocessType = reprocessType;
mDescription = description;
}
}
@@ -409,6 +460,189 @@ public final class MandatoryStreamCombination {
"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),
+ };
+
/**
* Helper builder class to generate a list of available mandatory stream combinations.
* @hide
@@ -466,6 +700,15 @@ public final class MandatoryStreamCombination {
// 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(
@@ -475,15 +718,42 @@ public final class MandatoryStreamCombination {
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);
@@ -519,6 +789,18 @@ public final class MandatoryStreamCombination {
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 availableStreamCombinations =
@@ -528,6 +810,26 @@ public final class MandatoryStreamCombination {
ArrayList streamsInfo =
new ArrayList();
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 inputSize = new ArrayList();
+ 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 sizes = null;
if (template.mFormat == ImageFormat.RAW_SENSOR) {
@@ -555,7 +857,7 @@ public final class MandatoryStreamCombination {
MandatoryStreamCombination streamCombination;
try {
streamCombination = new MandatoryStreamCombination(streamsInfo,
- combTemplate.mDescription);
+ combTemplate.mDescription, isReprocessable);
} catch (IllegalArgumentException e) {
Log.e(TAG, "No stream information for mandatory combination: "
+ combTemplate.mDescription);
@@ -649,6 +951,30 @@ public final class MandatoryStreamCombination {
return ret;
}
+ /**
+ * 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 Size getMaxSize(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)
@@ -742,6 +1068,22 @@ public final class MandatoryStreamCombination {
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.
*
From 3be63a3f651a29b190c1124ea8fd5c2e5c7b6ed8 Mon Sep 17 00:00:00 2001
From: Emilian Peev
Date: Mon, 19 Nov 2018 17:44:39 +0000
Subject: [PATCH 3/3] Camera: Implement legacy device stream combination query
Use the legacy stream configuration logic to check for
support of a given session configuration.
Bug: 111593096
Test:
runtest -x
cts/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
runtest -x
cts/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java -m
testMandatoryOutputCombinations
runtest -x
cts/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java -m
testBadSurfaceDimensions
runtest -x
cts/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java -m
testMandatoryReprocessConfigurations
runtest -x
cts/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
-m testDeferredSurfaces
Change-Id: I921f94be8212a55fa90150cb35ea06e488dc0b29
---
.../camera2/legacy/CameraDeviceUserShim.java | 34 +++++++++++++++++--
.../camera2/legacy/LegacyCameraDevice.java | 27 ++++++++++++++-
2 files changed, 58 insertions(+), 3 deletions(-)
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 8971f8d9e533e..3e130c530cada 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -484,8 +484,38 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
@Override
public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) {
- // TODO: Add support for this in legacy mode
- throw new UnsupportedOperationException("Session configuration query not supported!");
+ if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) {
+ Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " +
+ " regular. Legacy devices support only regular session types!");
+ return false;
+ }
+
+ if (sessionConfig.getInputConfiguration() != null) {
+ Log.e(TAG, "Input configuration present, legacy devices do not support this feature!");
+ return false;
+ }
+
+ List outputConfigs = sessionConfig.getOutputConfigurations();
+ if (outputConfigs.isEmpty()) {
+ Log.e(TAG, "Empty output configuration list!");
+ return false;
+ }
+
+ SparseArray surfaces = new SparseArray(outputConfigs.size());
+ int idx = 0;
+ for (OutputConfiguration outputConfig : outputConfigs) {
+ List surfaceList = outputConfig.getSurfaces();
+ if (surfaceList.isEmpty() || (surfaceList.size() > 1)) {
+ Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!");
+ return false;
+ }
+
+ surfaces.put(idx++, outputConfig.getSurface());
+ }
+
+ int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true);
+
+ return ret == LegacyExceptionUtils.NO_ERROR;
}
@Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 71a361bd00c6b..aff09f2a45f26 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -346,6 +346,25 @@ public class LegacyCameraDevice implements AutoCloseable {
* on success.
*/
public int configureOutputs(SparseArray outputs) {
+ return configureOutputs(outputs, /*validateSurfacesOnly*/false);
+ }
+
+ /**
+ * Configure the device with a set of output surfaces.
+ *
+ * Using empty or {@code null} {@code outputs} is the same as unconfiguring.
+ *
+ * Every surface in {@code outputs} must be non-{@code null}.
+ *
+ * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
+ * list; it must not be modified by the caller once it's passed in.
+ * @param validateSurfacesOnly If set it will only check whether the outputs are supported
+ * and avoid any device configuration.
+ * @return an error code for this binder operation, or {@link NO_ERROR}
+ * on success.
+ * @hide
+ */
+ public int configureOutputs(SparseArray outputs, boolean validateSurfacesOnly) {
List> sizedSurfaces = new ArrayList<>();
if (outputs != null) {
int count = outputs.size();
@@ -397,7 +416,9 @@ public class LegacyCameraDevice implements AutoCloseable {
sizedSurfaces.add(new Pair<>(output, s));
}
// Lock down the size before configuration
- setSurfaceDimens(output, s.getWidth(), s.getHeight());
+ if (!validateSurfacesOnly) {
+ setSurfaceDimens(output, s.getWidth(), s.getHeight());
+ }
} catch (BufferQueueAbandonedException e) {
Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
return BAD_VALUE;
@@ -406,6 +427,10 @@ public class LegacyCameraDevice implements AutoCloseable {
}
}
+ if (validateSurfacesOnly) {
+ return LegacyExceptionUtils.NO_ERROR;
+ }
+
boolean success = false;
if (mDeviceState.setConfiguring()) {
mRequestThreadManager.configure(sizedSurfaces);