[v5,3/3] gstreamer: Report camera properties as device properties
diff mbox series

Message ID 20250729153915.159243-4-uajain@igalia.com
State New
Headers show
Series
  • gstreamer: Report camera properties as device properties
Related show

Commit Message

Umang Jain July 29, 2025, 3:39 p.m. UTC
Iterate over all libcamera camera properties and report them as
device properties. Each libcamera ControlType is mapped to the
corresponding gstreamer GType. If the ControlValue is an array of
values (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY
is used to set the value of that ControlValue with the corresponding
GType.

Signed-off-by: Umang Jain <uajain@igalia.com>
---
 src/gstreamer/gstlibcamera-utils.cpp   | 197 +++++++++++++++++++++++++
 src/gstreamer/gstlibcamera-utils.h     |   3 +
 src/gstreamer/gstlibcameraprovider.cpp |  13 ++
 3 files changed, 213 insertions(+)

Comments

Nicolas Dufresne July 29, 2025, 4:06 p.m. UTC | #1
Le mardi 29 juillet 2025 à 21:09 +0530, Umang Jain a écrit :
> Iterate over all libcamera camera properties and report them as
> device properties. Each libcamera ControlType is mapped to the
> corresponding gstreamer GType. If the ControlValue is an array of
> values (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY
> is used to set the value of that ControlValue with the corresponding
> GType.
> 
> Signed-off-by: Umang Jain <uajain@igalia.com>

Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>

> ---
>  src/gstreamer/gstlibcamera-utils.cpp   | 197 +++++++++++++++++++++++++
>  src/gstreamer/gstlibcamera-utils.h     |   3 +
>  src/gstreamer/gstlibcameraprovider.cpp |  13 ++
>  3 files changed, 213 insertions(+)
> 
> diff --git a/src/gstreamer/gstlibcamera-utils.cpp
> b/src/gstreamer/gstlibcamera-utils.cpp
> index 63e00fe0..d11d7ba9 100644
> --- a/src/gstreamer/gstlibcamera-utils.cpp
> +++ b/src/gstreamer/gstlibcamera-utils.cpp
> @@ -8,6 +8,8 @@
>  
>  #include "gstlibcamera-utils.h"
>  
> +#include <string>
> +
>  #include <libcamera/control_ids.h>
>  #include <libcamera/formats.h>
>  
> @@ -333,6 +335,33 @@ bare_structure_from_format(const PixelFormat &format)
>  	}
>  }
>  
> +static const struct {
> +	ControlType c_type;
> +	GType g_type;
> +} control_type_gtype_map[] = {
> +	{ ControlTypeBool, G_TYPE_BOOLEAN },
> +	{ ControlTypeByte, G_TYPE_UINT },
> +	{ ControlTypeUnsigned16, G_TYPE_UINT },
> +	{ ControlTypeUnsigned32, G_TYPE_UINT },
> +	{ ControlTypeInteger32, G_TYPE_INT },
> +	{ ControlTypeInteger64, G_TYPE_INT64 },
> +	{ ControlTypeFloat, G_TYPE_FLOAT },
> +	{ ControlTypeString, G_TYPE_STRING },
> +	{ ControlTypeRectangle, GST_TYPE_ARRAY },
> +	{ ControlTypeSize, GST_TYPE_ARRAY },
> +	{ ControlTypePoint, GST_TYPE_ARRAY },
> +};
> +
> +static GType
> +control_type_to_gtype(const ControlType &type)
> +{
> +	for (auto &a : control_type_gtype_map) {
> +		if (a.c_type == type)
> +			return a.g_type;
> +	}
> +	return G_TYPE_INVALID;
> +}
> +
>  GstCaps *
>  gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
>  {
> @@ -706,3 +735,171 @@ gst_libcamera_get_camera_manager(int &ret)
>  
>  	return cm;
>  }
> +
> +int gst_libcamera_set_structure_field(GstStructure *structure, const
> ControlId *id,
> +				      const ControlValue &value)
> +{
> +	std::string prop = "api.libcamera." + id->name();
> +	g_auto(GValue) v = G_VALUE_INIT;
> +	g_auto(GValue) x = G_VALUE_INIT;
> +	gboolean is_array = value.isArray();
> +
> +	GType type = control_type_to_gtype(value.type());
> +	if (type == G_TYPE_INVALID)
> +		return -EINVAL;
> +
> +	if (is_array || type == GST_TYPE_ARRAY)
> +		g_value_init(&v, GST_TYPE_ARRAY);
> +
> +	switch (value.type()) {
> +	case ControlTypeBool:
> +		if (is_array) {
> +			Span<const bool> data = value.get<Span<const
> bool>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_boolean(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_BOOLEAN,
> +					  value.get<const bool>(), nullptr);
> +		}
> +		break;
> +	case ControlTypeByte:
> +		if (is_array) {
> +			Span<const uint8_t> data = value.get<Span<const
> uint8_t>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_uint(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_UINT,
> +					  value.get<const uint8_t>(),
> nullptr);
> +		}
> +		break;
> +	case ControlTypeUnsigned16:
> +		if (is_array) {
> +			Span<const uint16_t> data = value.get<Span<const
> uint16_t>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_uint(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_UINT,
> +					  value.get<const uint16_t>(),
> nullptr);
> +		}
> +		break;
> +	case ControlTypeUnsigned32:
> +		if (is_array) {
> +			Span<const uint32_t> data = value.get<Span<const
> uint32_t>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_uint(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_UINT,
> +					  value.get<const uint32_t>(),
> nullptr);
> +		}
> +		break;
> +	case ControlTypeInteger32:
> +		if (is_array) {
> +			Span<const int32_t> data = value.get<Span<const
> int32_t>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_int(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			if (!id->enumerators().empty()) {
> +				int32_t val = value.get<int32_t>();
> +				const auto &iter = id-
> >enumerators().find(val);
> +				if (iter != id->enumerators().end()) {
> +					gst_structure_set(structure,
> prop.c_str(),
> +							  G_TYPE_STRING,
> +							  iter-
> >second.c_str(),
> +							  nullptr);
> +				} else {
> +					return -EINVAL;
> +				}
> +			} else {
> +				gst_structure_set(structure, prop.c_str(),
> G_TYPE_INT,
> +						  value.get<const int32_t>(),
> nullptr);
> +			}
> +		}
> +		break;
> +	case ControlTypeInteger64:
> +		if (is_array) {
> +			Span<const int64_t> data = value.get<Span<const
> int64_t>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_int64(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_INT64,
> +					  value.get<const int64_t>(),
> nullptr);
> +		}
> +		break;
> +	case ControlTypeFloat:
> +		if (is_array) {
> +			Span<const float> data = value.get<Span<const
> float>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> {
> +				g_value_init(&x, type);
> +				g_value_set_float(&x, *it);
> +				gst_value_array_append_and_take_value(&v,
> &x);
> +			}
> +		} else {
> +			gst_structure_set(structure, prop.c_str(),
> G_TYPE_FLOAT,
> +					  value.get<const float>(), nullptr);
> +		}
> +		break;
> +	case ControlTypeString:
> +		/*
> +		 * isArray() is always true for strings hence, unset the
> GValue
> +		 * array because we are going to the toString() helper
> directly.
> +		 */
> +		g_value_unset(&v);
> +		gst_structure_set(structure, prop.c_str(), G_TYPE_STRING,
> +				  value.toString().c_str(), nullptr);
> +		break;
> +	case ControlTypeSize:
> +		if (is_array) {
> +			Span<const Size> data = value.get<Span<const
> Size>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> +				gst_libcamera_gvalue_set_size(&v, *it);
> +		} else {
> +			gst_libcamera_gvalue_set_size(&v, value.get<const
> Size>());
> +		}
> +		break;
> +	case ControlTypePoint:
> +		if (is_array) {
> +			Span<const Point> data = value.get<Span<const
> Point>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> +				gst_libcamera_gvalue_set_point(&v, *it);
> +		} else {
> +			gst_libcamera_gvalue_set_point(&v, value.get<const
> Point>());
> +		}
> +		break;
> +	case ControlTypeRectangle:
> +		if (is_array) {
> +			Span<const Rectangle> data = value.get<Span<const
> Rectangle>>();
> +			for (auto it = data.begin(); it != data.end(); ++it)
> +				gst_libcamera_gvalue_set_rectangle(&v, *it);
> +		} else {
> +			gst_libcamera_gvalue_set_rectangle(&v,
> value.get<const Rectangle>());
> +		}
> +		break;
> +	case ControlTypeNone:
> +		[[fallthrough]];
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (GST_VALUE_HOLDS_ARRAY(&v))
> +		gst_structure_set_value(structure, prop.c_str(), &v);
> +
> +	return 0;
> +}
> diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-
> utils.h
> index 1812be75..35df56fb 100644
> --- a/src/gstreamer/gstlibcamera-utils.h
> +++ b/src/gstreamer/gstlibcamera-utils.h
> @@ -29,6 +29,9 @@ void gst_libcamera_gvalue_set_point(GValue *value, const
> libcamera::Point &point
>  void gst_libcamera_gvalue_set_size(GValue *value, const libcamera::Size
> &size);
>  void gst_libcamera_gvalue_set_rectangle(GValue *value, const
> libcamera::Rectangle &rect);
>  libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);
> +int gst_libcamera_set_structure_field(GstStructure *structure,
> +				      const libcamera::ControlId *id,
> +				      const libcamera::ControlValue &value);
>  
>  #if !GST_CHECK_VERSION(1, 16, 0)
>  static inline void gst_clear_event(GstEvent **event_ptr)
> diff --git a/src/gstreamer/gstlibcameraprovider.cpp
> b/src/gstreamer/gstlibcameraprovider.cpp
> index 5da96ea3..1545c856 100644
> --- a/src/gstreamer/gstlibcameraprovider.cpp
> +++ b/src/gstreamer/gstlibcameraprovider.cpp
> @@ -12,6 +12,7 @@
>  
>  #include <libcamera/camera.h>
>  #include <libcamera/camera_manager.h>
> +#include <libcamera/property_ids.h>
>  
>  #include "gstlibcamerasrc.h"
>  #include "gstlibcamera-utils.h"
> @@ -144,12 +145,24 @@ gst_libcamera_device_new(const std::shared_ptr<Camera>
> &camera)
>  			gst_caps_append(caps, sub_caps);
>  	}
>  
> +	g_autoptr(GstStructure) props = gst_structure_new_empty("camera-
> properties");
> +	for (const auto &[key, value] : camera->properties()) {
> +		const ControlId *id = properties::properties.at(key);
> +
> +		int ret = gst_libcamera_set_structure_field(props, id,
> value);
> +		if (ret < 0) {
> +			GST_ERROR("Failed to retrieve value for %s property",
> id->name().c_str());
> +			return nullptr;
> +		}
> +	}
> +
>  	return GST_DEVICE(g_object_new(GST_TYPE_LIBCAMERA_DEVICE,
>  				       /* \todo Use a unique identifier
> instead of camera name. */
>  				       "name", name,
>  				       "display-name", name,
>  				       "caps", caps,
>  				       "device-class", "Source/Video",
> +				       "properties", props,
>  				       nullptr));
>  }
>

Patch
diff mbox series

diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index 63e00fe0..d11d7ba9 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -8,6 +8,8 @@ 
 
 #include "gstlibcamera-utils.h"
 
+#include <string>
+
 #include <libcamera/control_ids.h>
 #include <libcamera/formats.h>
 
@@ -333,6 +335,33 @@  bare_structure_from_format(const PixelFormat &format)
 	}
 }
 
+static const struct {
+	ControlType c_type;
+	GType g_type;
+} control_type_gtype_map[] = {
+	{ ControlTypeBool, G_TYPE_BOOLEAN },
+	{ ControlTypeByte, G_TYPE_UINT },
+	{ ControlTypeUnsigned16, G_TYPE_UINT },
+	{ ControlTypeUnsigned32, G_TYPE_UINT },
+	{ ControlTypeInteger32, G_TYPE_INT },
+	{ ControlTypeInteger64, G_TYPE_INT64 },
+	{ ControlTypeFloat, G_TYPE_FLOAT },
+	{ ControlTypeString, G_TYPE_STRING },
+	{ ControlTypeRectangle, GST_TYPE_ARRAY },
+	{ ControlTypeSize, GST_TYPE_ARRAY },
+	{ ControlTypePoint, GST_TYPE_ARRAY },
+};
+
+static GType
+control_type_to_gtype(const ControlType &type)
+{
+	for (auto &a : control_type_gtype_map) {
+		if (a.c_type == type)
+			return a.g_type;
+	}
+	return G_TYPE_INVALID;
+}
+
 GstCaps *
 gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
 {
@@ -706,3 +735,171 @@  gst_libcamera_get_camera_manager(int &ret)
 
 	return cm;
 }
+
+int gst_libcamera_set_structure_field(GstStructure *structure, const ControlId *id,
+				      const ControlValue &value)
+{
+	std::string prop = "api.libcamera." + id->name();
+	g_auto(GValue) v = G_VALUE_INIT;
+	g_auto(GValue) x = G_VALUE_INIT;
+	gboolean is_array = value.isArray();
+
+	GType type = control_type_to_gtype(value.type());
+	if (type == G_TYPE_INVALID)
+		return -EINVAL;
+
+	if (is_array || type == GST_TYPE_ARRAY)
+		g_value_init(&v, GST_TYPE_ARRAY);
+
+	switch (value.type()) {
+	case ControlTypeBool:
+		if (is_array) {
+			Span<const bool> data = value.get<Span<const bool>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_boolean(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_BOOLEAN,
+					  value.get<const bool>(), nullptr);
+		}
+		break;
+	case ControlTypeByte:
+		if (is_array) {
+			Span<const uint8_t> data = value.get<Span<const uint8_t>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_uint(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_UINT,
+					  value.get<const uint8_t>(), nullptr);
+		}
+		break;
+	case ControlTypeUnsigned16:
+		if (is_array) {
+			Span<const uint16_t> data = value.get<Span<const uint16_t>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_uint(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_UINT,
+					  value.get<const uint16_t>(), nullptr);
+		}
+		break;
+	case ControlTypeUnsigned32:
+		if (is_array) {
+			Span<const uint32_t> data = value.get<Span<const uint32_t>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_uint(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_UINT,
+					  value.get<const uint32_t>(), nullptr);
+		}
+		break;
+	case ControlTypeInteger32:
+		if (is_array) {
+			Span<const int32_t> data = value.get<Span<const int32_t>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_int(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			if (!id->enumerators().empty()) {
+				int32_t val = value.get<int32_t>();
+				const auto &iter = id->enumerators().find(val);
+				if (iter != id->enumerators().end()) {
+					gst_structure_set(structure, prop.c_str(),
+							  G_TYPE_STRING,
+							  iter->second.c_str(),
+							  nullptr);
+				} else {
+					return -EINVAL;
+				}
+			} else {
+				gst_structure_set(structure, prop.c_str(), G_TYPE_INT,
+						  value.get<const int32_t>(), nullptr);
+			}
+		}
+		break;
+	case ControlTypeInteger64:
+		if (is_array) {
+			Span<const int64_t> data = value.get<Span<const int64_t>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_int64(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_INT64,
+					  value.get<const int64_t>(), nullptr);
+		}
+		break;
+	case ControlTypeFloat:
+		if (is_array) {
+			Span<const float> data = value.get<Span<const float>>();
+			for (auto it = data.begin(); it != data.end(); ++it) {
+				g_value_init(&x, type);
+				g_value_set_float(&x, *it);
+				gst_value_array_append_and_take_value(&v, &x);
+			}
+		} else {
+			gst_structure_set(structure, prop.c_str(), G_TYPE_FLOAT,
+					  value.get<const float>(), nullptr);
+		}
+		break;
+	case ControlTypeString:
+		/*
+		 * isArray() is always true for strings hence, unset the GValue
+		 * array because we are going to the toString() helper directly.
+		 */
+		g_value_unset(&v);
+		gst_structure_set(structure, prop.c_str(), G_TYPE_STRING,
+				  value.toString().c_str(), nullptr);
+		break;
+	case ControlTypeSize:
+		if (is_array) {
+			Span<const Size> data = value.get<Span<const Size>>();
+			for (auto it = data.begin(); it != data.end(); ++it)
+				gst_libcamera_gvalue_set_size(&v, *it);
+		} else {
+			gst_libcamera_gvalue_set_size(&v, value.get<const Size>());
+		}
+		break;
+	case ControlTypePoint:
+		if (is_array) {
+			Span<const Point> data = value.get<Span<const Point>>();
+			for (auto it = data.begin(); it != data.end(); ++it)
+				gst_libcamera_gvalue_set_point(&v, *it);
+		} else {
+			gst_libcamera_gvalue_set_point(&v, value.get<const Point>());
+		}
+		break;
+	case ControlTypeRectangle:
+		if (is_array) {
+			Span<const Rectangle> data = value.get<Span<const Rectangle>>();
+			for (auto it = data.begin(); it != data.end(); ++it)
+				gst_libcamera_gvalue_set_rectangle(&v, *it);
+		} else {
+			gst_libcamera_gvalue_set_rectangle(&v, value.get<const Rectangle>());
+		}
+		break;
+	case ControlTypeNone:
+		[[fallthrough]];
+	default:
+		return -EINVAL;
+	}
+
+	if (GST_VALUE_HOLDS_ARRAY(&v))
+		gst_structure_set_value(structure, prop.c_str(), &v);
+
+	return 0;
+}
diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h
index 1812be75..35df56fb 100644
--- a/src/gstreamer/gstlibcamera-utils.h
+++ b/src/gstreamer/gstlibcamera-utils.h
@@ -29,6 +29,9 @@  void gst_libcamera_gvalue_set_point(GValue *value, const libcamera::Point &point
 void gst_libcamera_gvalue_set_size(GValue *value, const libcamera::Size &size);
 void gst_libcamera_gvalue_set_rectangle(GValue *value, const libcamera::Rectangle &rect);
 libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);
+int gst_libcamera_set_structure_field(GstStructure *structure,
+				      const libcamera::ControlId *id,
+				      const libcamera::ControlValue &value);
 
 #if !GST_CHECK_VERSION(1, 16, 0)
 static inline void gst_clear_event(GstEvent **event_ptr)
diff --git a/src/gstreamer/gstlibcameraprovider.cpp b/src/gstreamer/gstlibcameraprovider.cpp
index 5da96ea3..1545c856 100644
--- a/src/gstreamer/gstlibcameraprovider.cpp
+++ b/src/gstreamer/gstlibcameraprovider.cpp
@@ -12,6 +12,7 @@ 
 
 #include <libcamera/camera.h>
 #include <libcamera/camera_manager.h>
+#include <libcamera/property_ids.h>
 
 #include "gstlibcamerasrc.h"
 #include "gstlibcamera-utils.h"
@@ -144,12 +145,24 @@  gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)
 			gst_caps_append(caps, sub_caps);
 	}
 
+	g_autoptr(GstStructure) props = gst_structure_new_empty("camera-properties");
+	for (const auto &[key, value] : camera->properties()) {
+		const ControlId *id = properties::properties.at(key);
+
+		int ret = gst_libcamera_set_structure_field(props, id, value);
+		if (ret < 0) {
+			GST_ERROR("Failed to retrieve value for %s property", id->name().c_str());
+			return nullptr;
+		}
+	}
+
 	return GST_DEVICE(g_object_new(GST_TYPE_LIBCAMERA_DEVICE,
 				       /* \todo Use a unique identifier instead of camera name. */
 				       "name", name,
 				       "display-name", name,
 				       "caps", caps,
 				       "device-class", "Source/Video",
+				       "properties", props,
 				       nullptr));
 }