diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 907d2ae26f9ac..618195a5f5b43 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -34,7 +34,6 @@ import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.ListUtils; import android.hardware.camera2.utils.ParamsUtils; -import android.hardware.camera2.utils.SizeAreaComparator; import android.util.Log; import android.util.Range; import android.util.Size; @@ -88,6 +87,9 @@ public class LegacyMetadataMapper { static final int UNKNOWN_MODE = -1; + // Maximum difference between a preview size aspect ratio and a jpeg size aspect ratio + private static final float PREVIEW_ASPECT_RATIO_TOLERANCE = 0.01f; + /* * Development hijinks: Lie about not supporting certain capabilities * @@ -103,6 +105,7 @@ public class LegacyMetadataMapper { static final boolean LIE_ABOUT_AWB_STATE = false; static final boolean LIE_ABOUT_AWB = false; + /** * Create characteristics for a legacy device by mapping the {@code parameters} * and {@code info} @@ -262,6 +265,64 @@ public class LegacyMetadataMapper { * remapping to public format constants. */ List previewSizes = p.getSupportedPreviewSizes(); + List jpegSizes = p.getSupportedPictureSizes(); + /* + * Work-around for b/17589233: + * - Some HALs's largest preview size aspect ratio does not match the largest JPEG size AR + * - This causes a large amount of problems with focus/metering because it's relative to + * preview, making the difference between the JPEG and preview viewport inaccessible + * - This boils down to metering or focusing areas being "arbitrarily" cropped + * in the capture result. + * - Work-around the HAL limitations by removing all of the largest preview sizes + * until we get one with the same aspect ratio as the jpeg size. + */ + { + SizeAreaComparator areaComparator = new SizeAreaComparator(); + + // Sort preview to min->max + Collections.sort(previewSizes, areaComparator); + + Camera.Size maxJpegSize = SizeAreaComparator.findLargestByArea(jpegSizes); + float jpegAspectRatio = maxJpegSize.width * 1.0f / maxJpegSize.height; + + if (VERBOSE) { + Log.v(TAG, String.format("mapScalerStreamConfigs - largest JPEG area %dx%d, AR=%f", + maxJpegSize.width, maxJpegSize.height, jpegAspectRatio)); + } + + // Now remove preview sizes from the end (largest->smallest) until aspect ratio matches + while (!previewSizes.isEmpty()) { + int index = previewSizes.size() - 1; // max is always at the end + Camera.Size size = previewSizes.get(index); + + float previewAspectRatio = size.width * 1.0f / size.height; + + if (Math.abs(jpegAspectRatio - previewAspectRatio) >= + PREVIEW_ASPECT_RATIO_TOLERANCE) { + previewSizes.remove(index); // Assume removing from end is O(1) + + if (VERBOSE) { + Log.v(TAG, String.format( + "mapScalerStreamConfigs - removed preview size %dx%d, AR=%f " + + "was not the same", + size.width, size.height, previewAspectRatio)); + } + } else { + break; + } + } + + if (previewSizes.isEmpty()) { + // Fall-back to the original faulty behavior, but at least work + Log.w(TAG, "mapScalerStreamConfigs - failed to find any preview size matching " + + "JPEG aspect ratio " + jpegAspectRatio); + previewSizes = p.getSupportedPreviewSizes(); + } + + // Sort again, this time in descending order max->min + Collections.sort(previewSizes, Collections.reverseOrder(areaComparator)); + } + appendStreamConfig(availableStreamConfigs, HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, previewSizes); appendStreamConfig(availableStreamConfigs, @@ -279,7 +340,6 @@ public class LegacyMetadataMapper { } } - List jpegSizes = p.getSupportedPictureSizes(); appendStreamConfig(availableStreamConfigs, HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes()); /* @@ -620,7 +680,7 @@ public class LegacyMetadataMapper { if (thumbnailSizes != null) { Size[] sizes = convertSizeListToArray(thumbnailSizes); - Arrays.sort(sizes, new SizeAreaComparator()); + Arrays.sort(sizes, new android.hardware.camera2.utils.SizeAreaComparator()); m.set(JPEG_AVAILABLE_THUMBNAIL_SIZES, sizes); } } diff --git a/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java b/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java new file mode 100644 index 0000000000000..75a5bab948673 --- /dev/null +++ b/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 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.legacy; + +import android.hardware.Camera; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.android.internal.util.Preconditions.*; + +/** + * Comparator for api1 {@link Camera.Size} objects by the area. + * + *

This comparator totally orders by rectangle area. Tie-breaks on width.

+ */ +@SuppressWarnings("deprecation") +public class SizeAreaComparator implements Comparator { + /** + * {@inheritDoc} + */ + @Override + public int compare(Camera.Size size, Camera.Size size2) { + checkNotNull(size, "size must not be null"); + checkNotNull(size2, "size2 must not be null"); + + if (size.equals(size2)) { + return 0; + } + + long width = size.width; + long width2 = size2.width; + long area = width * size.height; + long area2 = width2 * size2.height; + + if (area == area2) { + return (width > width2) ? 1 : -1; + } + + return (area > area2) ? 1 : -1; + } + + /** + * Get the largest api1 {@code Camera.Size} from the list by comparing each size's area + * by each other using {@link SizeAreaComparator}. + * + * @param sizes a non-{@code null} list of non-{@code null} sizes + * @return a non-{@code null} size + * + * @throws NullPointerException if {@code sizes} or any elements in it were {@code null} + */ + public static Camera.Size findLargestByArea(List sizes) { + checkNotNull(sizes, "sizes must not be null"); + + return Collections.max(sizes, new SizeAreaComparator()); + } +}