Camera: Add support for new camera zoom API
- Add NDK API spec for the new zoom API
The new zoom API combines optical and digital zoom, and supports both
zoom-out and zoom-in with more precision.
- Add new NDK API to specify separate zoom ratio ranges for different
bokeh modes.
- Add ZoomRatioMapper in camera service to convert between
control.zoomRation to and from scaler.cropRegion.
Test: Camera CTS/ITS/CtsVerifier/ZoomRatioTest
Bug: 130025314
Change-Id: I4c7d867f840b5720bc73bb0485e8a9a93d2276b5
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index 7c41b5e..e15e1a0 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -480,6 +480,7 @@
case ACAMERA_CONTROL_POST_RAW_SENSITIVITY_BOOST:
case ACAMERA_CONTROL_ENABLE_ZSL:
case ACAMERA_CONTROL_BOKEH_MODE:
+ case ACAMERA_CONTROL_ZOOM_RATIO:
case ACAMERA_EDGE_MODE:
case ACAMERA_FLASH_MODE:
case ACAMERA_HOT_PIXEL_MODE:
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 0652cbb..b4a907f 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -518,11 +518,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same aeRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The aeRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * aeRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of the
+ * scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -698,11 +709,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same afRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The afRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * afRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of the
+ * scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -873,11 +895,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same awbRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The awbRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * awbRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of
+ * the scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1735,8 +1768,10 @@
ACAMERA_CONTROL_AF_SCENE_CHANGE = // byte (acamera_metadata_enum_android_control_af_scene_change_t)
ACAMERA_CONTROL_START + 42,
/**
- * <p>The list of bokeh modes that are supported by this camera device, and each bokeh mode's
- * maximum streaming (non-stall) size with bokeh effect.</p>
+ * <p>The list of bokeh modes for ACAMERA_CONTROL_BOKEH_MODE that are supported by this camera
+ * device, and each bokeh mode's maximum streaming (non-stall) size with bokeh effect.</p>
+ *
+ * @see ACAMERA_CONTROL_BOKEH_MODE
*
* <p>Type: int32[3*n]</p>
*
@@ -1761,9 +1796,31 @@
* application configures a stream with larger dimension, the stream may not have bokeh
* effect applied.</p>
*/
- ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES = // int32[3*n]
+ ACAMERA_CONTROL_AVAILABLE_BOKEH_MAX_SIZES = // int32[3*n]
ACAMERA_CONTROL_START + 43,
/**
+ * <p>The ranges of supported zoom ratio for non-OFF ACAMERA_CONTROL_BOKEH_MODE.</p>
+ *
+ * @see ACAMERA_CONTROL_BOKEH_MODE
+ *
+ * <p>Type: float[2*n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>When bokeh mode is enabled, the camera device may have limited range of zoom ratios
+ * compared to when bokeh mode is disabled. This tag lists the zoom ratio ranges for all
+ * supported non-OFF bokeh modes, in the same order as in
+ * ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES.</p>
+ * <p>Range [1.0, 1.0] means that no zoom (optical or digital) is supported.</p>
+ *
+ * @see ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES
+ */
+ ACAMERA_CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES = // float[2*n]
+ ACAMERA_CONTROL_START + 44,
+ /**
* <p>Whether bokeh mode is enabled for a particular capture request.</p>
*
* <p>Type: byte (acamera_metadata_enum_android_control_bokeh_mode_t)</p>
@@ -1804,7 +1861,71 @@
* bokeh mode is off.</p>
*/
ACAMERA_CONTROL_BOKEH_MODE = // byte (acamera_metadata_enum_android_control_bokeh_mode_t)
- ACAMERA_CONTROL_START + 44,
+ ACAMERA_CONTROL_START + 45,
+ /**
+ * <p>Minimum and maximum zoom ratios supported by this camera device.</p>
+ *
+ * <p>Type: float[2]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>If the camera device supports zoom-out from 1x zoom, minZoom will be less than 1.0, and
+ * setting android.control.zoomRation to values less than 1.0 increases the camera's field
+ * of view.</p>
+ */
+ ACAMERA_CONTROL_ZOOM_RATIO_RANGE = // float[2]
+ ACAMERA_CONTROL_START + 46,
+ /**
+ * <p>The desired zoom ratio</p>
+ *
+ * <p>Type: float</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * <li>ACaptureRequest</li>
+ * </ul></p>
+ *
+ * <p>Instead of using ACAMERA_SCALER_CROP_REGION with dual purposes of crop and zoom, the
+ * application can now choose to use this tag to specify the desired zoom level. The
+ * ACAMERA_SCALER_CROP_REGION can still be used to specify the horizontal or vertical
+ * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>By using this control, the application gains a simpler way to control zoom, which can
+ * be a combination of optical and digital zoom. More specifically, for a logical
+ * multi-camera with more than one focal length, using a floating point zoom ratio offers
+ * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of
+ * less than 1.0 to zoom out to a wide field of view.</p>
+ * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes
+ * to the effective after-zoom field-of-view represented by rectangle of (0, 0,
+ * activeArrayWidth, activeArrayHeight).</p>
+ * <p>For example, if ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE is 4032*3024, and the preview stream
+ * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in
+ * one of two ways:</p>
+ * <ul>
+ * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li>
+ * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li>
+ * </ul>
+ * <p>If the application intends to set aeRegions to be top-left quarter of the preview
+ * field-of-view, the ACAMERA_CONTROL_AE_REGIONS should be set to (0, 0, 2016, 1512) with
+ * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent
+ * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't
+ * explicitly set ACAMERA_CONTROL_ZOOM_RATIO, its value defaults to 1.0.</p>
+ * <p>This coordinate system change isn't applicable to RAW capture and its related metadata
+ * such as intrinsicCalibration and lensShadingMap.</p>
+ * <p>One limitation of controlling zoom using zoomRatio is that the ACAMERA_SCALER_CROP_REGION
+ * must only be used for letterboxing or pillarboxing of the sensor active array, and no
+ * FREEFORM cropping can be used with ACAMERA_CONTROL_ZOOM_RATIO other than 1.0.</p>
+ *
+ * @see ACAMERA_CONTROL_AE_REGIONS
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_SCALER_CROP_REGION
+ * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ */
+ ACAMERA_CONTROL_ZOOM_RATIO = // float
+ ACAMERA_CONTROL_START + 47,
ACAMERA_CONTROL_END,
/**
@@ -3203,8 +3324,17 @@
* for rounding and other hardware requirements; the final
* crop region used will be included in the output capture
* result.</p>
+ * <p>Starting from API level 30, it's strongly recommended to use ACAMERA_CONTROL_ZOOM_RATIO
+ * to take advantage of better support for zoom with logical multi-camera. The benefits
+ * include better precision with optical-digital zoom combination, and ability to do
+ * zoom-out from 1.0x. When using ACAMERA_CONTROL_ZOOM_RATIO for zoom, the crop region in
+ * the capture request must be either letterboxing or pillarboxing (but not both). The
+ * coordinate system is post-zoom, meaning that the activeArraySize or
+ * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom.
+ * See ACAMERA_CONTROL_ZOOM_RATIO for details.</p>
* <p>The data representation is int[4], which maps to (left, top, width, height).</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -3232,6 +3362,12 @@
* <p>Crop regions that have a width or height that is smaller
* than this ratio allows will be rounded up to the minimum
* allowed size by the camera device.</p>
+ * <p>Starting from API level 30, when using ACAMERA_CONTROL_ZOOM_RATIO to zoom in or out,
+ * the application must use ACAMERA_CONTROL_ZOOM_RATIO_RANGE to query both the minimum and
+ * maximum zoom ratio.</p>
+ *
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_CONTROL_ZOOM_RATIO_RANGE
*/
ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM = // float
ACAMERA_SCALER_START + 4,
@@ -3406,8 +3542,24 @@
* <p>Camera devices that support FREEFORM cropping will support any crop region that
* is inside of the active array. The camera device will apply the same crop region and
* return the final used crop region in capture result metadata ACAMERA_SCALER_CROP_REGION.</p>
+ * <p>Starting from API level 30,</p>
+ * <ul>
+ * <li>If the camera device supports FREEFORM cropping, in order to do FREEFORM cropping, the
+ * application must set ACAMERA_CONTROL_ZOOM_RATIO to 1.0, and use ACAMERA_SCALER_CROP_REGION
+ * for zoom.</li>
+ * <li>To do CENTER_ONLY zoom, the application has below 2 options:<ol>
+ * <li>Set ACAMERA_CONTROL_ZOOM_RATIO to 1.0; adjust zoom by ACAMERA_SCALER_CROP_REGION.</li>
+ * <li>Adjust zoom by ACAMERA_CONTROL_ZOOM_RATIO; use ACAMERA_SCALER_CROP_REGION to crop
+ * the field of view vertically (letterboxing) or horizontally (pillarboxing), but not
+ * windowboxing.</li>
+ * </ol>
+ * </li>
+ * <li>Setting ACAMERA_CONTROL_ZOOM_RATIO to values different than 1.0 and
+ * ACAMERA_SCALER_CROP_REGION to be windowboxing at the same time is undefined behavior.</li>
+ * </ul>
* <p>LEGACY capability devices will only support CENTER_ONLY cropping.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
@@ -4707,9 +4859,20 @@
* When the distortion correction mode is not OFF, the coordinate system follows
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with
* <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE == FULL</p>
+ * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE == FULL.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * ACAMERA_CONTROL_ZOOM_RATIO, the face landmarks move farther away from the center of the
+ * activeArray or preCorrectionActiveArray. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0
+ * (default), the face landmarks coordinates won't change as ACAMERA_SCALER_CROP_REGION
+ * changes. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
+ * @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see ACAMERA_STATISTICS_FACE_DETECT_MODE
@@ -4738,10 +4901,21 @@
* When the distortion correction mode is not OFF, the coordinate system follows
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with
* <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF
- * The data representation is <code>int[4]</code>, which maps to <code>(left, top, right, bottom)</code>.</p>
+ * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * ACAMERA_CONTROL_ZOOM_RATIO, the face rectangles grow larger and move farther away from
+ * the center of the activeArray or preCorrectionActiveArray. If ACAMERA_CONTROL_ZOOM_RATIO
+ * is set to 1.0 (default), the face rectangles won't change as ACAMERA_SCALER_CROP_REGION
+ * changes. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
+ * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, right, bottom)</code>.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
+ * @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see ACAMERA_STATISTICS_FACE_DETECT_MODE
diff --git a/services/camera/libcameraservice/Android.bp b/services/camera/libcameraservice/Android.bp
index 9cc15cd..6052a06 100644
--- a/services/camera/libcameraservice/Android.bp
+++ b/services/camera/libcameraservice/Android.bp
@@ -56,7 +56,9 @@
"device3/StatusTracker.cpp",
"device3/Camera3BufferManager.cpp",
"device3/Camera3StreamSplitter.cpp",
+ "device3/CoordinateMapper.cpp",
"device3/DistortionMapper.cpp",
+ "device3/ZoomRatioMapper.cpp",
"gui/RingBufferConsumer.cpp",
"utils/CameraThreadState.cpp",
"hidl/AidlCameraDeviceCallbacks.cpp",
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 7ed4c3d..23f7884 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -40,6 +40,7 @@
#include <utils/Trace.h>
#include "api2/HeicCompositeStream.h"
+#include "device3/ZoomRatioMapper.h"
namespace android {
@@ -232,6 +233,15 @@
return deviceInfo->hasFlashUnit();
}
+bool CameraProviderManager::supportNativeZoomRatio(const std::string &id) const {
+ std::lock_guard<std::mutex> lock(mInterfaceMutex);
+
+ auto deviceInfo = findDeviceInfoLocked(id);
+ if (deviceInfo == nullptr) return false;
+
+ return deviceInfo->supportNativeZoomRatio();
+}
+
status_t CameraProviderManager::getResourceCost(const std::string &id,
CameraResourceCost* cost) const {
std::lock_guard<std::mutex> lock(mInterfaceMutex);
@@ -2035,6 +2045,13 @@
__FUNCTION__, strerror(-res), res);
}
+ res = camera3::ZoomRatioMapper::overrideZoomRatioTags(
+ &mCameraCharacteristics, &mSupportNativeZoomRatio);
+ if (OK != res) {
+ ALOGE("%s: Unable to override zoomRatio related tags: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ }
+
camera_metadata_entry flashAvailable =
mCameraCharacteristics.find(ANDROID_FLASH_INFO_AVAILABLE);
if (flashAvailable.count == 1 &&
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 2ef1f6f..651b8a1 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -187,6 +187,11 @@
bool hasFlashUnit(const std::string &id) const;
/**
+ * Return true if the camera device has native zoom ratio support.
+ */
+ bool supportNativeZoomRatio(const std::string &id) const;
+
+ /**
* Return the resource cost of this camera device
*/
status_t getResourceCost(const std::string &id,
@@ -416,6 +421,7 @@
sp<ProviderInfo> mParentProvider;
bool hasFlashUnit() const { return mHasFlashUnit; }
+ bool supportNativeZoomRatio() const { return mSupportNativeZoomRatio; }
virtual status_t setTorchMode(bool enabled) = 0;
virtual status_t getCameraInfo(hardware::CameraInfo *info) const = 0;
virtual bool isAPI1Compatible() const = 0;
@@ -449,10 +455,11 @@
mIsLogicalCamera(false), mResourceCost(resourceCost),
mStatus(hardware::camera::common::V1_0::CameraDeviceStatus::PRESENT),
mParentProvider(parentProvider), mHasFlashUnit(false),
- mPublicCameraIds(publicCameraIds) {}
+ mSupportNativeZoomRatio(false), mPublicCameraIds(publicCameraIds) {}
virtual ~DeviceInfo();
protected:
- bool mHasFlashUnit;
+ bool mHasFlashUnit; // const after constructor
+ bool mSupportNativeZoomRatio; // const after constructor
const std::vector<std::string>& mPublicCameraIds;
template<class InterfaceT>
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 4e5c8d6..1c5281d 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -133,6 +133,7 @@
session->close();
return res;
}
+ mSupportNativeZoomRatio = manager->supportNativeZoomRatio(mId.string());
std::vector<std::string> physicalCameraIds;
bool isLogical = manager->isLogicalCamera(mId.string(), &physicalCameraIds);
@@ -147,8 +148,11 @@
return res;
}
- if (DistortionMapper::isDistortionSupported(mPhysicalDeviceInfoMap[physicalId])) {
- mDistortionMappers[physicalId].setupStaticInfo(mPhysicalDeviceInfoMap[physicalId]);
+ bool usePrecorrectArray =
+ DistortionMapper::isDistortionSupported(mPhysicalDeviceInfoMap[physicalId]);
+ if (usePrecorrectArray) {
+ res = mDistortionMappers[physicalId].setupStaticInfo(
+ mPhysicalDeviceInfoMap[physicalId]);
if (res != OK) {
SET_ERR_L("Unable to read camera %s's calibration fields for distortion "
"correction", physicalId.c_str());
@@ -156,6 +160,15 @@
return res;
}
}
+
+ res = mZoomRatioMappers[physicalId].initZoomRatioTags(
+ &mPhysicalDeviceInfoMap[physicalId],
+ mSupportNativeZoomRatio, usePrecorrectArray);
+ if (res != OK) {
+ SET_ERR_L("Failed to initialize camera %s's zoomRatio tags: %s (%d)",
+ physicalId.c_str(), strerror(-res), res);
+ return res;
+ }
}
}
@@ -331,13 +344,23 @@
}
}
- if (DistortionMapper::isDistortionSupported(mDeviceInfo)) {
+ bool usePrecorrectArray = DistortionMapper::isDistortionSupported(mDeviceInfo);
+ if (usePrecorrectArray) {
res = mDistortionMappers[mId.c_str()].setupStaticInfo(mDeviceInfo);
if (res != OK) {
SET_ERR_L("Unable to read necessary calibration fields for distortion correction");
return res;
}
}
+
+ res = mZoomRatioMappers[mId.c_str()].initZoomRatioTags(&mDeviceInfo,
+ mSupportNativeZoomRatio, usePrecorrectArray);
+ if (res != OK) {
+ SET_ERR_L("Failed to initialize zoomRatio tags: %s (%d)",
+ strerror(-res), res);
+ return res;
+ }
+
return OK;
}
@@ -2124,6 +2147,15 @@
set_camera_metadata_vendor_id(rawRequest, mVendorTagId);
mRequestTemplateCache[templateId].acquire(rawRequest);
+ // Override the template request with zoomRatioMapper
+ res = mZoomRatioMappers[mId.c_str()].initZoomRatioInTemplate(
+ &mRequestTemplateCache[templateId]);
+ if (res != OK) {
+ CLOGE("Failed to update zoom ratio for template %d: %s (%d)",
+ templateId, strerror(-res), res);
+ return res;
+ }
+
*request = mRequestTemplateCache[templateId];
mLastTemplateId = templateId;
}
@@ -3146,14 +3178,15 @@
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
bool hasAppCallback, nsecs_t maxExpectedDuration,
std::set<String8>& physicalCameraIds, bool isStillCapture,
- bool isZslCapture, const SurfaceMap& outputSurfaces) {
+ bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
+ const SurfaceMap& outputSurfaces) {
ATRACE_CALL();
Mutex::Autolock l(mInFlightLock);
ssize_t res;
res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
hasAppCallback, maxExpectedDuration, physicalCameraIds, isStillCapture, isZslCapture,
- outputSurfaces));
+ cameraIdsWithZoom, outputSurfaces));
if (res < 0) return res;
if (mInFlightMap.size() == 1) {
@@ -3504,7 +3537,7 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult,
uint32_t frameNumber,
- bool reprocess, bool zslStillCapture,
+ bool reprocess, bool zslStillCapture, const std::set<std::string>& cameraIdsWithZoom,
const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas) {
ATRACE_CALL();
if (pendingMetadata.isEmpty())
@@ -3573,19 +3606,39 @@
mDistortionMappers[mId.c_str()].correctCaptureResult(&captureResult.mMetadata);
if (res != OK) {
SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
- frameNumber, strerror(res), res);
+ frameNumber, strerror(-res), res);
return;
}
+
+ // Fix up result metadata to account for zoom ratio availabilities between
+ // HAL and app.
+ bool zoomRatioIs1 = cameraIdsWithZoom.find(mId.c_str()) == cameraIdsWithZoom.end();
+ res = mZoomRatioMappers[mId.c_str()].updateCaptureResult(
+ &captureResult.mMetadata, zoomRatioIs1);
+ if (res != OK) {
+ SET_ERR("Failed to update capture result zoom ratio metadata for frame %d: %s (%d)",
+ frameNumber, strerror(-res), res);
+ return;
+ }
+
for (auto& physicalMetadata : captureResult.mPhysicalMetadatas) {
String8 cameraId8(physicalMetadata.mPhysicalCameraId);
- if (mDistortionMappers.find(cameraId8.c_str()) == mDistortionMappers.end()) {
- continue;
+ if (mDistortionMappers.find(cameraId8.c_str()) != mDistortionMappers.end()) {
+ res = mDistortionMappers[cameraId8.c_str()].correctCaptureResult(
+ &physicalMetadata.mPhysicalCameraMetadata);
+ if (res != OK) {
+ SET_ERR("Unable to correct physical capture result metadata for frame %d: %s (%d)",
+ frameNumber, strerror(-res), res);
+ return;
+ }
}
- res = mDistortionMappers[cameraId8.c_str()].correctCaptureResult(
- &physicalMetadata.mPhysicalCameraMetadata);
+
+ zoomRatioIs1 = cameraIdsWithZoom.find(cameraId8.c_str()) == cameraIdsWithZoom.end();
+ res = mZoomRatioMappers[cameraId8.c_str()].updateCaptureResult(
+ &physicalMetadata.mPhysicalCameraMetadata, zoomRatioIs1);
if (res != OK) {
- SET_ERR("Unable to correct physical capture result metadata for frame %d: %s (%d)",
- frameNumber, strerror(res), res);
+ SET_ERR("Failed to update camera %s's physical zoom ratio metadata for "
+ "frame %d: %s(%d)", cameraId8.c_str(), frameNumber, strerror(-res), res);
return;
}
}
@@ -3790,7 +3843,7 @@
sendCaptureResult(metadata, request.resultExtras,
collectedPartialResult, frameNumber,
hasInputBufferInRequest, request.zslCapture && request.stillCapture,
- request.physicalMetadatas);
+ request.cameraIdsWithZoom, request.physicalMetadatas);
}
}
@@ -4008,7 +4061,7 @@
sendCaptureResult(r.pendingMetadata, r.resultExtras,
r.collectedPartialResult, msg.frame_number,
r.hasInputBuffer, r.zslCapture && r.stillCapture,
- r.physicalMetadatas);
+ r.cameraIdsWithZoom, r.physicalMetadatas);
}
bool timestampIncreasing = !(r.zslCapture || r.hasInputBuffer);
returnOutputBuffers(r.pendingOutputBuffers.array(),
@@ -5610,6 +5663,7 @@
// request in a batch as new
!(batchedRequest && i > 0);
if (newRequest) {
+ std::set<std::string> cameraIdsWithZoom;
/**
* HAL workaround:
* Insert a dummy trigger ID if a trigger is set but no trigger ID is
@@ -5642,6 +5696,28 @@
return INVALID_OPERATION;
}
}
+
+ for (it = captureRequest->mSettingsList.begin();
+ it != captureRequest->mSettingsList.end(); it++) {
+ if (parent->mZoomRatioMappers.find(it->cameraId) ==
+ parent->mZoomRatioMappers.end()) {
+ continue;
+ }
+
+ camera_metadata_entry_t e = it->metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (e.count > 0 && e.data.f[0] != 1.0f) {
+ cameraIdsWithZoom.insert(it->cameraId);
+ }
+
+ res = parent->mZoomRatioMappers[it->cameraId].updateCaptureRequest(
+ &(it->metadata));
+ if (res != OK) {
+ SET_ERR("RequestThread: Unable to correct capture requests "
+ "for zoom ratio for request %d: %s (%d)",
+ halRequest->frame_number, strerror(-res), res);
+ return INVALID_OPERATION;
+ }
+ }
}
}
@@ -5652,6 +5728,7 @@
captureRequest->mSettingsList.begin()->metadata.sort();
halRequest->settings = captureRequest->mSettingsList.begin()->metadata.getAndLock();
mPrevRequest = captureRequest;
+ mPrevCameraIdsWithZoom = cameraIdsWithZoom;
ALOGVV("%s: Request settings are NEW", __FUNCTION__);
IF_ALOGV() {
@@ -5839,7 +5916,7 @@
/*hasInput*/halRequest->input_buffer != NULL,
hasCallback,
calculateMaxExpectedDuration(halRequest->settings),
- requestedPhysicalCameras, isStillCapture, isZslCapture,
+ requestedPhysicalCameras, isStillCapture, isZslCapture, mPrevCameraIdsWithZoom,
(mUseHalBufManager) ? uniqueSurfaceIdMap :
SurfaceMap{});
ALOGVV("%s: registered in flight requestId = %" PRId32 ", frameNumber = %" PRId64
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index eabc44d..5faabd1 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -46,6 +46,7 @@
#include "device3/StatusTracker.h"
#include "device3/Camera3BufferManager.h"
#include "device3/DistortionMapper.h"
+#include "device3/ZoomRatioMapper.h"
#include "utils/TagMonitor.h"
#include "utils/LatencyHistogram.h"
#include <camera_metadata_hidden.h>
@@ -442,6 +443,7 @@
sp<HalInterface> mInterface;
CameraMetadata mDeviceInfo;
+ bool mSupportNativeZoomRatio;
std::unordered_map<std::string, CameraMetadata> mPhysicalDeviceInfoMap;
CameraMetadata mRequestTemplateCache[CAMERA3_TEMPLATE_COUNT];
@@ -980,6 +982,7 @@
sp<CaptureRequest> mPrevRequest;
int32_t mPrevTriggers;
+ std::set<std::string> mPrevCameraIdsWithZoom;
uint32_t mFrameNumber;
@@ -1078,6 +1081,9 @@
// Indicates a ZSL capture request
bool zslCapture;
+ // Requested camera ids (both logical and physical) with zoomRatio != 1.0f
+ std::set<std::string> cameraIdsWithZoom;
+
// What shared surfaces an output should go to
SurfaceMap outputSurfaces;
@@ -1099,7 +1105,7 @@
InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
bool hasAppCallback, nsecs_t maxDuration,
const std::set<String8>& physicalCameraIdSet, bool isStillCapture,
- bool isZslCapture,
+ bool isZslCapture, const std::set<std::string>& idsWithZoom,
const SurfaceMap& outSurfaces = SurfaceMap{}) :
shutterTimestamp(0),
sensorTimestamp(0),
@@ -1114,6 +1120,7 @@
physicalCameraIds(physicalCameraIdSet),
stillCapture(isStillCapture),
zslCapture(isZslCapture),
+ cameraIdsWithZoom(idsWithZoom),
outputSurfaces(outSurfaces) {
}
};
@@ -1131,7 +1138,7 @@
status_t registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
bool callback, nsecs_t maxExpectedDuration, std::set<String8>& physicalCameraIds,
- bool isStillCapture, bool isZslCapture,
+ bool isStillCapture, bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
const SurfaceMap& outputSurfaces);
/**
@@ -1256,6 +1263,7 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult, uint32_t frameNumber,
bool reprocess, bool zslStillCapture,
+ const std::set<std::string>& cameraIdsWithZoom,
const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas);
bool isLastFullResult(const InFlightRequest& inFlightRequest);
@@ -1288,6 +1296,11 @@
// logical camera and its physical subcameras.
std::unordered_map<std::string, camera3::DistortionMapper> mDistortionMappers;
+ /**
+ * Zoom ratio mapper support
+ */
+ std::unordered_map<std::string, camera3::ZoomRatioMapper> mZoomRatioMappers;
+
// Debug tracker for metadata tag value changes
// - Enabled with the -m <taglist> option to dumpsys, such as
// dumpsys -m android.control.aeState,android.control.aeMode
diff --git a/services/camera/libcameraservice/device3/CoordinateMapper.cpp b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
new file mode 100644
index 0000000..d62f397
--- /dev/null
+++ b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <system/camera_metadata_tags.h>
+
+#include "device3/CoordinateMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+/**
+ * Metadata keys to correct when adjusting coordinates for distortion correction
+ */
+
+// Both capture request and result
+constexpr std::array<uint32_t, 3> CoordinateMapper::kMeteringRegionsToCorrect = {
+ ANDROID_CONTROL_AF_REGIONS,
+ ANDROID_CONTROL_AE_REGIONS,
+ ANDROID_CONTROL_AWB_REGIONS
+};
+
+// Both capture request and result
+constexpr std::array<uint32_t, 1> CoordinateMapper::kRectsToCorrect = {
+ ANDROID_SCALER_CROP_REGION,
+};
+
+// Only for capture result
+constexpr std::array<uint32_t, 2> CoordinateMapper::kResultPointsToCorrectNoClamp = {
+ ANDROID_STATISTICS_FACE_RECTANGLES, // Says rectangles, is really points
+ ANDROID_STATISTICS_FACE_LANDMARKS,
+};
+
+} // namespace camera3
+
+} // namespace android
diff --git a/services/camera/libcameraservice/device3/CoordinateMapper.h b/services/camera/libcameraservice/device3/CoordinateMapper.h
new file mode 100644
index 0000000..5164856
--- /dev/null
+++ b/services/camera/libcameraservice/device3/CoordinateMapper.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_COORDINATEMAPPER_H
+#define ANDROID_SERVERS_COORDINATEMAPPER_H
+
+#include <array>
+
+namespace android {
+
+namespace camera3 {
+
+class CoordinateMapper {
+ // Right now only stores metadata tags containing 2D coordinates
+ // to be corrected.
+protected:
+ // Metadata key lists to correct
+
+ // Both capture request and result
+ static const std::array<uint32_t, 3> kMeteringRegionsToCorrect;
+
+ // Both capture request and result
+ static const std::array<uint32_t, 1> kRectsToCorrect;
+
+ // Only for capture results; don't clamp
+ static const std::array<uint32_t, 2> kResultPointsToCorrectNoClamp;
+}; // class CoordinateMapper
+
+} // namespace camera3
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/device3/DistortionMapper.cpp b/services/camera/libcameraservice/device3/DistortionMapper.cpp
index ae7af8e..8132225 100644
--- a/services/camera/libcameraservice/device3/DistortionMapper.cpp
+++ b/services/camera/libcameraservice/device3/DistortionMapper.cpp
@@ -27,41 +27,14 @@
namespace camera3 {
-/**
- * Metadata keys to correct when adjusting coordinates for distortion correction
- */
-
-// Both capture request and result
-constexpr std::array<uint32_t, 3> DistortionMapper::kMeteringRegionsToCorrect = {
- ANDROID_CONTROL_AF_REGIONS,
- ANDROID_CONTROL_AE_REGIONS,
- ANDROID_CONTROL_AWB_REGIONS
-};
-
-// Only capture request
-constexpr std::array<uint32_t, 1> DistortionMapper::kRequestRectsToCorrect = {
- ANDROID_SCALER_CROP_REGION,
-};
-
-// Only for capture result
-constexpr std::array<uint32_t, 1> DistortionMapper::kResultRectsToCorrect = {
- ANDROID_SCALER_CROP_REGION,
-};
-
-// Only for capture result
-constexpr std::array<uint32_t, 2> DistortionMapper::kResultPointsToCorrectNoClamp = {
- ANDROID_STATISTICS_FACE_RECTANGLES, // Says rectangles, is really points
- ANDROID_STATISTICS_FACE_LANDMARKS,
-};
-
DistortionMapper::DistortionMapper() : mValidMapping(false), mValidGrids(false) {
}
-bool DistortionMapper::isDistortionSupported(const CameraMetadata &result) {
+bool DistortionMapper::isDistortionSupported(const CameraMetadata &deviceInfo) {
bool isDistortionCorrectionSupported = false;
camera_metadata_ro_entry_t distortionCorrectionModes =
- result.find(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES);
+ deviceInfo.find(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES);
for (size_t i = 0; i < distortionCorrectionModes.count; i++) {
if (distortionCorrectionModes.data.u8[i] !=
ANDROID_DISTORTION_CORRECTION_MODE_OFF) {
@@ -124,7 +97,7 @@
if (res != OK) return res;
}
}
- for (auto rect : kRequestRectsToCorrect) {
+ for (auto rect : kRectsToCorrect) {
e = request->find(rect);
res = mapCorrectedRectToRaw(e.data.i32, e.count / 4, /*clamp*/true);
if (res != OK) return res;
@@ -160,7 +133,7 @@
if (res != OK) return res;
}
}
- for (auto rect : kResultRectsToCorrect) {
+ for (auto rect : kRectsToCorrect) {
e = result->find(rect);
res = mapRawRectToCorrected(e.data.i32, e.count / 4, /*clamp*/true);
if (res != OK) return res;
@@ -390,7 +363,6 @@
return OK;
}
-
status_t DistortionMapper::mapCorrectedRectToRaw(int32_t *rects, int rectCount, bool clamp,
bool simple) const {
if (!mValidMapping) return INVALID_OPERATION;
diff --git a/services/camera/libcameraservice/device3/DistortionMapper.h b/services/camera/libcameraservice/device3/DistortionMapper.h
index 4c0a1a6..a255003 100644
--- a/services/camera/libcameraservice/device3/DistortionMapper.h
+++ b/services/camera/libcameraservice/device3/DistortionMapper.h
@@ -22,6 +22,7 @@
#include <mutex>
#include "camera/CameraMetadata.h"
+#include "device3/CoordinateMapper.h"
namespace android {
@@ -31,7 +32,7 @@
* Utilities to transform between raw (distorted) and warped (corrected) coordinate systems
* for cameras that support geometric distortion
*/
-class DistortionMapper {
+class DistortionMapper : private CoordinateMapper {
public:
DistortionMapper();
@@ -150,20 +151,6 @@
// Fuzziness for float inequality tests
constexpr static float kFloatFuzz = 1e-4;
- // Metadata key lists to correct
-
- // Both capture request and result
- static const std::array<uint32_t, 3> kMeteringRegionsToCorrect;
-
- // Only capture request
- static const std::array<uint32_t, 1> kRequestRectsToCorrect;
-
- // Only capture result
- static const std::array<uint32_t, 1> kResultRectsToCorrect;
-
- // Only for capture results; don't clamp
- static const std::array<uint32_t, 2> kResultPointsToCorrectNoClamp;
-
// Single implementation for various mapCorrectedToRaw methods
template<typename T>
status_t mapCorrectedToRawImpl(T* coordPairs, int coordCount, bool clamp, bool simple) const;
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
new file mode 100644
index 0000000..7718819
--- /dev/null
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Camera3-ZoomRatioMapper"
+//#define LOG_NDEBUG 0
+
+#include <algorithm>
+
+#include "device3/ZoomRatioMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+ZoomRatioMapper::ZoomRatioMapper() : mHalSupportsZoomRatio(false) {
+}
+
+status_t ZoomRatioMapper::initZoomRatioInTemplate(CameraMetadata *request) {
+ camera_metadata_entry_t entry;
+ entry = request->find(ANDROID_CONTROL_ZOOM_RATIO);
+ float defaultZoomRatio = 1.0f;
+ if (entry.count == 0) {
+ return request->update(ANDROID_CONTROL_ZOOM_RATIO, &defaultZoomRatio, 1);
+ }
+ return OK;
+}
+
+status_t ZoomRatioMapper::overrideZoomRatioTags(
+ CameraMetadata* deviceInfo, bool* supportNativeZoomRatio) {
+ if (deviceInfo == nullptr || supportNativeZoomRatio == nullptr) {
+ return BAD_VALUE;
+ }
+
+ camera_metadata_entry_t entry;
+ entry = deviceInfo->find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ if (entry.count != 2 && entry.count != 0) return BAD_VALUE;
+
+ // Hal has zoom ratio support
+ if (entry.count == 2) {
+ *supportNativeZoomRatio = true;
+ return OK;
+ }
+
+ // Hal has no zoom ratio support
+ *supportNativeZoomRatio = false;
+
+ entry = deviceInfo->find(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+ if (entry.count != 1) {
+ ALOGI("%s: Camera device doesn't support SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key!",
+ __FUNCTION__);
+ return OK;
+ }
+
+ float zoomRange[] = {1.0f, entry.data.f[0]};
+ status_t res = deviceInfo->update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, zoomRange, 2);
+ if (res != OK) {
+ ALOGE("%s: Failed to update CONTROL_ZOOM_RATIO_RANGE key: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> requestKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
+ if (entry.count > 0) {
+ requestKeys.insert(requestKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ requestKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS,
+ requestKeys.data(), requestKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_REQUEST_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> resultKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
+ if (entry.count > 0) {
+ resultKeys.insert(resultKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ resultKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS,
+ resultKeys.data(), resultKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_RESULT_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> charKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+ if (entry.count > 0) {
+ charKeys.insert(charKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ charKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+ charKeys.data(), charKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_CHARACTERISTICS_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ return OK;
+}
+
+status_t ZoomRatioMapper::initZoomRatioTags(const CameraMetadata* deviceInfo,
+ bool supportNativeZoomRatio, bool usePrecorrectArray) {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ camera_metadata_ro_entry_t entry;
+
+ entry = deviceInfo->find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+ if (entry.count != 4) return BAD_VALUE;
+ int32_t arrayW = entry.data.i32[2];
+ int32_t arrayH = entry.data.i32[3];
+
+ entry = deviceInfo->find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ if (entry.count != 4) return BAD_VALUE;
+ int32_t activeW = entry.data.i32[2];
+ int32_t activeH = entry.data.i32[3];
+
+ if (usePrecorrectArray) {
+ mArrayWidth = arrayW;
+ mArrayHeight = arrayH;
+ } else {
+ mArrayWidth = activeW;
+ mArrayHeight = activeH;
+ }
+ mHalSupportsZoomRatio = supportNativeZoomRatio;
+
+ ALOGV("%s: array size: %d x %d, mHalSupportsZoomRatio %d",
+ __FUNCTION__, mArrayWidth, mArrayHeight, mHalSupportsZoomRatio);
+ return OK;
+}
+
+status_t ZoomRatioMapper::updateCaptureRequest(CameraMetadata* request) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ status_t res = OK;
+ bool zoomRatioIs1 = true;
+ camera_metadata_entry_t entry;
+
+ entry = request->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1 && entry.data.f[0] != 1.0f) {
+ zoomRatioIs1 = false;
+ }
+
+ if (mHalSupportsZoomRatio && zoomRatioIs1) {
+ res = separateZoomFromCropLocked(request, false/*isResult*/);
+ } else if (!mHalSupportsZoomRatio && !zoomRatioIs1) {
+ res = combineZoomAndCropLocked(request, false/*isResult*/);
+ }
+
+ // If CONTROL_ZOOM_RATIO is in request, but HAL doesn't support
+ // CONTROL_ZOOM_RATIO, remove it from the request.
+ if (!mHalSupportsZoomRatio && entry.count == 1) {
+ request->erase(ANDROID_CONTROL_ZOOM_RATIO);
+ }
+
+ return res;
+}
+
+status_t ZoomRatioMapper::updateCaptureResult(CameraMetadata* result, bool requestedZoomRatioIs1) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ status_t res = OK;
+
+ if (mHalSupportsZoomRatio && requestedZoomRatioIs1) {
+ res = combineZoomAndCropLocked(result, true/*isResult*/);
+ } else if (!mHalSupportsZoomRatio && !requestedZoomRatioIs1) {
+ res = separateZoomFromCropLocked(result, true/*isResult*/);
+ } else {
+ camera_metadata_entry_t entry = result->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 0) {
+ float zoomRatio1x = 1.0f;
+ result->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio1x, 1);
+ }
+ }
+
+ return res;
+}
+
+float ZoomRatioMapper::deriveZoomRatio(const CameraMetadata* metadata) {
+ float zoomRatio = 1.0;
+
+ camera_metadata_ro_entry_t entry;
+ entry = metadata->find(ANDROID_SCALER_CROP_REGION);
+ if (entry.count != 4) return zoomRatio;
+
+ // Center of the preCorrection/active size
+ float arrayCenterX = mArrayWidth / 2.0;
+ float arrayCenterY = mArrayHeight / 2.0;
+
+ // Re-map crop region to coordinate system centered to (arrayCenterX,
+ // arrayCenterY).
+ float cropRegionLeft = arrayCenterX - entry.data.i32[0] ;
+ float cropRegionTop = arrayCenterY - entry.data.i32[1];
+ float cropRegionRight = entry.data.i32[0] + entry.data.i32[2] - arrayCenterX;
+ float cropRegionBottom = entry.data.i32[1] + entry.data.i32[3] - arrayCenterY;
+
+ // Calculate the scaling factor for left, top, bottom, right
+ float zoomRatioLeft = std::max(mArrayWidth / (2 * cropRegionLeft), 1.0f);
+ float zoomRatioTop = std::max(mArrayHeight / (2 * cropRegionTop), 1.0f);
+ float zoomRatioRight = std::max(mArrayWidth / (2 * cropRegionRight), 1.0f);
+ float zoomRatioBottom = std::max(mArrayHeight / (2 * cropRegionBottom), 1.0f);
+
+ // Use minimum scaling factor to handle letterboxing or pillarboxing
+ zoomRatio = std::min(std::min(zoomRatioLeft, zoomRatioRight),
+ std::min(zoomRatioTop, zoomRatioBottom));
+
+ ALOGV("%s: derived zoomRatio is %f", __FUNCTION__, zoomRatio);
+ return zoomRatio;
+}
+
+status_t ZoomRatioMapper::separateZoomFromCropLocked(CameraMetadata* metadata, bool isResult) {
+ status_t res;
+ float zoomRatio = deriveZoomRatio(metadata);
+
+ // Update zoomRatio metadata tag
+ res = metadata->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ if (res != OK) {
+ ALOGE("%s: Failed to update ANDROID_CONTROL_ZOOM_RATIO: %s(%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ // Scale regions using zoomRatio
+ camera_metadata_entry_t entry;
+ for (auto region : kMeteringRegionsToCorrect) {
+ entry = metadata->find(region);
+ for (size_t j = 0; j < entry.count; j += 5) {
+ int32_t weight = entry.data.i32[j + 4];
+ if (weight == 0) {
+ continue;
+ }
+ // Top-left is inclusively clamped
+ scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, ClampInclusive);
+ // Bottom-right is exclusively clamped
+ scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, ClampExclusive);
+ }
+ }
+
+ for (auto rect : kRectsToCorrect) {
+ entry = metadata->find(rect);
+ scaleRects(entry.data.i32, entry.count / 4, zoomRatio);
+ }
+
+ if (isResult) {
+ for (auto pts : kResultPointsToCorrectNoClamp) {
+ entry = metadata->find(pts);
+ scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, ClampOff);
+ }
+ }
+
+ return OK;
+}
+
+status_t ZoomRatioMapper::combineZoomAndCropLocked(CameraMetadata* metadata, bool isResult) {
+ float zoomRatio = 1.0f;
+ camera_metadata_entry_t entry;
+ entry = metadata->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1) {
+ zoomRatio = entry.data.f[0];
+ }
+
+ // Unscale regions with zoomRatio
+ status_t res;
+ for (auto region : kMeteringRegionsToCorrect) {
+ entry = metadata->find(region);
+ for (size_t j = 0; j < entry.count; j += 5) {
+ int32_t weight = entry.data.i32[j + 4];
+ if (weight == 0) {
+ continue;
+ }
+ // Top-left is inclusively clamped
+ scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, ClampInclusive);
+ // Bottom-right is exclusively clamped
+ scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, ClampExclusive);
+ }
+ }
+ for (auto rect : kRectsToCorrect) {
+ entry = metadata->find(rect);
+ scaleRects(entry.data.i32, entry.count / 4, 1.0 / zoomRatio);
+ }
+ if (isResult) {
+ for (auto pts : kResultPointsToCorrectNoClamp) {
+ entry = metadata->find(pts);
+ scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, ClampOff);
+ }
+ }
+
+ zoomRatio = 1.0;
+ res = metadata->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ if (res != OK) {
+ return res;
+ }
+
+ return OK;
+}
+
+void ZoomRatioMapper::scaleCoordinates(int32_t* coordPairs, int coordCount,
+ float scaleRatio, ClampMode clamp) {
+ for (int i = 0; i < coordCount * 2; i += 2) {
+ float x = coordPairs[i];
+ float y = coordPairs[i + 1];
+ float xCentered = x - mArrayWidth / 2;
+ float yCentered = y - mArrayHeight / 2;
+ float scaledX = xCentered * scaleRatio;
+ float scaledY = yCentered * scaleRatio;
+ scaledX += mArrayWidth / 2;
+ scaledY += mArrayHeight / 2;
+ // Clamp to within activeArray/preCorrectionActiveArray
+ coordPairs[i] = static_cast<int32_t>(scaledX);
+ coordPairs[i+1] = static_cast<int32_t>(scaledY);
+ if (clamp != ClampOff) {
+ int32_t right, bottom;
+ if (clamp == ClampInclusive) {
+ right = mArrayWidth - 1;
+ bottom = mArrayHeight - 1;
+ } else {
+ right = mArrayWidth;
+ bottom = mArrayHeight;
+ }
+ coordPairs[i] =
+ std::min(right, std::max(0, coordPairs[i]));
+ coordPairs[i+1] =
+ std::min(bottom, std::max(0, coordPairs[i+1]));
+ }
+ ALOGV("%s: coordinates: %d, %d", __FUNCTION__, coordPairs[i], coordPairs[i+1]);
+ }
+}
+
+void ZoomRatioMapper::scaleRects(int32_t* rects, int rectCount,
+ float scaleRatio) {
+ for (int i = 0; i < rectCount * 4; i += 4) {
+ // Map from (l, t, width, height) to (l, t, r, b).
+ // [l, t] is inclusive, and [r, b] is exclusive.
+ int32_t coords[4] = {
+ rects[i],
+ rects[i + 1],
+ rects[i] + rects[i + 2],
+ rects[i + 1] + rects[i + 3]
+ };
+
+ // top-left
+ scaleCoordinates(coords, 1, scaleRatio, ClampInclusive);
+ // bottom-right
+ scaleCoordinates(coords+2, 1, scaleRatio, ClampExclusive);
+
+ // Map back to (l, t, width, height)
+ rects[i] = coords[0];
+ rects[i + 1] = coords[1];
+ rects[i + 2] = coords[2] - coords[0];
+ rects[i + 3] = coords[3] - coords[1];
+ }
+}
+
+} // namespace camera3
+
+} // namespace android
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.h b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
new file mode 100644
index 0000000..38efbfd
--- /dev/null
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_ZOOMRATIOMAPPER_H
+#define ANDROID_SERVERS_ZOOMRATIOMAPPER_H
+
+#include <utils/Errors.h>
+#include <array>
+#include <mutex>
+
+#include "camera/CameraMetadata.h"
+#include "device3/CoordinateMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+/**
+ * Utilities to convert between the new zoomRatio and existing cropRegion
+ * metadata tags. Note that this class does conversions in 2 scenarios:
+ * - HAL supports zoomRatio and the application uses cropRegion, or
+ * - HAL doesn't support zoomRatio, but the application uses zoomRatio
+ */
+class ZoomRatioMapper : private CoordinateMapper {
+ public:
+ ZoomRatioMapper();
+ ZoomRatioMapper(const ZoomRatioMapper& other) :
+ mHalSupportsZoomRatio(other.mHalSupportsZoomRatio),
+ mArrayWidth(other.mArrayWidth), mArrayHeight(other.mArrayHeight) {}
+
+ /**
+ * Initialize request template with valid zoomRatio if necessary.
+ */
+ static status_t initZoomRatioInTemplate(CameraMetadata *request);
+
+ /**
+ * Override zoomRatio related tags in the static metadata.
+ */
+ static status_t overrideZoomRatioTags(
+ CameraMetadata* deviceInfo, bool* supportNativeZoomRatio);
+
+ /**
+ * Initialize zoom ratio mapper with static metadata.
+ *
+ * Note:
+ * This function may modify the static metadata with zoomRatio related
+ * tags.
+ */
+ status_t initZoomRatioTags(const CameraMetadata *deviceInfo,
+ bool supportNativeZoomRatio, bool usePrecorrectArray);
+
+ /**
+ * Update capture request to handle both cropRegion and zoomRatio.
+ */
+ status_t updateCaptureRequest(CameraMetadata *request);
+
+ /**
+ * Update capture result to handle both cropRegion and zoomRatio.
+ */
+ status_t updateCaptureResult(CameraMetadata *request, bool requestedZoomRatioIs1);
+
+ public: // Visible for testing. Do not use concurently.
+ enum ClampMode {
+ ClampOff,
+ ClampInclusive,
+ ClampExclusive,
+ };
+
+ void scaleCoordinates(int32_t* coordPairs, int coordCount,
+ float scaleRatio, ClampMode clamp);
+
+ private:
+ bool mHalSupportsZoomRatio;
+
+ // active array / pre-correction array dimension
+ int32_t mArrayWidth, mArrayHeight;
+
+ mutable std::mutex mMutex;
+
+ float deriveZoomRatio(const CameraMetadata* metadata);
+
+ void scaleRects(int32_t* rects, int rectCount, float scaleRatio);
+
+ status_t separateZoomFromCropLocked(CameraMetadata* metadata, bool isResult);
+ status_t combineZoomAndCropLocked(CameraMetadata* metadata, bool isResult);
+};
+
+} // namespace camera3
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
new file mode 100644
index 0000000..1bfa03f
--- /dev/null
+++ b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "ZoomRatioMapperTest"
+
+#include <gtest/gtest.h>
+#include <utils/Errors.h>
+
+#include "../device3/ZoomRatioMapper.h"
+
+using namespace std;
+using namespace android;
+using namespace android::camera3;
+
+constexpr int kMaxAllowedPixelError = 1;
+constexpr float kMaxAllowedRatioError = 0.1;
+
+constexpr int32_t testActiveArraySize[] = {100, 100, 1024, 768};
+constexpr int32_t testPreCorrActiveArraySize[] = {90, 90, 1044, 788};
+
+constexpr int32_t testDefaultCropSize[][4] = {
+ {0, 0, 1024, 768}, // active array default crop
+ {0, 0, 1044, 788}, // preCorrection active array default crop
+};
+
+constexpr int32_t test2xCropRegion[][4] = {
+ {256, 192, 512, 384}, // active array 2x zoom crop
+ {261, 197, 522, 394}, // preCorrection active array default crop
+};
+
+constexpr int32_t testLetterBoxSize[][4] = {
+ {0, 96, 1024, 576}, // active array 2x zoom crop
+ {0, 106, 1024, 576}, // preCorrection active array default crop
+};
+
+status_t setupTestMapper(ZoomRatioMapper *m, float maxDigitalZoom,
+ const int32_t activeArray[4], const int32_t preCorrectArray[4],
+ bool hasZoomRatioRange, float zoomRatioRange[2],
+ bool usePreCorrectArray) {
+ CameraMetadata deviceInfo;
+
+ deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, activeArray, 4);
+ deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, preCorrectArray, 4);
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, &maxDigitalZoom, 1);
+ if (hasZoomRatioRange) {
+ deviceInfo.update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, zoomRatioRange, 2);
+ }
+
+ bool supportNativeZoomRatio;
+ status_t res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ if (res != OK) {
+ return res;
+ }
+
+ return m->initZoomRatioTags(&deviceInfo, hasZoomRatioRange, usePreCorrectArray);
+}
+
+TEST(ZoomRatioTest, Initialization) {
+ CameraMetadata deviceInfo;
+ status_t res;
+ camera_metadata_entry_t entry;
+
+ deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+ testPreCorrActiveArraySize, 4);
+ deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, testActiveArraySize, 4);
+
+ // Test initialization from devices not supporting zoomRange
+ float maxDigitalZoom = 4.0f;
+ ZoomRatioMapper mapperNoZoomRange;
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, &maxDigitalZoom, 1);
+ bool supportNativeZoomRatio;
+ res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ ASSERT_EQ(res, OK);
+ ASSERT_EQ(supportNativeZoomRatio, false);
+ res = mapperNoZoomRange.initZoomRatioTags(&deviceInfo,
+ supportNativeZoomRatio, true/*usePreCorrectArray*/);
+ ASSERT_EQ(res, OK);
+ res = mapperNoZoomRange.initZoomRatioTags(&deviceInfo,
+ supportNativeZoomRatio, false/*usePreCorrectArray*/);
+ ASSERT_EQ(res, OK);
+
+ entry = deviceInfo.find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ ASSERT_EQ(entry.count, 2U);
+ ASSERT_EQ(entry.data.f[0], 1.0);
+ ASSERT_EQ(entry.data.f[1], maxDigitalZoom);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO_RANGE), entry.data.i32 + entry.count);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO), entry.data.i32 + entry.count);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO), entry.data.i32 + entry.count);
+
+ // Test initialization from devices supporting zoomRange
+ float ratioRange[2] = {0.2f, maxDigitalZoom};
+ deviceInfo.update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, ratioRange, 2);
+ res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ ASSERT_EQ(res, OK);
+ ASSERT_EQ(supportNativeZoomRatio, true);
+ ZoomRatioMapper mapperWithZoomRange;
+ res = mapperWithZoomRange.initZoomRatioTags(&deviceInfo,
+ supportNativeZoomRatio, true/*usePreCorrectArray*/);
+ ASSERT_EQ(res, OK);
+ res = mapperWithZoomRange.initZoomRatioTags(&deviceInfo,
+ supportNativeZoomRatio, false/*usePreCorrectArray*/);
+ ASSERT_EQ(res, OK);
+
+ entry = deviceInfo.find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ ASSERT_EQ(entry.count, 2U);
+ ASSERT_EQ(entry.data.f[0], ratioRange[0]);
+ ASSERT_EQ(entry.data.f[1], ratioRange[1]);
+
+ // Test default zoom ratio in template
+ CameraMetadata requestTemplate;
+ res = ZoomRatioMapper::initZoomRatioInTemplate(&requestTemplate);
+ ASSERT_EQ(res, OK);
+ entry = requestTemplate.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.count, 1U);
+ ASSERT_EQ(entry.data.f[0], 1.0f);
+
+ float customRatio = 0.5f;
+ res = requestTemplate.update(ANDROID_CONTROL_ZOOM_RATIO, &customRatio, 1);
+ ASSERT_EQ(res, OK);
+ res = ZoomRatioMapper::initZoomRatioInTemplate(&requestTemplate);
+ ASSERT_EQ(res, OK);
+ entry = requestTemplate.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.count, 1U);
+ ASSERT_EQ(entry.data.f[0], customRatio);
+}
+
+void subScaleCoordinatesTest(bool usePreCorrectArray) {
+ ZoomRatioMapper mapper;
+ float maxDigitalZoom = 4.0f;
+ float zoomRatioRange[2];
+ ASSERT_EQ(OK, setupTestMapper(&mapper, maxDigitalZoom,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray));
+
+ size_t index = 0;
+ int32_t width = testActiveArraySize[2];
+ int32_t height = testActiveArraySize[3];
+ if (usePreCorrectArray) {
+ index = 1;
+ width = testPreCorrActiveArraySize[2];
+ height = testPreCorrActiveArraySize[3];
+ }
+
+ std::array<int32_t, 16> originalCoords = {
+ 0, 0, // top-left
+ width, 0, // top-right
+ 0, height, // bottom-left
+ width, height, // bottom-right
+ width / 2, height / 2, // center
+ width / 4, height / 4, // top-left after 2x
+ width / 3, height * 2 / 3, // bottom-left after 3x zoom
+ width * 7 / 8, height / 2, // middle-right after 1.33x zoom
+ };
+
+ // Verify 1.0x zoom doesn't change the coordinates
+ auto coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_EQ(coords[i], originalCoords[i]);
+ }
+
+ // Verify 2.0x zoom work as expected (no clamping)
+ std::array<float, 16> expected2xCoords = {
+ - width / 2.0f, - height / 2.0f,// top-left
+ width * 3 / 2.0f, - height / 2.0f, // top-right
+ - width / 2.0f, height * 3 / 2.0f, // bottom-left
+ width * 3 / 2.0f, height * 3 / 2.0f, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f, // bottom-left after 3x zoom
+ width + width / 4.0f, height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoords[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 2.0x zoom work as expected (with inclusive clamping)
+ std::array<float, 16> expected2xCoordsClampedInc = {
+ 0, 0, // top-left
+ static_cast<float>(width) - 1, 0, // top-right
+ 0, static_cast<float>(height) - 1, // bottom-left
+ static_cast<float>(width) - 1, static_cast<float>(height) - 1, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
+ static_cast<float>(width) - 1, height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampInclusive);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedInc[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 2.0x zoom work as expected (with exclusive clamping)
+ std::array<float, 16> expected2xCoordsClampedExc = {
+ 0, 0, // top-left
+ static_cast<float>(width), 0, // top-right
+ 0, static_cast<float>(height), // bottom-left
+ static_cast<float>(width), static_cast<float>(height), // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
+ static_cast<float>(width), height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampExclusive);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedExc[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 0.33x zoom work as expected
+ std::array<float, 16> expectedZoomOutCoords = {
+ width / 3.0f, height / 3.0f, // top-left
+ width * 2 / 3.0f, height / 3.0f, // top-right
+ width / 3.0f, height * 2 / 3.0f, // bottom-left
+ width * 2 / 3.0f, height * 2 / 3.0f, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ width * 5 / 12.0f, height * 5 / 12.0f, // top-left after 2x
+ width * 4 / 9.0f, height * 5 / 9.0f, // bottom-left after 3x zoom-in
+ width * 5 / 8.0f, height / 2.0f, // middle-right after 1.33x zoom-in
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expectedZoomOutCoords[i]), kMaxAllowedPixelError);
+ }
+}
+
+TEST(ZoomRatioTest, scaleCoordinatesTest) {
+ subScaleCoordinatesTest(false/*usePreCorrectArray*/);
+ subScaleCoordinatesTest(true/*usePreCorrectArray*/);
+}
+
+void subCropOverMaxDigitalZoomTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float noZoomRatioRange[2];
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, noZoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+
+ metadata.update(ANDROID_SCALER_CROP_REGION, test2xCropRegion[index], 4);
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_TRUE(entry.count == 0 || (entry.count == 1 && entry.data.f[0] == 1.0f));
+}
+
+TEST(ZoomRatioTest, CropOverMaxDigitalZoomTest) {
+ subCropOverMaxDigitalZoomTest(false/*usePreCorrectArray*/);
+ subCropOverMaxDigitalZoomTest(true/*usePreCorrectArray*/);
+}
+
+void subCropOverZoomRangeTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float zoomRatioRange[2] = {0.5f, 4.0f};
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ true/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // 2x zoom crop region, zoomRatio is 1.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, test2xCropRegion[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 2.0f, kMaxAllowedRatioError);
+
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+ }
+
+ // Letter boxing crop region, zoomRatio is 1.0
+ float zoomRatio = 1.0f;
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ metadata.update(ANDROID_SCALER_CROP_REGION, testLetterBoxSize[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testLetterBoxSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testLetterBoxSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+}
+
+TEST(ZoomRatioTest, CropOverZoomRangeTest) {
+ subCropOverZoomRangeTest(false/*usePreCorrectArray*/);
+ subCropOverZoomRangeTest(true/*usePreCorrectArray*/);
+}
+
+void subZoomOverMaxDigitalZoomTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float noZoomRatioRange[2];
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, noZoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ float zoomRatio = 3.0f;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // Full active array crop, zoomRatio is 3.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ std::array<float, 4> expectedCrop = {
+ testDefaultCropSize[index][2] / 3.0f, /*x*/
+ testDefaultCropSize[index][3] / 3.0f, /*y*/
+ testDefaultCropSize[index][2] / 3.0f, /*width*/
+ testDefaultCropSize[index][3] / 3.0f, /*height*/
+ };
+ for (int i = 0; i < 4; i++) {
+ EXPECT_LE(std::abs(entry.data.i32[i] - expectedCrop[i]), kMaxAllowedPixelError);
+ }
+
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1) {
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+ }
+}
+
+TEST(ZoomRatioTest, ZoomOverMaxDigitalZoomTest) {
+ subZoomOverMaxDigitalZoomTest(false/*usePreCorrectArray*/);
+ subZoomOverMaxDigitalZoomTest(true/*usePreCorrectArray*/);
+}
+
+void subZoomOverZoomRangeTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float zoomRatioRange[2] = {1.0f, 4.0f};
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ true/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ float zoomRatio = 3.0f;
+ camera_metadata_entry_t entry;
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // Full active array crop, zoomRatio is 3.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.data.f[0], zoomRatio);
+
+ res = mapper.updateCaptureResult(&metadata, false/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.data.f[0], zoomRatio);
+}
+
+TEST(ZoomRatioTest, ZoomOverZoomRangeTest) {
+ subZoomOverZoomRangeTest(false/*usePreCorrectArray*/);
+ subZoomOverZoomRangeTest(true/*usePreCorrectArray*/);
+}