[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