[PATCH v3 3/3] gstreamer: Generate controls from control_ids_*.yaml files

Jaslo Ziska jaslo at ziska.de
Mon Oct 21 18:07:50 CEST 2024


Hi Nicolas,

thanks for the review.

Nicolas Dufresne <nicolas at ndufresne.ca> writes:
> Le jeudi 17 octobre 2024 à 19:04 +0200, Jaslo Ziska a écrit :
>> This commit implements gstreamer controls for the libcamera 
>> element by
>> generating the controls from the control_ids_*.yaml files using 
>> a new
>> gen-gst-controls.py script. The appropriate meson files are 
>> also changed
>> to automatically run the script when building.
>> 
>> The gen-gst-controls.py script works similar to the 
>> gen-controls.py
>> script by parsing the control_ids_*.yaml files and generating 
>> C++ code
>> for each exposed control.
>> For the controls to be used as gstreamer properties the type 
>> for each
>> control needs to be translated to the appropriate glib type and 
>> a
>> GEnumValue is generated for each enum control. Then a
>> g_object_install_property(), _get_property() and 
>> _set_property()
>> function is generated for each control.
>> The vendor controls get prefixed with "$vendor-" in the final 
>> gstreamer
>> property name.
>> 
>> The C++ code generated by the gen-gst-controls.py script is 
>> written into
>> the template gstlibcamerasrc-controls.cpp.in file. The matching
>> gstlibcamerasrc-controls.h header defines the GstCameraControls 
>> class
>> which handles the installation of the gstreamer properties as 
>> well as
>> keeping track of the control values and setting and getting the
>> controls. The content of these functions is generated in the 
>> Python
>> script.
>> 
>> Finally the libcamerasrc element itself is edited to make use 
>> of the new
>> GstCameraControls class. The way this works is by defining a 
>> PROP_LAST
>> enum variant which is passed to the installProperties() 
>> function so the
>> properties are defined with the appropriate offset. When 
>> getting or
>> setting a property PROP_LAST is subtracted from the requested 
>> property
>> to translate the control back into a libcamera::controls:: enum
>> variant.
>> 
>> Signed-off-by: Jaslo Ziska <jaslo at ziska.de>
>> ---
>>  src/gstreamer/gstlibcamera-controls.cpp.in | 332 
>>  +++++++++++++++++++++
>>  src/gstreamer/gstlibcamera-controls.h      |  43 +++
>>  src/gstreamer/gstlibcamerasrc.cpp          |  22 +-
>>  src/gstreamer/meson.build                  |  10 +
>>  utils/codegen/controls.py                  |   8 +
>>  utils/codegen/gen-gst-controls.py          | 166 +++++++++++
>>  utils/codegen/meson.build                  |   1 +
>>  7 files changed, 579 insertions(+), 3 deletions(-)
>>  create mode 100644 src/gstreamer/gstlibcamera-controls.cpp.in
>>  create mode 100644 src/gstreamer/gstlibcamera-controls.h
>>  create mode 100755 utils/codegen/gen-gst-controls.py
>> 
>> diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in 
>> b/src/gstreamer/gstlibcamera-controls.cpp.in
>> new file mode 100644
>> index 00000000..6861f5d1
>> --- /dev/null
>> +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
>> @@ -0,0 +1,332 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2024, Jaslo Ziska
>> + *
>> + * GStreamer Camera Controls
>> + *
>> + * This file is auto-generated. Do not edit.
>> + */
>> +
>> +#include <vector>
>> +
>> +#include <libcamera/control_ids.h>
>> +#include <libcamera/controls.h>
>> +#include <libcamera/geometry.h>
>> +
>> +#include "gstlibcamera-controls.h"
>> +
>> +using namespace libcamera;
>> +
>> +static void value_set_rectangle(GValue *value, const Rectangle 
>> &rect)
>> +{
>> +	Point top_left = rect.topLeft();
>> +	Size size = rect.size();
>> +
>> +	GValue x = G_VALUE_INIT;
>> +	g_value_init(&x, G_TYPE_INT);
>> +	g_value_set_int(&x, top_left.x);
>> +	gst_value_array_append_and_take_value(value, &x);
>> +
>> +	GValue y = G_VALUE_INIT;
>> +	g_value_init(&y, G_TYPE_INT);
>> +	g_value_set_int(&y, top_left.y);
>> +	gst_value_array_append_and_take_value(value, &y);
>> +
>> +	GValue width = G_VALUE_INIT;
>> +	g_value_init(&width, G_TYPE_INT);
>> +	g_value_set_int(&width, size.width);
>> +	gst_value_array_append_and_take_value(value, &width);
>> +
>> +	GValue height = G_VALUE_INIT;
>> +	g_value_init(&height, G_TYPE_INT);
>> +	g_value_set_int(&x, size.height);
>> +	gst_value_array_append_and_take_value(value, &height);
>> +}
>> +
>> +static Rectangle value_get_rectangle(const GValue *value)
>> +{
>> +	const GValue *r;
>> +	r = gst_value_array_get_value(value, 0);
>> +	int x = g_value_get_int(r);
>> +	r = gst_value_array_get_value(value, 1);
>> +	int y = g_value_get_int(r);
>> +	r = gst_value_array_get_value(value, 2);
>> +	int w = g_value_get_int(r);
>> +	r = gst_value_array_get_value(value, 3);
>> +	int h = g_value_get_int(r);
>> +
>> +	return Rectangle(x, y, w, h);
>> +}
>> +
>> +{% for vendor, ctrls in controls %}
>> +{%- for ctrl in ctrls if ctrl.is_enum %}
>> +static const GEnumValue {{ ctrl.name|snake_case }}_types[] = {
>> +{%- for enum in ctrl.enum_values %}
>> +	{
>> +		controls::{{ ctrl.namespace }}{{ enum.name }},
>> +		{{ enum.description|format_description|indent('\t\t') 
>> }},
>> +		"{{ enum.gst_name }}"
>> +	},
>> +{%- endfor %}
>> +	{0, NULL, NULL}
>> +};
>> +
>> +#define TYPE_{{ ctrl.name|snake_case|upper }} \
>> +	({{ ctrl.name|snake_case }}_get_type())
>> +static GType {{ ctrl.name|snake_case }}_get_type()
>> +{
>> +	static GType {{ ctrl.name|snake_case }}_type = 0;
>> +
>> +	if (!{{ ctrl.name|snake_case }}_type)
>> +		{{ ctrl.name|snake_case }}_type =
>> +			g_enum_register_static("{{ ctrl.name }}",
>> +					       {{ ctrl.name|snake_case }}_types);
>> +
>> +	return {{ ctrl.name|snake_case }}_type;
>> +}
>> +{% endfor %}
>> +{%- endfor %}
>> +
>> +void GstCameraControls::installProperties(GObjectClass *klass, 
>> int lastPropId)
>> +{
>> +{%- for vendor, ctrls in controls %}
>> +{%- for ctrl in ctrls %}
>> +
>> +{%- set spec %}
>> +{%- if ctrl.is_rectangle -%}
>> +gst_param_spec_array(
>> +{%- else -%}
>> +g_param_spec_{{ ctrl.gtype }}(
>> +{%- endif -%}
>> +{%- if ctrl.is_array %}
>> +	"{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case 
>> }}-value",
>> +	"{{ ctrl.name }} Value",
>> +	"One {{ ctrl.name }} element value",
>> +{%- else %}
>> +	"{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case }}",
>> +	"{{ ctrl.name }}",
>> +	{{ ctrl.description|format_description|indent('\t') }},
>> +{%- endif %}
>> +{%- if ctrl.is_enum %}
>> +	TYPE_{{ ctrl.name|snake_case|upper }},
>> +	{{ ctrl.default }},
>> +{%- elif ctrl.is_rectangle %}
>> +	g_param_spec_int(
>> +		"rectangle-value",
>> +		"Rectangle Value",
>> +		"One rectangle value, either x, y, width or height.",
>> +		{{ ctrl.min }}, {{ ctrl.max }}, {{ ctrl.default }},
>> +		(GParamFlags) (GST_PARAM_CONTROLLABLE | 
>> G_PARAM_READWRITE |
>> +			       G_PARAM_STATIC_STRINGS)
>> +	),
>> +{%- elif ctrl.gtype == 'boolean' %}
>> +	{{ ctrl.default }},
>> +{%- elif ctrl.gtype in ['float', 'int', 'int64', 'uchar'] %}
>> +	{{ ctrl.min }}, {{ ctrl.max }}, {{ ctrl.default }},
>> +{%- endif %}
>> +	(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE 
>> |
>> +		       G_PARAM_STATIC_STRINGS)
>> +)
>> +{%- endset %}
>> +
>> +	g_object_class_install_property(
>> +		klass,
>> +		lastPropId + controls::{{ ctrl.namespace }}{{ 
>> ctrl.name|snake_case|upper }},
>> +{%- if ctrl.is_array %}
>> +		gst_param_spec_array(
>> +			"{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case 
>> }}",
>> +			"{{ ctrl.name }}",
>> +			{{ 
>> ctrl.description|format_description|indent('\t\t\t') }},
>> +			{{ spec|indent('\t\t\t') }},
>> +			(GParamFlags) (GST_PARAM_CONTROLLABLE |
>> +				       G_PARAM_READWRITE |
>> +				       G_PARAM_STATIC_STRINGS)
>> +		)
>> +{%- else %}
>> +		{{ spec|indent('\t\t') }}
>> +{%- endif %}
>> +	);
>> +{%- endfor %}
>> +{%- endfor %}
>> +}
>> +
>> +bool GstCameraControls::getProperty(guint propId, GValue 
>> *value,
>> +				    [[maybe_unused]] GParamSpec *pspec)
>> +{
>> +	if (!controls_acc_.contains(propId)) {
>> +		GST_WARNING("Control '%s' is not available, default 
>> value will "
>> +			    "be returned",
>> + 
>> controls::controls.at(propId)->name().c_str());
>> +		return true;
>> +	}
>> +	const ControlValue &cv = controls_acc_.get(propId);
>> +
>> +	switch (propId) {
>> +{%- for vendor, ctrls in controls %}
>> +{%- for ctrl in ctrls %}
>> +
>> +	case controls::{{ ctrl.namespace }}{{ 
>> ctrl.name|snake_case|upper }}: {
>> +		auto control = cv.get<{{ ctrl.type }}>();
>> +
>> +{%- if ctrl.is_array %}
>> +		for (size_t i = 0; i < control.size(); ++i) {
>> +			GValue element = G_VALUE_INIT;
>> +{%- if ctrl.is_rectangle %}
>> +			g_value_init(&element, GST_TYPE_PARAM_ARRAY_LIST);
>> +			value_set_rectangle(&element, control[i]);
>> +{%- else %}
>> +			g_value_init(&element, G_TYPE_{{ ctrl.gtype|upper 
>> }});
>> +			g_value_set_{{ ctrl.gtype }}(&element, 
>> control[i]);
>> +{%- endif %}
>> +			gst_value_array_append_and_take_value(value, 
>> &element);
>> +		}
>> +{%- else %}
>> +{%- if ctrl.is_rectangle %}
>> +		value_set_rectangle(value, control);
>> +{%- else %}
>> +		g_value_set_{{ ctrl.gtype }}(value, control);
>> +{%- endif %}
>> +{%- endif %}
>> +
>> +		return true;
>> +	}
>> +{%- endfor %}
>> +{%- endfor %}
>> +
>> +	default:
>> +		return false;
>> +	}
>> +}
>> +
>> +bool GstCameraControls::setProperty(guint propId, const GValue 
>> *value,
>> +				    [[maybe_unused]] GParamSpec *pspec)
>> +{
>> +	/*
>> +	 * Check whether the camera capabilities are already 
>> available.
>> +	 * They might not be available if the pipeline has not 
>> started yet.
>> +	 */
>> +	if (!capabilities_.empty()) {
>> + 		/* If so, check that the control is supported by the 
>> camera. */
>> +		const ControlId *cid = 
>> capabilities_.idmap().at(propId);
>> +		auto info = capabilities_.find(cid);
>> +
>> +		if (info == capabilities_.end()) {
>> +			GST_WARNING("Control '%s' is not supported by the 
>> "
>> +				    "camera and will be ignored",
>> +				    cid->name().c_str());
>> +			return true;
>> +		}
>> +	}
>> +
>> +	switch (propId) {
>> +{%- for vendor, ctrls in controls %}
>> +{%- for ctrl in ctrls %}
>> +
>> +	case controls::{{ ctrl.namespace }}{{ 
>> ctrl.name|snake_case|upper }}: {
>> +		ControlValue control;
>> +{%- if ctrl.is_array %}
>> +		size_t size = gst_value_array_get_size(value);
>> +{%- if ctrl.size != 0 %}
>> +		if (size != {{ ctrl.size }}) {
>> +			GST_ERROR("Incorrect array size for control "
>> +				  "'{{ ctrl.name|kebab_case }}', must be of "
>> +				  "size {{ ctrl.size }}");
>> +			return true;
>> +		}
>> +{%- endif %}
>> +
>> +		std::vector<{{ ctrl.element_type }}> values(size);
>> +		for (size_t i = 0; i < size; ++i) {
>> +			const GValue *element =
>> +				gst_value_array_get_value(value, i);
>> +{%- if ctrl.is_rectangle %}
>> +			if (gst_value_array_get_size(element) != 4) {
>> +				GST_ERROR("Rectangle in control "
>> +					  "'{{ ctrl.name|kebab_case }}' at"
>> +					  "index %lu must be an array of size 4",
>> +					  i);
>> +				return true;
>> +			}
>> +			values[i] = value_get_rectangle(element);
>> +{%- else %}
>> +			values[i] = g_value_get_{{ ctrl.gtype }}(element);
>> +{%- endif %}
>> +		}
>> +
>> +{%- if ctrl.size == 0 %}
>> +		control.set(Span<const {{ ctrl.element_type 
>> }}>(values.data(),
>> +								size));
>> +{%- else %}
>> +		control.set(Span<const {{ ctrl.element_type }},
>> +			         {{ ctrl.size }}>(values.data(),
>> +						  {{ ctrl.size }}));
>> +{%- endif %}
>> +{%- else %}
>> +{%- if ctrl.is_rectangle %}
>> +		if (gst_value_array_get_size(value) != 4) {
>> +			GST_ERROR("Rectangle in control "
>> +				  "'{{ ctrl.name|kebab_case }}' must be an "
>> +				  "array of size 4");
>> +			return true;
>> +		}
>> +		Rectangle val = value_get_rectangle(value);
>> +{%- else %}
>> +		auto val = g_value_get_{{ ctrl.gtype }}(value);
>> +{%- endif %}
>> +		control.set(val);
>> +{%- endif %}
>> +		controls_.set(propId, control);
>> +		controls_acc_.set(propId, control);
>> +		return true;
>> +	}
>> +{%- endfor %}
>> +{%- endfor %}
>> +
>> +	default:
>> +		return false;
>> +	}
>> +}
>> +
>> +void GstCameraControls::setCamera(const 
>> std::shared_ptr<libcamera::Camera> &cam)
>> +{
>> +	capabilities_ = cam->controls();
>> +
>> +	/*
>> +	 * Check the controls which were set before the camera 
>> capabilities were
>> +	 * known. This is required because GStreamer may set 
>> properties before
>> +	 * the pipeline has started and thus before the camera was 
>> known.
>> +	 */
>> +	ControlList new_controls;
>> +	for (auto control = controls_acc_.begin();
>> +	     control != controls_acc_.end();
>> +	     ++control) {
>> +		unsigned int id = control->first;
>> +		ControlValue value = control->second;
>> +
>> +		const ControlId *cid = capabilities_.idmap().at(id);
>> +		auto info = capabilities_.find(cid);
>> +
>> +		/* Only add controls which are supported. */
>> +		if (info != capabilities_.end())
>> +			new_controls.set(id, value);
>> +		else
>> +			GST_WARNING("Control '%s' is not supported by the 
>> "
>> +				    "camera and will be ignored",
>> +				    cid->name().c_str());
>> +	}
>> +
>> +	controls_acc_ = new_controls;
>> +	controls_ = new_controls;
>> +}
>> +
>> +void 
>> GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> 
>> &request)
>> +{
>> +	request->controls().merge(controls_);
>> +	controls_.clear();
>> +}
>> +
>> +void GstCameraControls::readMetadata(libcamera::Request 
>> *request)
>> +{
>> +	controls_acc_.merge(request->metadata(),
>> +			    ControlList::MergePolicy::OverwriteExisting);
>> +}
>> diff --git a/src/gstreamer/gstlibcamera-controls.h 
>> b/src/gstreamer/gstlibcamera-controls.h
>> new file mode 100644
>> index 00000000..749220b5
>> --- /dev/null
>> +++ b/src/gstreamer/gstlibcamera-controls.h
>> @@ -0,0 +1,43 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2023, Collabora Ltd.
>> + *     Author: Nicolas Dufresne 
>> <nicolas.dufresne at collabora.com>
>> + *
>> + * GStreamer Camera Controls
>> + */
>> +
>> +#pragma once
>> +
>> +#include <memory>
>> +
>> +#include <libcamera/camera.h>
>> +#include <libcamera/controls.h>
>> +#include <libcamera/request.h>
>> +
>> +#include "gstlibcamerasrc.h"
>> +
>> +namespace libcamera {
>> +
>> +class GstCameraControls
>> +{
>> +public:
>> +	static void installProperties(GObjectClass *klass, int 
>> lastProp);
>> +
>> +	bool getProperty(guint propId, GValue *value, GParamSpec 
>> *pspec);
>> +	bool setProperty(guint propId, const GValue *value, 
>> GParamSpec *pspec);
>> +
>> +	void setCamera(const std::shared_ptr<libcamera::Camera> 
>> &cam);
>> +
>> +	void applyControls(std::unique_ptr<libcamera::Request> 
>> &request);
>> +	void readMetadata(libcamera::Request *request);
>> +
>> +private:
>> +	/* Supported controls and limits of camera. */
>> +	ControlInfoMap capabilities_;
>> +	/* Set of user modified controls. */
>> +	ControlList controls_;
>> +	/* Accumulator of all controls ever set and metadata 
>> returned by camera */
>> +	ControlList controls_acc_;
>> +};
>> +
>> +} /* namespace libcamera */
>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp 
>> b/src/gstreamer/gstlibcamerasrc.cpp
>> index 40b787c8..8efa25f4 100644
>> --- a/src/gstreamer/gstlibcamerasrc.cpp
>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
>> @@ -37,10 +37,11 @@
>>  
>>  #include <gst/base/base.h>
>>  
>> +#include "gstlibcamera-controls.h"
>> +#include "gstlibcamera-utils.h"
>>  #include "gstlibcameraallocator.h"
>>  #include "gstlibcamerapad.h"
>>  #include "gstlibcamerapool.h"
>> -#include "gstlibcamera-utils.h"
>>  
>>  using namespace libcamera;
>>  
>> @@ -128,6 +129,7 @@ struct GstLibcameraSrcState {
>>  
>>  	ControlList initControls_;
>>  	guint group_id_;
>> +	GstCameraControls controls_;
>>  
>>  	int queueRequest();
>>  	void requestCompleted(Request *request);
>> @@ -153,6 +155,7 @@ struct _GstLibcameraSrc {
>>  enum {
>>  	PROP_0,
>>  	PROP_CAMERA_NAME,
>> +	PROP_LAST
>>  };
>>  
>>  static void gst_libcamera_src_child_proxy_init(gpointer 
>>  g_iface,
>> @@ -183,6 +186,9 @@ int GstLibcameraSrcState::queueRequest()
>>  	if (!request)
>>  		return -ENOMEM;
>>  
>> +	/* Apply controls */
>> +	controls_.applyControls(request);
>> +
>>  	std::unique_ptr<RequestWrap> wrap =
>>  		std::make_unique<RequestWrap>(std::move(request));
>>  
>> @@ -226,6 +232,9 @@ 
>> GstLibcameraSrcState::requestCompleted(Request *request)
>>  
>>  	{
>>  		GLibLocker locker(&lock_);
>> +
>> +		controls_.readMetadata(request);
>> +
>>  		wrap = std::move(queuedRequests_.front());
>>  		queuedRequests_.pop();
>>  	}
>> @@ -408,6 +417,8 @@ gst_libcamera_src_open(GstLibcameraSrc 
>> *self)
>>  		return false;
>>  	}
>>  
>> +	self->state->controls_.setCamera(cam);
>> +
>>  	cam->requestCompleted.connect(self->state, 
>>  &GstLibcameraSrcState::requestCompleted);
>>  
>>  	/* No need to lock here, we didn't start our threads yet. 
>>  */
>> @@ -722,6 +733,7 @@ gst_libcamera_src_set_property(GObject 
>> *object, guint prop_id,
>>  {
>>  	GLibLocker lock(GST_OBJECT(object));
>>  	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object);
>> +	GstLibcameraSrcState *state = self->state;
>>  
>>  	switch (prop_id) {
>>  	case PROP_CAMERA_NAME:
>> @@ -729,7 +741,8 @@ gst_libcamera_src_set_property(GObject 
>> *object, guint prop_id,
>>  		self->camera_name = g_value_dup_string(value);
>>  		break;
>>  	default:
>> -		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, 
>> pspec);
>> +		if (!state->controls_.setProperty(prop_id - PROP_LAST, 
>> value, pspec))
>> +			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, 
>> pspec);
>>  		break;
>>  	}
>>  }
>> @@ -740,13 +753,15 @@ gst_libcamera_src_get_property(GObject 
>> *object, guint prop_id, GValue *value,
>>  {
>>  	GLibLocker lock(GST_OBJECT(object));
>>  	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object);
>> +	GstLibcameraSrcState *state = self->state;
>>  
>>  	switch (prop_id) {
>>  	case PROP_CAMERA_NAME:
>>  		g_value_set_string(value, self->camera_name);
>>  		break;
>>  	default:
>> -		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, 
>> pspec);
>> +		if (!state->controls_.getProperty(prop_id - PROP_LAST, 
>> value, pspec))
>> +			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, 
>> pspec);
>>  		break;
>>  	}
>>  }
>> @@ -947,6 +962,7 @@ 
>> gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
>>  							     | G_PARAM_STATIC_STRINGS));
>>  	g_object_class_install_property(object_class, 
>>  PROP_CAMERA_NAME, spec);
>>  
>> +	GstCameraControls::installProperties(object_class, 
>> PROP_LAST);
>>  }
>>  
>>  /* GstChildProxy implementation */
>> diff --git a/src/gstreamer/meson.build 
>> b/src/gstreamer/meson.build
>> index c2a01e7b..6b7e53b5 100644
>> --- a/src/gstreamer/meson.build
>> +++ b/src/gstreamer/meson.build
>> @@ -25,6 +25,16 @@ libcamera_gst_sources = [
>>      'gstlibcamerasrc.cpp',
>>  ]
>>  
>> +# Generate gstreamer control properties
>> +
>> +gen_gst_controls_template = 
>> files('gstlibcamera-controls.cpp.in')
>> +libcamera_gst_sources += 
>> custom_target('gstlibcamera-controls.cpp',
>> +                                       input : controls_files,
>> +                                       output : 
>> 'gstlibcamera-controls.cpp',
>> +                                       command : 
>> [gen_gst_controls, '-o', '@OUTPUT@',
>> +                                                  '-t', 
>> gen_gst_controls_template, '@INPUT@'],
>> +                                       env : py_build_env)
>> +
>>  libcamera_gst_cpp_args = [
>>      '-DVERSION="@0@"'.format(libcamera_git_version),
>>      '-DPACKAGE="@0@"'.format(meson.project_name()),
>> diff --git a/utils/codegen/controls.py 
>> b/utils/codegen/controls.py
>> index 7bafee59..03c77cc6 100644
>> --- a/utils/codegen/controls.py
>> +++ b/utils/codegen/controls.py
>> @@ -110,3 +110,11 @@ class Control(object):
>>              return f"Span<const {typ}, {self.__size}>"
>>          else:
>>              return f"Span<const {typ}>"
>> +
>> +    @property
>> +    def element_type(self):
>> +        return self.__data.get('type')
>> +
>> +    @property
>> +    def size(self):
>> +        return self.__size
>> diff --git a/utils/codegen/gen-gst-controls.py 
>> b/utils/codegen/gen-gst-controls.py
>> new file mode 100755
>> index 00000000..0b899258
>> --- /dev/null
>> +++ b/utils/codegen/gen-gst-controls.py
>> @@ -0,0 +1,166 @@
>> +#!/usr/bin/env python3
>> +# SPDX-License-Identifier: GPL-2.0-or-later
>> +# Copyright (C) 2019, Google Inc.
>> +# Copyright (C) 2024, Jaslo Ziska
>> +#
>> +# Authors:
>> +# Laurent Pinchart <laurent.pinchart at ideasonboard.com>
>> +# Jaslo Ziska <jaslo at ziska.de>
>> +#
>> +# Generate gstreamer control properties from YAML
>> +
>> +import argparse
>> +import jinja2
>> +import re
>> +import sys
>> +import yaml
>> +
>> +from controls import Control
>> +
>> +
>> +exposed_controls = [
>> +    "AeEnable", "AeLocked", "AeMeteringMode", 
>> "AeConstraintMode",
>
> AeLocked is not settable, until we can expose it correctly I'd 
> drop. We might
> also want something more accurate, so we can match an exact 
> frame?

You are right, I forgot that the read-only properties should not 
be exposed yet, I will fix that.

>> +    "AeExposureMode", "ExposureValue", "ExposureTime", 
>> "AnalogueGain",
>> +    "AeFlickerMode", "AeFlickerPeriod", "AeFlickerDetected", 
>> "Brightness",
>
> I'm not sure about AeFlickerMode and AeFlickerDetected, and its 
> supposed to be
> read-only. Without a use case I cannot tell the proper way to 
> expose these, I
> would drop that.

I think only AeFlickerDetected is read-only but I can leave them 
out for now.

>> +    "Contrast", "Lux", "AwbEnable", "AwbMode", "AwbLocked", 
>> "ColourGains",
>
> AwbLocked is similar to AeLocked. Same goes for ColourGains and
> ColourTemperature, probably fine as property, but read-only.

ColourGains is writeable, I tested that. But you are correct about 
ColourTemperature.

>> 
>> +    "ColourTemperature", "Saturation", "SensorBlackLevels", 
>> "Sharpness",
>> +    "FocusFoM", "ColourCorrectionMatrix", "ScalerCrop", 
>> "DigitalGain",
>> +    "FrameDuration", "SensorTemperature", "SensorTimestamp", 
>> "AfMode",
>
> FrameDuration and SensorTimestamp should be meta, and attached 
> to GstBuffer, I
> would drop it from the list in this serie. SensorTimestamp is 
> clearly a fit for
> GstReferenceTimestampMeta. I would need to read more about 
> FrameDuration to see
> if we should use it, perhaps could be set to GstBuffer.duration 
> ?

I will just drop them.

>> +    "AfRange", "AfSpeed", "AfMetering", "AfWindows", 
>> "LensPosition", "AfState",
>> +    "AfPauseState", "Gamma",
>
> AfState, AfPausedState are not settable, I'd remove for now 
> until we can
> generate read-only properties.

Adding the readable/writeable property and integrating that into 
the Gstreamer element will be my next goal once this is merged.

Best regards,

Jaslo

> cheers
> Nicolas
>
>> +]
>> +
>> +
>> +def find_common_prefix(strings):
>> +    prefix = strings[0]
>> +
>> +    for string in strings[1:]:
>> +        while string[:len(prefix)] != prefix and prefix:
>> +            prefix = prefix[:len(prefix) - 1]
>> +        if not prefix:
>> +            break
>> +
>> +    return prefix
>> +
>> +
>> +def format_description(description):
>> +    # Substitute doxygen keywords \sa (see also) and \todo
>> +    description = re.sub(r'\\sa((?: \w+)+)',
>> +                         lambda match: 'See also: ' + ', 
>> '.join(
>> +                             map(kebab_case, 
>> match.group(1).strip().split(' '))
>> +                         ) + '.', description)
>> +    description = re.sub(r'\\todo', 'Todo:', description)
>> +
>> +    description = description.strip().split('\n')
>> +    return '\n'.join([
>> +        '"' + line.replace('\\', r'\\').replace('"', r'\"') + 
>> ' "' for line in description if line
>> +    ]).rstrip()
>> +
>> +
>> +def snake_case(s):
>> +    return ''.join([
>> +        c.isupper() and ('_' + c.lower()) or c for c in s
>> +    ]).strip('_')
>> +
>> +
>> +def kebab_case(s):
>> +    return snake_case(s).replace('_', '-')
>> +
>> +
>> +def extend_control(ctrl):
>> +    if ctrl.vendor != 'libcamera':
>> +        ctrl.namespace = f'{ctrl.vendor}::'
>> +        ctrl.vendor_prefix = f'{ctrl.vendor}-'
>> +    else:
>> +        ctrl.namespace = ''
>> +        ctrl.vendor_prefix = ''
>> +
>> +    ctrl.is_array = ctrl.size is not None
>> +
>> +    if ctrl.is_enum:
>> +        # Remove common prefix from enum variant names
>> +        prefix = find_common_prefix([enum.name for enum in 
>> ctrl.enum_values])
>> +        for enum in ctrl.enum_values:
>> +            enum.gst_name = 
>> kebab_case(enum.name.removeprefix(prefix))
>> +
>> +        ctrl.gtype = 'enum'
>> +        ctrl.default = '0'
>> +    elif ctrl.element_type == 'bool':
>> +        ctrl.gtype = 'boolean'
>> +        ctrl.default = 'false'
>> +    elif ctrl.element_type == 'float':
>> +        ctrl.gtype = 'float'
>> +        ctrl.default = '0'
>> +        ctrl.min = '-G_MAXFLOAT'
>> +        ctrl.max = 'G_MAXFLOAT'
>> +    elif ctrl.element_type == 'int32_t':
>> +        ctrl.gtype = 'int'
>> +        ctrl.default = '0'
>> +        ctrl.min = 'G_MININT'
>> +        ctrl.max = 'G_MAXINT'
>> +    elif ctrl.element_type == 'int64_t':
>> +        ctrl.gtype = 'int64'
>> +        ctrl.default = '0'
>> +        ctrl.min = 'G_MININT64'
>> +        ctrl.max = 'G_MAXINT64'
>> +    elif ctrl.element_type == 'uint8_t':
>> +        ctrl.gtype = 'uchar'
>> +        ctrl.default = '0'
>> +        ctrl.min = '0'
>> +        ctrl.max = 'G_MAXUINT8'
>> +    elif ctrl.element_type == 'Rectangle':
>> +        ctrl.is_rectangle = True
>> +        ctrl.default = '0'
>> +        ctrl.min = '0'
>> +        ctrl.max = 'G_MAXINT'
>> +    else:
>> +        raise RuntimeError(f'The type `{ctrl.element_type}` is 
>> unknown')
>> +
>> +    return ctrl
>> +
>> +
>> +def main(argv):
>> +    # Parse command line arguments
>> +    parser = argparse.ArgumentParser()
>> +    parser.add_argument('--output', '-o', metavar='file', 
>> type=str,
>> +                        help='Output file name. Defaults to 
>> standard output if not specified.')
>> +    parser.add_argument('--template', '-t', dest='template', 
>> type=str, required=True,
>> +                        help='Template file name.')
>> +    parser.add_argument('input', type=str, nargs='+',
>> +                        help='Input file name.')
>> +
>> +    args = parser.parse_args(argv[1:])
>> +
>> +    controls = {}
>> +    for input in args.input:
>> +        data = yaml.safe_load(open(input, 'rb').read())
>> +
>> +        vendor = data['vendor']
>> +        ctrls = controls.setdefault(vendor, [])
>> +
>> +        for ctrl in data['controls']:
>> +            ctrl = Control(*ctrl.popitem(), vendor)
>> +
>> +            if ctrl.name in exposed_controls:
>> +                ctrls.append(extend_control(ctrl))
>> +
>> +    data = {'controls': list(controls.items())}
>> +
>> +    env = jinja2.Environment()
>> +    env.filters['format_description'] = format_description
>> +    env.filters['snake_case'] = snake_case
>> +    env.filters['kebab_case'] = kebab_case
>> +    template = env.from_string(open(args.template, 'r', 
>> encoding='utf-8').read())
>> +    string = template.render(data)
>> +
>> +    if args.output:
>> +        with open(args.output, 'w', encoding='utf-8') as 
>> output:
>> +            output.write(string)
>> +    else:
>> +        sys.stdout.write(string)
>> +
>> +    return 0
>> +
>> +
>> +if __name__ == '__main__':
>> +    sys.exit(main(sys.argv))
>> diff --git a/utils/codegen/meson.build 
>> b/utils/codegen/meson.build
>> index adf33bba..904dd66d 100644
>> --- a/utils/codegen/meson.build
>> +++ b/utils/codegen/meson.build
>> @@ -11,6 +11,7 @@ py_modules += ['jinja2', 'yaml']
>>  
>>  gen_controls = files('gen-controls.py')
>>  gen_formats = files('gen-formats.py')
>> +gen_gst_controls = files('gen-gst-controls.py')
>>  gen_header = files('gen-header.sh')
>>  gen_ipa_pub_key = files('gen-ipa-pub-key.py')
>>  gen_tracepoints = files('gen-tp-header.py')


More information about the libcamera-devel mailing list