Add v4l2 enum controls.
Some V4L2 controls are simple enum menus, and are directly
analagous to metadata controls. The new V4L2EnumControl class
is a generalized converter between the two.
BUG: https://b/30140438, https://b/29394024
TEST: unit tests pass
Change-Id: I9f8bed200e21cb7e3f3336608fe210f094b7aa42
diff --git a/modules/camera/3_4/Android.mk b/modules/camera/3_4/Android.mk
index 7b644c3..278a351 100644
--- a/modules/camera/3_4/Android.mk
+++ b/modules/camera/3_4/Android.mk
@@ -40,6 +40,7 @@
v4l2_src_files := \
camera.cpp \
metadata/metadata.cpp \
+ metadata/v4l2_enum_control.cpp \
stream.cpp \
stream_format.cpp \
v4l2_camera.cpp \
@@ -54,6 +55,7 @@
metadata/ignored_control_test.cpp \
metadata/metadata_test.cpp \
metadata/optioned_control_test.cpp \
+ metadata/v4l2_enum_control_test.cpp \
# V4L2 Camera HAL.
# ==============================================================================
diff --git a/modules/camera/3_4/metadata/control.h b/modules/camera/3_4/metadata/control.h
index 4b4adcf..eff8c2f 100644
--- a/modules/camera/3_4/metadata/control.h
+++ b/modules/camera/3_4/metadata/control.h
@@ -46,6 +46,11 @@
inline int32_t ControlTag() const { return ControlTags()[0]; }
// Get/Set the control value. Return non-0 on failure.
+ // Controls are allowed to be unreliable, so SetValue is best-effort;
+ // GetValue immediately after may not match (SetValue may, for example,
+ // automatically replace invalid values with valid ones,
+ // or have a delay before setting the requested value).
+ // GetValue should always indicate the actual current value of the control.
virtual int GetValue(T* value) const = 0;
virtual int SetValue(const T& value) = 0;
// Helper to check if val is supported by this control.
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control.cpp b/modules/camera/3_4/metadata/v4l2_enum_control.cpp
new file mode 100644
index 0000000..21ca22b
--- /dev/null
+++ b/modules/camera/3_4/metadata/v4l2_enum_control.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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 "v4l2_enum_control.h"
+
+#include "../common.h"
+
+namespace v4l2_camera_hal {
+
+V4L2EnumControl* V4L2EnumControl::NewV4L2EnumControl(
+ std::shared_ptr<V4L2Wrapper> device, int v4l2_control, int32_t control_tag,
+ int32_t options_tag, const std::map<int32_t, uint8_t>& v4l2_to_metadata) {
+ HAL_LOG_ENTER();
+
+ // Query the device.
+ v4l2_query_ext_ctrl control_query;
+ int res = device->QueryControl(v4l2_control, &control_query);
+ if (res) {
+ HAL_LOGE("Failed to query control %d.", v4l2_control);
+ return nullptr;
+ }
+ if (control_query.type != V4L2_CTRL_TYPE_MENU) {
+ HAL_LOGE(
+ "Enum controls can only be constructed from V4L2 menu controls (%d is "
+ "of type %d)",
+ v4l2_control, control_query.type);
+ return nullptr;
+ }
+
+ // Convert device options to metadata options.
+ std::vector<uint8_t> options;
+ int32_t control_max = static_cast<int32_t>(control_query.maximum);
+ // Query maximum is inclusive.
+ for (int32_t i = static_cast<int32_t>(control_query.minimum);
+ i <= control_max; i += control_query.step) {
+ auto map_entry = v4l2_to_metadata.find(i);
+ if (map_entry == v4l2_to_metadata.end()) {
+ HAL_LOGW("Control %d has unknown option %d.", v4l2_control, i);
+ } else {
+ options.push_back(map_entry->second);
+ }
+ }
+ if (options.empty()) {
+ HAL_LOGE("No supported options for control %d.", v4l2_control);
+ return nullptr;
+ }
+
+ // Construct the device.
+ return new V4L2EnumControl(device, v4l2_control, control_tag, options_tag,
+ std::move(v4l2_to_metadata), std::move(options));
+}
+
+V4L2EnumControl::V4L2EnumControl(
+ std::shared_ptr<V4L2Wrapper> device, int v4l2_control, int32_t control_tag,
+ int32_t options_tag, const std::map<int32_t, uint8_t> v4l2_to_metadata,
+ std::vector<uint8_t> options)
+ : OptionedControl<uint8_t>(control_tag, options_tag, std::move(options)),
+ device_(device),
+ v4l2_control_(v4l2_control),
+ v4l2_to_metadata_(std::move(v4l2_to_metadata)) {
+ HAL_LOG_ENTER();
+}
+
+int V4L2EnumControl::GetValue(uint8_t* value) const {
+ HAL_LOG_ENTER();
+
+ // Query the device for V4L2 value.
+ int32_t v4l2_value = 0;
+ int res = device_->GetControl(v4l2_control_, &v4l2_value);
+ if (res) {
+ HAL_LOGE("Failed to get value for control %d from device.", v4l2_control_);
+ return res;
+ }
+
+ // Convert to metadata value.
+ auto metadata_element = v4l2_to_metadata_.find(v4l2_value);
+ if (metadata_element == v4l2_to_metadata_.end()) {
+ HAL_LOGE("Unknown value %d for control %d.", v4l2_value, v4l2_control_);
+ return -ENODEV;
+ }
+ *value = metadata_element->second;
+ return 0;
+}
+
+int V4L2EnumControl::SetValue(const uint8_t& value) {
+ HAL_LOG_ENTER();
+
+ if (!IsSupported(value)) {
+ HAL_LOGE("Invalid control value %d.", value);
+ return -EINVAL;
+ }
+
+ // Convert to V4L2 value by doing an inverse lookup in the map.
+ bool found = false;
+ int32_t v4l2_value = -1;
+ for (auto kv : v4l2_to_metadata_) {
+ if (kv.second == value) {
+ v4l2_value = kv.first;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ HAL_LOGE("Couldn't find V4L2 conversion of valid control value %d.", value);
+ return -ENODEV;
+ }
+
+ return device_->SetControl(v4l2_control_, v4l2_value);
+}
+
+} // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control.h b/modules/camera/3_4/metadata/v4l2_enum_control.h
new file mode 100644
index 0000000..93fc0e2
--- /dev/null
+++ b/modules/camera/3_4/metadata/v4l2_enum_control.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 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 V4L2_CAMERA_HAL_METADATA_V4L2_ENUM_CONTROL_H_
+#define V4L2_CAMERA_HAL_METADATA_V4L2_ENUM_CONTROL_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest_prod.h>
+
+#include "../v4l2_wrapper.h"
+#include "optioned_control.h"
+
+namespace v4l2_camera_hal {
+
+// A V4L2EnumControl is a direct mapping between a V4L2 control
+// and an Android metadata control.
+class V4L2EnumControl : public OptionedControl<uint8_t> {
+ public:
+ // Use this method to create V4L2EnumControl objects.
+ // Functionally equivalent to "new V4L2EnumControl"
+ // except that it may return nullptr in case of failure.
+ static V4L2EnumControl* NewV4L2EnumControl(
+ std::shared_ptr<V4L2Wrapper> device, int v4l2_control,
+ int32_t control_tag, int32_t options_tag,
+ const std::map<int32_t, uint8_t>& v4l2_to_metadata);
+
+ private:
+ // Constructor private to allow failing on bad input.
+ // Use NewV4L2EnumControl instead.
+ // The values in |v4l2_to_metadata| must be a superset of |options|.
+ V4L2EnumControl(std::shared_ptr<V4L2Wrapper> device, int v4l2_control,
+ int32_t control_tag, int32_t options_tag,
+ const std::map<int32_t, uint8_t> v4l2_to_metadata,
+ std::vector<uint8_t> options);
+
+ virtual int GetValue(uint8_t* value) const override;
+ virtual int SetValue(const uint8_t& value) override;
+
+ std::shared_ptr<V4L2Wrapper> device_;
+ const int v4l2_control_;
+ const std::map<int32_t, uint8_t> v4l2_to_metadata_;
+
+ FRIEND_TEST(V4L2EnumControlTest, SetValue);
+ FRIEND_TEST(V4L2EnumControlTest, SetValueFail);
+ FRIEND_TEST(V4L2EnumControlTest, SetInvalidValue);
+ FRIEND_TEST(V4L2EnumControlTest, SetUnmapped);
+ FRIEND_TEST(V4L2EnumControlTest, GetValue);
+ FRIEND_TEST(V4L2EnumControlTest, GetValueFail);
+ FRIEND_TEST(V4L2EnumControlTest, GetUnmapped);
+ friend class V4L2EnumControlTest; // Access to private constructor.
+
+ DISALLOW_COPY_AND_ASSIGN(V4L2EnumControl);
+};
+
+} // namespace v4l2_camera_hal
+
+#endif // V4L2_CAMERA_HAL_METADATA_V4L2_ENUM_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp b/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp
new file mode 100644
index 0000000..6df7321
--- /dev/null
+++ b/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 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 "v4l2_enum_control.h"
+
+#include <array>
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "../v4l2_wrapper_mock.h"
+#include "array_vector.h"
+#include "test_common.h"
+
+using testing::AtMost;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class V4L2EnumControlTest : public Test {
+ protected:
+ V4L2EnumControlTest()
+ : options_({10, 20, 30, 50}), // Subset of the full map.
+ options_map_(
+ {{0, 0}, {1, 10}, {2, 20}, {3, 30}, {4, 40}, {5, 50}, {6, 60}}) {}
+
+ virtual void SetUp() {
+ device_.reset(new V4L2WrapperMock());
+ control_.reset(new V4L2EnumControl(device_, v4l2_control_, control_tag_,
+ options_tag_, options_map_, options_));
+ }
+
+ virtual uint8_t V4L2ToMetadata(int32_t value) {
+ return options_map_.at(value);
+ }
+
+ virtual int32_t MetadataToV4L2(uint8_t value) {
+ for (auto kv : options_map_) {
+ if (kv.second == value) {
+ return kv.first;
+ }
+ }
+ return -1;
+ }
+
+ std::unique_ptr<V4L2EnumControl> control_;
+ std::shared_ptr<V4L2WrapperMock> device_;
+
+ static constexpr int v4l2_control_ = 123;
+ // Need tags that match the data type (uint8_t) being passed.
+ static constexpr int32_t options_tag_ = ANDROID_CONTROL_AVAILABLE_SCENE_MODES;
+ static constexpr int32_t control_tag_ = ANDROID_CONTROL_SCENE_MODE;
+
+ const std::vector<uint8_t> options_;
+ const std::map<int32_t, uint8_t> options_map_;
+};
+
+TEST_F(V4L2EnumControlTest, NewV4L2EnumSuccess) {
+ // Should query the device.
+ v4l2_query_ext_ctrl query_result;
+ query_result.type = V4L2_CTRL_TYPE_MENU;
+ query_result.minimum = 1;
+ query_result.maximum = 5;
+ query_result.step = 2;
+ EXPECT_CALL(*device_, QueryControl(v4l2_control_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+
+ std::unique_ptr<V4L2EnumControl> test_control(
+ V4L2EnumControl::NewV4L2EnumControl(device_, v4l2_control_, control_tag_,
+ options_tag_, options_map_));
+ // Shouldn't be null.
+ ASSERT_NE(test_control.get(), nullptr);
+
+ // Should pass through tags.
+ // The macro doesn't like the static variables
+ // being passed directly to assertions.
+ int32_t expected_tag = options_tag_;
+ ASSERT_EQ(test_control->StaticTags().size(), 1);
+ EXPECT_EQ(test_control->StaticTags()[0], expected_tag);
+ // Controls use the same tag for getting and setting.
+ expected_tag = control_tag_;
+ ASSERT_EQ(test_control->ControlTags().size(), 1);
+ EXPECT_EQ(test_control->ControlTags()[0], expected_tag);
+ ASSERT_EQ(test_control->DynamicTags().size(), 1);
+ EXPECT_EQ(test_control->DynamicTags()[0], expected_tag);
+
+ // Should populate the options according to capabilities returned.
+ android::CameraMetadata metadata;
+ ASSERT_EQ(test_control->PopulateStaticFields(&metadata), 0);
+ // Min 1, max 5, step 2 means {1,3,5} converted to metadata values.
+ std::vector<uint8_t> expected_options;
+ expected_options.push_back(V4L2ToMetadata(1));
+ expected_options.push_back(V4L2ToMetadata(3));
+ expected_options.push_back(V4L2ToMetadata(5));
+ ExpectMetadataEq(metadata, options_tag_, expected_options);
+}
+
+TEST_F(V4L2EnumControlTest, NewV4L2EnumFailed) {
+ // Querying the device fails.
+ int err = -99;
+ EXPECT_CALL(*device_, QueryControl(v4l2_control_, _)).WillOnce(Return(err));
+
+ std::unique_ptr<V4L2EnumControl> test_control(
+ V4L2EnumControl::NewV4L2EnumControl(device_, v4l2_control_, control_tag_,
+ options_tag_, options_map_));
+ // Should return null to indicate error.
+ ASSERT_EQ(test_control.get(), nullptr);
+}
+
+TEST_F(V4L2EnumControlTest, NewV4L2EnumInvalid) {
+ // Control type is not supported.
+ v4l2_query_ext_ctrl query_result;
+ query_result.type = V4L2_CTRL_TYPE_INTEGER;
+ query_result.minimum = 1;
+ query_result.maximum = 5;
+ query_result.step = 2;
+ EXPECT_CALL(*device_, QueryControl(v4l2_control_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+
+ std::unique_ptr<V4L2EnumControl> test_control(
+ V4L2EnumControl::NewV4L2EnumControl(device_, v4l2_control_, control_tag_,
+ options_tag_, options_map_));
+ // Should return null to indicate error.
+ ASSERT_FALSE(test_control);
+}
+
+TEST_F(V4L2EnumControlTest, SetValue) {
+ // Should go through the device.
+ uint8_t val = options_[1];
+ EXPECT_CALL(*device_, SetControl(v4l2_control_, MetadataToV4L2(val), _))
+ .WillOnce(Return(0));
+ EXPECT_EQ(control_->SetValue(val), 0);
+}
+
+TEST_F(V4L2EnumControlTest, SetValueFail) {
+ // Should go through the device but fail.
+ uint8_t val = options_[1];
+ int err = -99;
+ EXPECT_CALL(*device_, SetControl(v4l2_control_, MetadataToV4L2(val), _))
+ .WillOnce(Return(err));
+ EXPECT_EQ(control_->SetValue(val), err);
+}
+
+TEST_F(V4L2EnumControlTest, SetInvalidValue) {
+ uint8_t val = 99; // Not one of the supported values.
+ EXPECT_EQ(control_->SetValue(val), -EINVAL);
+}
+
+TEST_F(V4L2EnumControlTest, SetUnmapped) {
+ // If the enum control is validly constructed, this should never happen.
+ // Purposefully misconstruct a device for this test (empty map).
+ V4L2EnumControl test_control(device_, v4l2_control_, control_tag_,
+ options_tag_, {}, options_);
+ EXPECT_EQ(test_control.SetValue(options_[0]), -ENODEV);
+}
+
+TEST_F(V4L2EnumControlTest, GetValue) {
+ // Should go through the device.
+ uint8_t expected = options_[0];
+ EXPECT_CALL(*device_, GetControl(v4l2_control_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(MetadataToV4L2(expected)), Return(0)));
+ uint8_t actual =
+ expected + 1; // Initialize to something other than expected.
+ EXPECT_EQ(control_->GetValue(&actual), 0);
+ EXPECT_EQ(actual, expected);
+}
+
+TEST_F(V4L2EnumControlTest, GetValueFail) {
+ // Should go through the device but fail.
+ int err = -99;
+ EXPECT_CALL(*device_, GetControl(v4l2_control_, _)).WillOnce(Return(err));
+ uint8_t unused;
+ EXPECT_EQ(control_->GetValue(&unused), err);
+}
+
+TEST_F(V4L2EnumControlTest, GetUnmapped) {
+ int32_t invalid = -99; // Not in our map.
+ EXPECT_CALL(*device_, GetControl(v4l2_control_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(invalid), Return(0)));
+ uint8_t unused;
+ EXPECT_EQ(control_->GetValue(&unused), -ENODEV);
+}
+
+} // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_wrapper.h b/modules/camera/3_4/v4l2_wrapper.h
index da38e5a..f2f08b3 100644
--- a/modules/camera/3_4/v4l2_wrapper.h
+++ b/modules/camera/3_4/v4l2_wrapper.h
@@ -43,7 +43,9 @@
public:
Connection(std::shared_ptr<V4L2Wrapper> device)
: device_(std::move(device)), connect_result_(device_->Connect()) {}
- ~Connection() { if (connect_result_ == 0) device_->Disconnect(); }
+ ~Connection() {
+ if (connect_result_ == 0) device_->Disconnect();
+ }
// Check whether the connection succeeded or not.
inline int status() const { return connect_result_; }
@@ -53,26 +55,26 @@
};
// Turn the stream on or off.
- int StreamOn();
- int StreamOff();
+ virtual int StreamOn();
+ virtual int StreamOff();
// Manage controls.
- int QueryControl(uint32_t control_id, v4l2_query_ext_ctrl* result);
- int GetControl(uint32_t control_id, int32_t* value);
- int SetControl(uint32_t control_id, int32_t desired,
- int32_t* result = nullptr);
+ virtual int QueryControl(uint32_t control_id, v4l2_query_ext_ctrl* result);
+ virtual int GetControl(uint32_t control_id, int32_t* value);
+ virtual int SetControl(uint32_t control_id, int32_t desired,
+ int32_t* result = nullptr);
// Manage format.
- int GetFormats(std::set<uint32_t>* v4l2_formats);
- int GetFormatFrameSizes(uint32_t v4l2_format,
- std::set<std::array<int32_t, 2>>* sizes);
+ virtual int GetFormats(std::set<uint32_t>* v4l2_formats);
+ virtual int GetFormatFrameSizes(uint32_t v4l2_format,
+ std::set<std::array<int32_t, 2>>* sizes);
// Durations are returned in ns.
- int GetFormatFrameDurationRange(uint32_t v4l2_format,
- const std::array<int32_t, 2>& size,
- std::array<int64_t, 2>* duration_range);
- int SetFormat(const default_camera_hal::Stream& stream,
- uint32_t* result_max_buffers);
+ virtual int GetFormatFrameDurationRange(
+ uint32_t v4l2_format, const std::array<int32_t, 2>& size,
+ std::array<int64_t, 2>* duration_range);
+ virtual int SetFormat(const default_camera_hal::Stream& stream,
+ uint32_t* result_max_buffers);
// Manage buffers.
- int EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer);
- int DequeueBuffer(v4l2_buffer* buffer);
+ virtual int EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer);
+ virtual int DequeueBuffer(v4l2_buffer* buffer);
private:
// Constructor is private to allow failing on bad input.
diff --git a/modules/camera/3_4/v4l2_wrapper_mock.h b/modules/camera/3_4/v4l2_wrapper_mock.h
index d82ed33..1c3d27b 100644
--- a/modules/camera/3_4/v4l2_wrapper_mock.h
+++ b/modules/camera/3_4/v4l2_wrapper_mock.h
@@ -28,8 +28,6 @@
class V4L2WrapperMock : public V4L2Wrapper {
public:
V4L2WrapperMock() : V4L2Wrapper("", nullptr){};
- MOCK_METHOD0(Connect, int());
- MOCK_METHOD0(Disconnect, void());
MOCK_METHOD0(StreamOn, int());
MOCK_METHOD0(StreamOff, int());
MOCK_METHOD2(QueryControl,
@@ -37,6 +35,12 @@
MOCK_METHOD2(GetControl, int(uint32_t control_id, int32_t* value));
MOCK_METHOD3(SetControl,
int(uint32_t control_id, int32_t desired, int32_t* result));
+ MOCK_METHOD1(GetFormats, int(std::set<uint32_t>*));
+ MOCK_METHOD2(GetFormatFrameSizes,
+ int(uint32_t, std::set<std::array<int32_t, 2>>*));
+ MOCK_METHOD3(GetFormatFrameDurationRange,
+ int(uint32_t, const std::array<int32_t, 2>&,
+ std::array<int64_t, 2>*));
MOCK_METHOD2(SetFormat, int(const default_camera_hal::Stream& stream,
uint32_t* result_max_buffers));
MOCK_METHOD1(EnqueueBuffer,