CameraService: Implement SCALER_ROTATE_AND_CROP_AUTO, part 1
When an app sets SCALER_ROTATE_AND_CROP to AUTO, the camera service
needs to select the right ROTATE_AND_CROP mode given the application
UI state at the moment, received from the window manager.
In addition, some of the metadata in the active array coordinate
system needs to be converted to/from the cropped+rotated coordinate
system to ensure roundtripping UI information works as before.
Also ensure that the available rotate and crop metadata field is
always available, with a value of NONE if nothing else.
This commit adds support for doing the coordinate transforms and
overriding AUTO to a concrete value; it does not wire up a connection
to another system service to receive the correct override value, but
does add a command to set the override value for all current camera
clients.
Test: New CTS tests pass, unit tests for RotateAndCropMapper pass
Bug: 134631897
Change-Id: Icc45530e2cfbaf838a1e4d04e4fd2aef8122e8e1
diff --git a/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp b/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp
new file mode 100644
index 0000000..c638d40
--- /dev/null
+++ b/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2020 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 "RotateAndCropMapperTest"
+
+#include <functional>
+#include <random>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "../device3/RotateAndCropMapper.h"
+
+namespace rotateAndCropMapperTest {
+
+using namespace android;
+using namespace android::camera3;
+
+using ::testing::ElementsAreArray;
+using ::testing::Each;
+using ::testing::AllOf;
+using ::testing::Ge;
+using ::testing::Le;
+
+#define EXPECT_EQUAL_WITHIN_N(vec, array, N, msg) \
+{ \
+ std::vector<int32_t> vec_diff; \
+ std::transform(vec.begin(), vec.end(), array, \
+ std::back_inserter(vec_diff), std::minus()); \
+ EXPECT_THAT(vec_diff, Each(AllOf(Ge(-N), Le(N)))) << msg; \
+}
+
+int32_t testActiveArray[] = {100, 100, 4000, 3000};
+
+std::vector<uint8_t> basicModes = {
+ ANDROID_SCALER_ROTATE_AND_CROP_NONE,
+ ANDROID_SCALER_ROTATE_AND_CROP_90,
+ ANDROID_SCALER_ROTATE_AND_CROP_AUTO
+};
+
+CameraMetadata setupDeviceInfo(int32_t activeArray[4], std::vector<uint8_t> availableCropModes ) {
+ CameraMetadata deviceInfo;
+
+ deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+ activeArray, 4);
+
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
+ availableCropModes.data(), availableCropModes.size());
+
+ return deviceInfo;
+}
+
+TEST(RotationMapperTest, Initialization) {
+ CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+ {ANDROID_SCALER_ROTATE_AND_CROP_NONE});
+
+ ASSERT_FALSE(RotateAndCropMapper::isNeeded(&deviceInfo));
+
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
+ basicModes.data(), 3);
+
+ ASSERT_TRUE(RotateAndCropMapper::isNeeded(&deviceInfo));
+}
+
+TEST(RotationMapperTest, IdentityTransform) {
+ status_t res;
+
+ CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+ basicModes);
+
+ RotateAndCropMapper mapper(&deviceInfo);
+
+ CameraMetadata request;
+ uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_NONE;
+ auto full_crop = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3]};
+ auto full_region = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3], 1};
+ request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+ &mode, 1);
+ request.update(ANDROID_SCALER_CROP_REGION,
+ full_crop.data(), full_crop.size());
+ request.update(ANDROID_CONTROL_AE_REGIONS,
+ full_region.data(), full_region.size());
+
+ // Map to HAL
+
+ res = mapper.updateCaptureRequest(&request);
+ ASSERT_TRUE(res == OK);
+
+ auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+ EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
+
+ e = request.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
+
+ // Add fields in HAL
+
+ CameraMetadata result(request);
+
+ auto face = std::vector<int32_t> {300,300,500,500};
+ result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+ face.data(), face.size());
+
+ // Map to app
+
+ res = mapper.updateCaptureResult(&result);
+ ASSERT_TRUE(res == OK);
+
+ e = result.find(ANDROID_CONTROL_AE_REGIONS);
+ EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
+
+ e = result.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
+
+ e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+ EXPECT_THAT(face, ElementsAreArray(e.data.i32, e.count));
+}
+
+TEST(RotationMapperTest, Transform90) {
+ status_t res;
+
+ CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+ basicModes);
+
+ RotateAndCropMapper mapper(&deviceInfo);
+
+ CameraMetadata request;
+ uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_90;
+ auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+ auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+ request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+ &mode, 1);
+ request.update(ANDROID_SCALER_CROP_REGION,
+ full_crop.data(), full_crop.size());
+ request.update(ANDROID_CONTROL_AE_REGIONS,
+ full_region.data(), full_region.size());
+
+ // Map to HAL
+
+ res = mapper.updateCaptureRequest(&request);
+ ASSERT_TRUE(res == OK);
+
+ auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+ float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
+ int32_t rw = full_crop[3] / aspectRatio;
+ int32_t rh = full_crop[3];
+ auto rotated_region = std::vector<int32_t> {
+ full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
+ full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
+ 1
+ };
+ EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated AE region isn't right";
+
+ e = request.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated crop region isn't right";
+
+ // Add fields in HAL
+
+ CameraMetadata result(request);
+
+ auto face = std::vector<int32_t> {
+ rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+ rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
+ result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+ face.data(), face.size());
+
+ auto landmarks = std::vector<int32_t> {
+ rotated_region[0], rotated_region[1],
+ rotated_region[2], rotated_region[3],
+ rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+ rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
+ rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
+ };
+ result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+ landmarks.data(), landmarks.size());
+
+ // Map to app
+
+ res = mapper.updateCaptureResult(&result);
+ ASSERT_TRUE(res == OK);
+
+ // Round-trip results can't be exact since we've gone from a large int range -> small int range
+ // and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
+ e = result.find(ANDROID_CONTROL_AE_REGIONS);
+ EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
+
+ e = result.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
+
+ auto full_face = std::vector<int32_t> {
+ full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+ EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+ auto full_landmarks = std::vector<int32_t> {
+ full_crop[0], full_crop[1] + full_crop[3],
+ full_crop[0] + full_crop[2], full_crop[1],
+ full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
+ full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+ EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+TEST(RotationMapperTest, Transform270) {
+ status_t res;
+
+ CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+ basicModes);
+
+ RotateAndCropMapper mapper(&deviceInfo);
+
+ CameraMetadata request;
+ uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_270;
+ auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+ auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+ request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+ &mode, 1);
+ request.update(ANDROID_SCALER_CROP_REGION,
+ full_crop.data(), full_crop.size());
+ request.update(ANDROID_CONTROL_AE_REGIONS,
+ full_region.data(), full_region.size());
+
+ // Map to HAL
+
+ res = mapper.updateCaptureRequest(&request);
+ ASSERT_TRUE(res == OK);
+
+ auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+ float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
+ int32_t rw = full_crop[3] / aspectRatio;
+ int32_t rh = full_crop[3];
+ auto rotated_region = std::vector<int32_t> {
+ full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
+ full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
+ 1
+ };
+ EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated AE region isn't right";
+
+ e = request.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated crop region isn't right";
+
+ // Add fields in HAL
+
+ CameraMetadata result(request);
+
+ auto face = std::vector<int32_t> {
+ rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+ rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
+ result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+ face.data(), face.size());
+
+ auto landmarks = std::vector<int32_t> {
+ rotated_region[0], rotated_region[1],
+ rotated_region[2], rotated_region[3],
+ rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+ rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
+ rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
+ };
+ result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+ landmarks.data(), landmarks.size());
+
+ // Map to app
+
+ res = mapper.updateCaptureResult(&result);
+ ASSERT_TRUE(res == OK);
+
+ // Round-trip results can't be exact since we've gone from a large int range -> small int range
+ // and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
+
+ e = result.find(ANDROID_CONTROL_AE_REGIONS);
+ EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
+
+ e = result.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
+
+ auto full_face = std::vector<int32_t> {
+ full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+ EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+ auto full_landmarks = std::vector<int32_t> {
+ full_crop[0] + full_crop[2], full_crop[1],
+ full_crop[0], full_crop[1] + full_crop[3],
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+ full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+ full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+ EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+TEST(RotationMapperTest, Transform180) {
+ status_t res;
+
+ CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+ basicModes);
+
+ RotateAndCropMapper mapper(&deviceInfo);
+
+ CameraMetadata request;
+ uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_180;
+ auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+ auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+ request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+ &mode, 1);
+ request.update(ANDROID_SCALER_CROP_REGION,
+ full_crop.data(), full_crop.size());
+ request.update(ANDROID_CONTROL_AE_REGIONS,
+ full_region.data(), full_region.size());
+
+ // Map to HAL
+
+ res = mapper.updateCaptureRequest(&request);
+ ASSERT_TRUE(res == OK);
+
+ auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+ auto rotated_region = full_region;
+ EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated AE region isn't right";
+
+ e = request.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+ << "Rotated crop region isn't right";
+
+ // Add fields in HAL
+
+ CameraMetadata result(request);
+
+ float rw = full_region[2] - full_region[0];
+ float rh = full_region[3] - full_region[1];
+ auto face = std::vector<int32_t> {
+ rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
+ rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
+ };
+ result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+ face.data(), face.size());
+
+ auto landmarks = std::vector<int32_t> {
+ rotated_region[0], rotated_region[1],
+ rotated_region[2], rotated_region[3],
+ rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
+ rotated_region[0] + (int)(rw / 2), rotated_region[1] + (int)(rh / 2),
+ rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
+ };
+ result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+ landmarks.data(), landmarks.size());
+
+ // Map to app
+
+ res = mapper.updateCaptureResult(&result);
+ ASSERT_TRUE(res == OK);
+
+ e = result.find(ANDROID_CONTROL_AE_REGIONS);
+ EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count))
+ << "Round-tripped AE region isn't right";
+
+ e = result.find(ANDROID_SCALER_CROP_REGION);
+ EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+ << "Round-tripped crop region isn't right";
+
+ auto full_face = std::vector<int32_t> {
+ full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+ EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+ auto full_landmarks = std::vector<int32_t> {
+ full_crop[0] + full_crop[2], full_crop[1] + full_crop[3],
+ full_crop[0], full_crop[1],
+ full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
+ full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+ full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4
+ };
+ e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+ EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+
+} // namespace rotateAndCropMapperTest