[libcamera-devel] [RFC 1/2] WIP libcamera: controls: Support compound controls

Jacopo Mondi jacopo at jmondi.org
Wed Dec 11 15:53:26 CET 2019


A compound control transports an array of data values. Add
support to compound controls to the ControlValue class.

Signed-off-by: Jacopo Mondi <jacopo at jmondi.org>
---
 include/libcamera/controls.h         |  50 +++-
 include/libcamera/span.h             | 111 +++++++++
 src/libcamera/control_ids.in         |   0
 src/libcamera/control_ids.yaml       |   4 +
 src/libcamera/control_serializer.cpp |  15 +-
 src/libcamera/controls.cpp           | 344 +++++++++++++++++++++++++--
 src/libcamera/pipeline/vimc.cpp      |   7 +
 test/controls/compound_controls.cpp  |  94 ++++++++
 test/controls/meson.build            |   1 +
 9 files changed, 596 insertions(+), 30 deletions(-)
 create mode 100644 include/libcamera/span.h
 create mode 100644 src/libcamera/control_ids.in
 create mode 100644 test/controls/compound_controls.cpp

diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 458b84e8fa8c..b9f8d4db0bea 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -11,6 +11,8 @@
 #include <string>
 #include <unordered_map>
 
+#include <libcamera/span.h>
+
 namespace libcamera {
 
 class ControlValidator;
@@ -18,8 +20,15 @@ class ControlValidator;
 enum ControlType {
 	ControlTypeNone,
 	ControlTypeBool,
+	ControlTypeInteger8,
 	ControlTypeInteger32,
 	ControlTypeInteger64,
+	ControlTypeFloat,
+	ControlTypeCompoundBool,
+	ControlTypeCompoundInt8,
+	ControlTypeCompoundInt32,
+	ControlTypeCompoundInt64,
+	ControlTypeCompoundFloat,
 };
 
 class ControlValue
@@ -27,16 +36,28 @@ class ControlValue
 public:
 	ControlValue();
 	ControlValue(bool value);
+	ControlValue(int8_t value);
 	ControlValue(int32_t value);
 	ControlValue(int64_t value);
+	ControlValue(float value);
+	ControlValue(Span<bool> &values);
+	ControlValue(Span<int8_t> &values);
+	ControlValue(Span<int32_t> &values);
+	ControlValue(Span<int64_t> &values);
+	ControlValue(Span<float> &values);
+	~ControlValue();
 
 	ControlType type() const { return type_; }
 	bool isNone() const { return type_ == ControlTypeNone; }
+	std::size_t numElements() const { return numElements_; }
 
 	template<typename T>
-	const T &get() const;
+	const Span<T> get() const;
+
 	template<typename T>
 	void set(const T &value);
+	template<typename T>
+	void set(const Span<T> &values);
 
 	std::string toString() const;
 
@@ -51,9 +72,23 @@ private:
 
 	union {
 		bool bool_;
+		int8_t integer8_;
 		int32_t integer32_;
 		int64_t integer64_;
+		float float_;
 	};
+
+	bool *pbool_;
+	int8_t *p8_;
+	int32_t *p32_;
+	int64_t *p64_;
+	float *pfloat_;
+
+	std::size_t numElements_;
+
+	void release();
+	bool compareElement(const ControlValue &other, unsigned int i) const;
+	std::string elemToString(unsigned int i) const;
 };
 
 class ControlId
@@ -182,6 +217,7 @@ public:
 
 private:
 	void generateIdmap();
+	bool matchRangeType(enum ControlType type1, enum ControlType type2);
 
 	ControlIdMap idmap_;
 };
@@ -212,7 +248,7 @@ public:
 	bool contains(unsigned int id) const;
 
 	template<typename T>
-	const T &get(const Control<T> &ctrl) const
+	const Span<T> get(const Control<T> &ctrl) const
 	{
 		const ControlValue *val = find(ctrl.id());
 		if (!val) {
@@ -233,6 +269,16 @@ public:
 		val->set<T>(value);
 	}
 
+	template<typename T>
+	void set(const Control<T> &ctrl, const Span<T> &values)
+	{
+		ControlValue *val = find(ctrl.id());
+		if (!val)
+			return;
+
+		val->set<T>(values);
+	}
+
 	const ControlValue &get(unsigned int id) const;
 	void set(unsigned int id, const ControlValue &value);
 
diff --git a/include/libcamera/span.h b/include/libcamera/span.h
new file mode 100644
index 000000000000..8ff0ebc7c6d2
--- /dev/null
+++ b/include/libcamera/span.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * span.h - C++20 std::span<> implementation for C++11
+ */
+
+#ifndef __LIBCAMERA_SPAN_H__
+#define __LIBCAMERA_SPAN_H__
+
+#include <iostream>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+namespace libcamera {
+
+template <typename T>
+class Span
+{
+private:
+	using iterator = T *;
+	using const_iterator = const T *;
+	using reverse_iterator = std::reverse_iterator<iterator>;
+	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+	class Storage
+	{
+	public:
+		Storage(T *ptr, std::size_t size)
+			: ptr_(ptr), size_(size)
+		{
+		}
+
+		T *ptr() const { return ptr_; }
+		std::size_t size() const { return size_; }
+
+	private:
+		T *ptr_;
+		std::size_t size_;
+	};
+
+public:
+	Span(T &v)
+		: storage_(&v, 1)
+	{
+	}
+
+	Span(const T &v)
+		: storage_(const_cast<T *>(&v), 1)
+	{
+	}
+
+	Span(T *v, std::size_t s)
+		: storage_(v, s)
+	{
+	}
+
+	Span(const T *v, std::size_t s)
+		: storage_(const_cast<T *>(v), s)
+	{
+	}
+
+	Span(std::initializer_list<T> list)
+		: storage_(const_cast<T *>(list.begin()), list.size())
+	{
+	}
+
+	Span(const Span &other) = default;
+
+	Span &operator=(const Span &other) = default;
+	operator T() const { return *data(); }
+	T &operator[](unsigned int index) const
+	{
+		if (index >= size())
+			return *(end() - 1);
+		return *(data() + index);
+	}
+
+	T *data() const { return storage_.ptr(); }
+	std::size_t size() const { return storage_.size(); }
+
+	constexpr iterator begin() const { return data(); }
+	constexpr iterator end() const { return data() + size(); }
+	constexpr iterator cbegin() const { return begin(); }
+	constexpr iterator cend() const { return end(); }
+	constexpr reverse_iterator rbegin() const
+	{
+		return reverse_iterator(end());
+	}
+	constexpr reverse_iterator rend() const
+	{
+		return reverse_iterator(begin());
+	}
+	constexpr const_reverse_iterator crbegin() const
+	{
+		return const_reverse_iterator(end());
+	}
+	constexpr const_reverse_iterator crend() const
+	{
+		return const_reverse_iterator(begin());
+	}
+
+private:
+	Storage storage_;
+};
+
+}; /* namespace libcamera */
+
+#endif /* __LIBCAMERA_SPAN_H__ */
diff --git a/src/libcamera/control_ids.in b/src/libcamera/control_ids.in
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml
index 4befec746a59..b72e81dae060 100644
--- a/src/libcamera/control_ids.yaml
+++ b/src/libcamera/control_ids.yaml
@@ -50,4 +50,8 @@ controls:
       type: int32_t
       description: Specify a fixed gain parameter
 
+  - CompoundControl:
+      type: int8_t
+      description: A fictional compound control
+
 ...
diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp
index b787655e6769..2140d3a5dd3f 100644
--- a/src/libcamera/control_serializer.cpp
+++ b/src/libcamera/control_serializer.cpp
@@ -30,10 +30,17 @@ LOG_DEFINE_CATEGORY(Serializer)
 namespace {
 
 static constexpr size_t ControlValueSize[] = {
-	[ControlTypeNone]	= 1,
-	[ControlTypeBool]	= sizeof(bool),
-	[ControlTypeInteger32]	= sizeof(int32_t),
-	[ControlTypeInteger64]	= sizeof(int64_t),
+	[ControlTypeNone]		= 1,
+	[ControlTypeBool]		= sizeof(bool),
+	[ControlTypeInteger8]		= sizeof(int8_t),
+	[ControlTypeInteger32]		= sizeof(int32_t),
+	[ControlTypeInteger64]		= sizeof(int64_t),
+	[ControlTypeFloat]		= sizeof(float),
+	[ControlTypeCompoundBool]	= sizeof(bool *),
+	[ControlTypeCompoundInt8]	= sizeof(int8_t *),
+	[ControlTypeCompoundInt32]	= sizeof(int32_t *),
+	[ControlTypeCompoundInt64]	= sizeof(int64_t *),
+	[ControlTypeCompoundFloat]	= sizeof(float *),
 };
 
 } /* namespace */
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index 1678d3cba4f3..64d99f7669b6 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -9,7 +9,9 @@
 
 #include <iomanip>
 #include <sstream>
-#include <string>
+#include <string.h>
+
+#include <libcamera/span.h>
 
 #include "control_validator.h"
 #include "log.h"
@@ -69,7 +71,8 @@ LOG_DEFINE_CATEGORY(Controls)
  * \brief Construct an empty ControlValue.
  */
 ControlValue::ControlValue()
-	: type_(ControlTypeNone)
+	: type_(ControlTypeNone), pbool_(nullptr), p8_(nullptr), p32_(nullptr),
+	  p64_(nullptr), pfloat_(nullptr), numElements_(0)
 {
 }
 
@@ -78,7 +81,15 @@ ControlValue::ControlValue()
  * \param[in] value Boolean value to store
  */
 ControlValue::ControlValue(bool value)
-	: type_(ControlTypeBool), bool_(value)
+	: type_(ControlTypeBool), bool_(value), pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr), numElements_(1)
+{
+}
+
+ControlValue::ControlValue(int8_t value)
+	: type_(ControlTypeInteger8), integer8_(value), pbool_(nullptr),
+	  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(1)
 {
 }
 
@@ -87,7 +98,9 @@ ControlValue::ControlValue(bool value)
  * \param[in] value Integer value to store
  */
 ControlValue::ControlValue(int32_t value)
-	: type_(ControlTypeInteger32), integer32_(value)
+	: type_(ControlTypeInteger32), integer32_(value), pbool_(nullptr),
+	  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(1)
 {
 }
 
@@ -96,8 +109,87 @@ ControlValue::ControlValue(int32_t value)
  * \param[in] value Integer value to store
  */
 ControlValue::ControlValue(int64_t value)
-	: type_(ControlTypeInteger64), integer64_(value)
+	: type_(ControlTypeInteger64), integer64_(value), pbool_(nullptr),
+	  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(1)
+{
+}
+
+ControlValue::ControlValue(float value)
+	: type_(ControlTypeInteger64), float_(value), pbool_(nullptr),
+	  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(1)
+{
+}
+
+ControlValue::ControlValue(Span<bool> &values)
+	: type_(ControlTypeCompoundBool), pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(values.size())
+{
+	pbool_ = new bool[numElements_];
+	memcpy(pbool_, values.data(), sizeof(bool) * numElements_);
+}
+
+ControlValue::ControlValue(Span<int8_t> &values)
+	: type_(ControlTypeCompoundInt8),pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(values.size())
+{
+	p8_ = new int8_t[numElements_];
+	memcpy(p8_, values.data(), sizeof(int8_t) * numElements_);
+}
+
+ControlValue::ControlValue(Span<int32_t> &values)
+	: type_(ControlTypeCompoundInt32), pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(values.size())
+{
+	p32_ = new int32_t[numElements_];
+	memcpy(p32_, values.data(), sizeof(int32_t) * numElements_);
+}
+
+ControlValue::ControlValue(Span<int64_t> &values)
+	: type_(ControlTypeCompoundInt64), pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(values.size())
 {
+	p64_ = new int64_t[numElements_];
+	memcpy(p64_, values.data(), sizeof(int64_t) * numElements_);
+}
+
+ControlValue::ControlValue(Span<float> &values)
+	: type_(ControlTypeCompoundFloat), pbool_(nullptr), p8_(nullptr),
+	  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),
+	  numElements_(values.size())
+{
+	pfloat_ = new float[numElements_];
+	memcpy(pfloat_, values.data(), sizeof(float) * numElements_);
+}
+
+void ControlValue::release()
+{
+	if (pbool_)
+		delete[] pbool_;
+	if (p8_)
+		delete[] p8_;
+	if (p32_)
+		delete[] p32_;
+	if (p64_)
+		delete[] p64_;
+	if (pfloat_)
+		delete[] pfloat_;
+
+	pbool_ = nullptr;
+	p8_ = nullptr;
+	p32_ = nullptr;
+	p64_ = nullptr;
+	pfloat_ = nullptr;
+}
+
+ControlValue::~ControlValue()
+{
+	release();
 }
 
 /**
@@ -130,69 +222,225 @@ ControlValue::ControlValue(int64_t value)
 
 #ifndef __DOXYGEN__
 template<>
-const bool &ControlValue::get<bool>() const
+const Span<bool> ControlValue::get<bool>() const
 {
-	ASSERT(type_ == ControlTypeBool);
+	ASSERT(type_ == ControlTypeBool || type_ == ControlTypeCompoundBool);
 
-	return bool_;
+	const bool *p = pbool_ ? pbool_ : &bool_;
+	/*
+	 * Explicitly create a Span<bool> instance, otherwise the compiler
+	 * tries to match the "{ p, numElements_}" construct used in other get()
+	 * operation specialization with the initializer_list constructor of
+	 * class Span (clang++ 8.0.1)
+	 */
+	return Span<bool>(p, static_cast<std::size_t>(numElements_));
 }
 
 template<>
-const int32_t &ControlValue::get<int32_t>() const
+const Span<int8_t> ControlValue::get<int8_t>() const
 {
-	ASSERT(type_ == ControlTypeInteger32 || type_ == ControlTypeInteger64);
+	ASSERT(type_ == ControlTypeInteger8 ||
+	       type_ == ControlTypeCompoundInt8);
 
-	return integer32_;
+	const int8_t *p = p8_ ? p8_ : &integer8_;
+	return { p, static_cast<std::size_t>(numElements_) };
 }
 
 template<>
-const int64_t &ControlValue::get<int64_t>() const
+const Span<int32_t> ControlValue::get<int32_t>() const
 {
-	ASSERT(type_ == ControlTypeInteger32 || type_ == ControlTypeInteger64);
+	ASSERT(type_ == ControlTypeInteger32 ||
+	       type_ == ControlTypeInteger64 ||
+	       type_ == ControlTypeCompoundInt32 ||
+	       type_ == ControlTypeCompoundInt64);
 
-	return integer64_;
+	const int32_t *p = p32_ ? p32_ : &integer32_;
+	return { p, numElements_ };
+}
+
+template<>
+const Span<int64_t> ControlValue::get<int64_t>() const
+{
+	ASSERT(type_ == ControlTypeInteger32 ||
+	       type_ == ControlTypeInteger64 ||
+	       type_ == ControlTypeCompoundInt32 ||
+	       type_ == ControlTypeCompoundInt64);
+
+	const int64_t *p = p64_ ? p64_ : &integer64_;
+	return { p, numElements_ };
+}
+
+template<>
+const Span<float> ControlValue::get<float>() const
+{
+	ASSERT(type_ == ControlTypeFloat || type_ == ControlTypeCompoundFloat);
+
+	const float *p = pfloat_ ? pfloat_ : &float_;
+	return { p, numElements_ };
 }
 
 template<>
 void ControlValue::set<bool>(const bool &value)
 {
+	release();
+
 	type_ = ControlTypeBool;
 	bool_ = value;
+	numElements_ = 1;
 }
 
 template<>
 void ControlValue::set<int32_t>(const int32_t &value)
 {
+	release();
+
 	type_ = ControlTypeInteger32;
 	integer32_ = value;
+	numElements_ = 1;
 }
 
 template<>
 void ControlValue::set<int64_t>(const int64_t &value)
 {
+	release();
+
 	type_ = ControlTypeInteger64;
 	integer64_ = value;
+	numElements_ = 1;
 }
+
+template<>
+void ControlValue::set<bool>(const Span<bool> &values)
+{
+	release();
+
+	type_ = ControlTypeCompoundBool;
+	numElements_ = values.size();
+
+	pbool_ = new bool[numElements_];
+	memcpy(pbool_, values.data(), sizeof(bool) * numElements_);
+}
+
+template<>
+void ControlValue::set<int8_t>(const Span<int8_t> &values)
+{
+	release();
+
+	type_ = ControlTypeCompoundInt8;
+	numElements_ = values.size();
+
+	p8_ = new int8_t[numElements_];
+	memcpy(p8_, values.data(), sizeof(int8_t) * numElements_);
+}
+
+template<>
+void ControlValue::set<int32_t>(const Span<int32_t> &values)
+{
+	release();
+
+	type_ = ControlTypeCompoundInt32;
+	numElements_ = values.size();
+
+	p32_ = new int32_t[numElements_];
+	memcpy(p32_, values.data(), sizeof(int32_t) * numElements_);
+}
+
+template<>
+void ControlValue::set<int64_t>(const Span<int64_t> &values)
+{
+	release();
+
+	type_ = ControlTypeCompoundInt64;
+	numElements_ = values.size();
+
+	p64_ = new int64_t[numElements_];
+	memcpy(p64_, values.data(), sizeof(int64_t) * numElements_);
+}
+
+template<>
+void ControlValue::set<float>(const Span<float> &values)
+{
+	release();
+
+	type_ = ControlTypeCompoundFloat;
+	numElements_ = values.size();
+
+	pfloat_ = new float[numElements_];
+	memcpy(pfloat_, values.data(), sizeof(float) * numElements_);
+}
+
 #endif /* __DOXYGEN__ */
 
 /**
  * \brief Assemble and return a string describing the value
  * \return A string describing the ControlValue
  */
-std::string ControlValue::toString() const
+std::string ControlValue::elemToString(unsigned int i) const
 {
 	switch (type_) {
-	case ControlTypeNone:
-		return "<None>";
 	case ControlTypeBool:
-		return bool_ ? "True" : "False";
+		return bool_ ? "True " : "False ";
+	case ControlTypeInteger8:
+		return std::to_string(integer8_);
 	case ControlTypeInteger32:
 		return std::to_string(integer32_);
 	case ControlTypeInteger64:
 		return std::to_string(integer64_);
+	case ControlTypeFloat:
+		return std::to_string(float_);
+	case ControlTypeCompoundBool:
+		return pbool_[i] ? "True " : "False ";
+	case ControlTypeCompoundInt8:
+		return std::to_string(p8_[i]) + " ";
+	case ControlTypeCompoundInt32:
+		return std::to_string(p32_[i]) + " ";
+	case ControlTypeCompoundInt64:
+		return std::to_string(p64_[i]) + " ";
+	case ControlTypeCompoundFloat:
+		return std::to_string(pfloat_[i]) + " ";
+	default:
+		return "<None>";
 	}
+}
+
+std::string ControlValue::toString() const
+{
+	if (ControlTypeNone)
+		return "<ValueType Error>";
+
+	std::string str;
+	for (unsigned int i = 0; i < numElements_; ++i)
+		str += elemToString(i);
+
+	return str;
+}
 
-	return "<ValueType Error>";
+bool ControlValue::compareElement(const ControlValue &other, unsigned int i) const
+{
+	switch (type_) {
+	case ControlTypeBool:
+		return bool_ == other.bool_;
+	case ControlTypeInteger8:
+		return integer8_ == other.integer8_;
+	case ControlTypeInteger32:
+		return integer32_ == other.integer32_;
+	case ControlTypeInteger64:
+		return integer64_ == other.integer64_;
+	case ControlTypeFloat:
+		return float_ == other.float_;
+	case ControlTypeCompoundBool:
+		return pbool_[i] == other.pbool_[i];
+	case ControlTypeCompoundInt8:
+		return p8_[i] == other.p8_[i];
+	case ControlTypeCompoundInt32:
+		return p32_[i] == other.p32_[i];
+	case ControlTypeCompoundInt64:
+		return p64_[i] == other.p64_[i];
+	case ControlTypeCompoundFloat:
+		return pfloat_[i] == other.pfloat_[i];
+	default:
+		return false;
+	}
 }
 
 /**
@@ -204,13 +452,27 @@ bool ControlValue::operator==(const ControlValue &other) const
 	if (type_ != other.type_)
 		return false;
 
+	if (numElements_ != other.numElements())
+		return false;
+
 	switch (type_) {
 	case ControlTypeBool:
-		return bool_ == other.bool_;
+	case ControlTypeInteger8:
 	case ControlTypeInteger32:
-		return integer32_ == other.integer32_;
 	case ControlTypeInteger64:
-		return integer64_ == other.integer64_;
+	case ControlTypeFloat:
+		return compareElement(other, 0);
+	case ControlTypeCompoundBool:
+	case ControlTypeCompoundInt8:
+	case ControlTypeCompoundInt32:
+	case ControlTypeCompoundInt64:
+	case ControlTypeCompoundFloat:
+		for (unsigned int i = 0; i < numElements_; ++i) {
+			if (!compareElement(other, i))
+				return false;
+		}
+
+		return true;
 	default:
 		return false;
 	}
@@ -330,6 +592,12 @@ Control<bool>::Control(unsigned int id, const char *name)
 {
 }
 
+template<>
+Control<int8_t>::Control(unsigned int id, const char *name)
+	: ControlId(id, name, ControlTypeInteger8)
+{
+}
+
 template<>
 Control<int32_t>::Control(unsigned int id, const char *name)
 	: ControlId(id, name, ControlTypeInteger32)
@@ -341,6 +609,12 @@ Control<int64_t>::Control(unsigned int id, const char *name)
 	: ControlId(id, name, ControlTypeInteger64)
 {
 }
+
+template<>
+Control<float>::Control(unsigned int id, const char *name)
+	: ControlId(id, name, ControlTypeFloat)
+{
+}
 #endif /* __DOXYGEN__ */
 
 /**
@@ -583,15 +857,37 @@ ControlInfoMap::const_iterator ControlInfoMap::find(unsigned int id) const
  * \return The ControlId map
  */
 
+bool ControlInfoMap::matchRangeType(enum ControlType type1, enum ControlType type2)
+{
+	if (type1 == type2)
+		return true;
+
+	switch (type1) {
+	case ControlTypeCompoundInt8:
+		return type2 == ControlTypeInteger8;
+	case ControlTypeCompoundInt32:
+		return type2 == ControlTypeInteger32;
+	case ControlTypeCompoundInt64:
+		return type2 == ControlTypeInteger64;
+	case ControlTypeCompoundFloat:
+		return type2 == ControlTypeFloat;
+	default:
+		return false;
+	}
+}
+
 void ControlInfoMap::generateIdmap()
 {
 	idmap_.clear();
 
 	for (const auto &ctrl : *this) {
-		if (ctrl.first->type() != ctrl.second.min().type()) {
+		if (!matchRangeType(ctrl.first->type(),
+				    ctrl.second.min().type())) {
 			LOG(Controls, Error)
 				<< "Control " << utils::hex(ctrl.first->id())
-				<< " type and range type mismatch";
+				<< " type and range type mismatch: "
+				<< ctrl.first->type() << " - "
+				<< ctrl.second.min().type();
 			idmap_.clear();
 			clear();
 			return;
diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp
index f043cf55889e..2bfab35314c4 100644
--- a/src/libcamera/pipeline/vimc.cpp
+++ b/src/libcamera/pipeline/vimc.cpp
@@ -453,6 +453,13 @@ int VimcCameraData::init(MediaDevice *media)
 			      std::forward_as_tuple(range));
 	}
 
+	/* Register a compound control. */
+	ctrls.emplace(std::piecewise_construct,
+		      std::forward_as_tuple(&controls::CompoundControl),
+		      std::forward_as_tuple(0, 100));
+
+
+
 	controlInfo_ = std::move(ctrls);
 
 	/* Initialize the camera properties. */
diff --git a/test/controls/compound_controls.cpp b/test/controls/compound_controls.cpp
new file mode 100644
index 000000000000..de00573df141
--- /dev/null
+++ b/test/controls/compound_controls.cpp
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * compound_controls.cpp - CompoundControls test
+ */
+
+#include <iostream>
+#include <vector>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/span.h>
+
+#include "camera_controls.h"
+
+#include "camera_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class CompoundControlsTest : public CameraTest, public Test
+{
+public:
+	CompoundControlsTest()
+		: CameraTest("VIMC Sensor B")
+	{
+	}
+
+protected:
+	int init() override
+	{
+		return status_;
+	}
+
+	int run() override
+	{
+		CameraControlValidator validator(camera_.get());
+		ControlList list(controls::controls, &validator);
+
+		/*
+		 * Span
+		 *
+		 * A Span can be initialized with an initializer list
+		 * and sequentially or random accessed
+		 */
+		Span<int32_t> span = { 1, 2, 3 };
+		for (uint32_t i : span)
+			cout << i << endl;
+		cout << span[0] << endl;
+
+		/*
+		 * Compound Controls
+		 *
+		 * As of now, all Controls are now 'compounds' when set with a
+		 * Span<> of values.
+		 *
+		 * We need to define how to establish that a Control is actually
+		 * a compound or supports a single value.
+		 */
+		list.set(controls::Brightness, {0, 125, 255});
+		Span<int32_t> iSpan = list.get(controls::Brightness);
+
+		cout << iSpan.size() << endl;
+		for (uint32_t i : iSpan)
+			cout << i << endl;
+
+		/*
+		 * But they can still be accessed and operated with a single
+		 * value.
+		 */
+		list.set(controls::Brightness, 112);
+		cout << list.get(controls::Brightness) << endl;
+
+		/*
+		 * Or set with a Span and accessed by value. The first item
+		 * is returned.
+		 */
+		list.set(controls::Brightness, {50, 125});
+		cout << list.get(controls::Brightness) << endl;
+
+		/* The other way around works as well. */
+		list.set(controls::Brightness, 133);
+		Span<int32_t> s = list.get(controls::Brightness);
+		cout << s << endl;
+
+		return TestPass;
+	}
+};
+
+TEST_REGISTER(CompoundControlsTest)
diff --git a/test/controls/meson.build b/test/controls/meson.build
index f0850df28c8a..f4752dcff33b 100644
--- a/test/controls/meson.build
+++ b/test/controls/meson.build
@@ -3,6 +3,7 @@ control_tests = [
     [ 'control_list',   'control_list.cpp' ],
     [ 'control_range',  'control_range.cpp' ],
     [ 'control_value',  'control_value.cpp' ],
+    [ 'compound_controls', 'compound_controls.cpp'],
 ]
 
 foreach t : control_tests
-- 
2.24.0



More information about the libcamera-devel mailing list