[PATCH 2/3] gstreamer: Generate controls from control_ids_*.yaml files
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Fri Aug 9 13:35:06 CEST 2024
Hi Jaslo,
Thank you for the patch.
On Mon, Aug 05, 2024 at 11:28:37AM +0200, Jaslo Ziska wrote:
> 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 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.
Nice explanation, that's very helpful for all of us who are not
gstreamer specialists (which includes myself).
> Signed-off-by: Jaslo Ziska <jaslo at ziska.de>
> ---
> src/gstreamer/gstlibcamera-controls.cpp.in | 46 +++
> src/gstreamer/gstlibcamera-controls.h | 36 ++
> src/gstreamer/gstlibcamerasrc.cpp | 17 +-
> src/gstreamer/meson.build | 14 +
> utils/gen-gst-controls.py | 398 +++++++++++++++++++++
> utils/meson.build | 1 +
> 6 files changed, 509 insertions(+), 3 deletions(-)
> create mode 100644 src/gstreamer/gstlibcamera-controls.cpp.in
> create mode 100644 src/gstreamer/gstlibcamera-controls.h
> create mode 100755 utils/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..ff93f5c3
> --- /dev/null
> +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Collabora Ltd.
> + * Author: Nicolas Dufresne <nicolas.dufresne at collabora.com>
> + *
> + * gstlibcamera-controls.cpp - GStreamer Camera Controls
You can drop the file name part and just keep the description. There
were a few remaining file names in templates, I have posted a patch to
drop them.
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#include "gstlibcamera-controls.h"
> +
> +#include <libcamera/control_ids.h>
> +
> +using namespace libcamera;
> +
> +${enum_def}
I've posted "[PATCH 00/10] libcamera: Improve code generation for
controls" (https://patchwork.libcamera.org/project/libcamera/list/?series=4506)
yesterday to convert our Python string templates to jinja2 templates.
jinja2 allows better separation of the template logic and the data
logic, making the code much more readable. Would you be able to rebase
on top of that series ? I expect it will be merged soon.
I'm sorry for the extra work. I had conversion to jinja2 in mind for a
while, and your series triggered me to complete it, in order to share
more code between the control code generation scripts. Hopefully you
will like the new templates better as well.
> +
> +void GstCameraControls::installProperties(GObjectClass *klass, int lastPropId)
> +{
> +${install_properties}
> +}
> +
> +bool GstCameraControls::getProperty(guint propId, GValue *value, GParamSpec *pspec)
> +{
> + switch (propId) {
> +${get_properties}
> + default:
> + return false;
> + }
> +}
> +
> +bool GstCameraControls::setProperty(guint propId, const GValue *value,
> + [[maybe_unused]] GParamSpec *pspec)
> +{
> + switch (propId) {
> +${set_properties}
> + default:
> + return false;
> + }
> +}
> +
> +void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
> +{
> + request->controls().merge(controls_);
> +}
> diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
> new file mode 100644
> index 00000000..4e1d5bf9
> --- /dev/null
> +++ b/src/gstreamer/gstlibcamera-controls.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Collabora Ltd.
> + * Author: Nicolas Dufresne <nicolas.dufresne at collabora.com>
> + *
> + * gstlibcamera-controls.h - GStreamer Camera Controls
Here too please drop the file name and keep the description only.
> + */
> +
> +#pragma once
#include <memory>
for std::unique_ptr<>
> +
> +#include <libcamera/controls.h>
> +#include <libcamera/request.h>
> +
> +#include "gstlibcamerasrc.h"
> +
> +namespace libcamera {
> +
> +class GstCameraControls
> +{
> +public:
> + GstCameraControls() {};
> + ~GstCameraControls() {};
GstCameraControls() = default;
~GstCameraControls() = default;
or just drop them, as they will be implicitly declared and defined by
default.
> +
> + 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 applyControls(std::unique_ptr<libcamera::Request> &request);
> +
> +private:
> + /* set of user modified controls */
> + ControlList controls_;
> +};
> +
> +} /* namespace libcamera */
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> index 5a3e2989..85dab67f 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));
>
> @@ -722,6 +728,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 +736,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 +748,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 +957,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..e6c20124 100644
> --- a/src/gstreamer/meson.build
> +++ b/src/gstreamer/meson.build
> @@ -25,6 +25,20 @@ libcamera_gst_sources = [
> 'gstlibcamerasrc.cpp',
> ]
>
> +# Generate gstreamer control properties
> +
> +gen_gst_controls_input_files = []
> +gen_gst_controls_template = files('gstlibcamera-controls.cpp.in')
> +foreach file : controls_files
> + gen_gst_controls_input_files += files('../libcamera/' + file)
> +endforeach
> +
> +libcamera_gst_sources += custom_target('gstlibcamera-controls.cpp',
> + input : gen_gst_controls_input_files,
> + output : 'gstlibcamera-controls.cpp',
> + command : [gen_gst_controls, '-o', '@OUTPUT@',
> + '-t', gen_gst_controls_template, '@INPUT@'])
> +
> libcamera_gst_cpp_args = [
> '-DVERSION="@0@"'.format(libcamera_git_version),
> '-DPACKAGE="@0@"'.format(meson.project_name()),
> diff --git a/utils/gen-gst-controls.py b/utils/gen-gst-controls.py
> new file mode 100755
> index 00000000..d0c12b50
> --- /dev/null
> +++ b/utils/gen-gst-controls.py
> @@ -0,0 +1,398 @@
> +#!/usr/bin/env python3
> +
> +import argparse
> +import re
> +import string
> +import sys
> +import yaml
> +
> +
> +class ControlEnum(object):
> + def __init__(self, data):
> + self.__data = data
> +
> + @property
> + def description(self):
> + """The enum description"""
> + return self.__data.get('description')
> +
> + @property
> + def name(self):
> + """The enum name"""
> + return self.__data.get('name')
> +
> + @property
> + def value(self):
> + """The enum value"""
> + return self.__data.get('value')
> +
> +
> +class Control(object):
> + def __init__(self, name, data, vendor):
> + self.__name = name
> + self.__data = data
> + self.__enum_values = None
> + self.__size = None
> + self.__vendor = vendor
> +
> + enum_values = data.get('enum')
> + if enum_values is not None:
> + self.__enum_values = [ControlEnum(enum) for enum in enum_values]
> +
> + size = self.__data.get('size')
> + if size is not None:
> + if len(size) == 0:
> + raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension')
> +
> + # Compute the total number of elements in the array. If any of the
> + # array dimension is a string, the array is variable-sized.
> + num_elems = 1
> + for dim in size:
> + if type(dim) is str:
> + num_elems = 0
> + break
> +
> + dim = int(dim)
> + if dim <= 0:
> + raise RuntimeError(f'Control `{self.__name}` size must have positive values only')
> +
> + num_elems *= dim
> +
> + self.__size = num_elems
> +
> + @property
> + def description(self):
> + """The control description"""
> + return self.__data.get('description')
> +
> + @property
> + def enum_values(self):
> + """The enum values, if the control is an enumeration"""
> + if self.__enum_values is None:
> + return
> + for enum in self.__enum_values:
> + yield enum
> +
> + @property
> + def is_enum(self):
> + """Is the control an enumeration"""
> + return self.__enum_values is not None
> +
> + @property
> + def vendor(self):
> + """The vendor string, or None"""
> + return self.__vendor
> +
> + @property
> + def name(self):
> + """The control name (CamelCase)"""
> + return self.__name
> +
> + @property
> + def type(self):
> + typ = self.__data.get('type')
> + size = self.__data.get('size')
> +
> + if typ == 'string':
> + return 'std::string'
> +
> + if self.__size is None:
> + return typ
> +
> + if self.__size:
> + return f"Span<const {typ}, {self.__size}>"
> + else:
> + return f"Span<const {typ}>"
> +
> + @property
> + def element_type(self):
> + typ = self.__data.get('type')
> + return typ
> +
> + @property
> + def size(self):
> + return self.__size
> +
> +
> +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, indent = 0):
> + # 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([indent * '\t' + '"' + 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 indent(s, n):
> + lines = s.split('\n')
> + return '\n'.join([n * '\t' + line for line in lines])
> +
> +
> +def generate_cpp(controls):
> + # Beginning of a GEnumValue definition for enum controls
> + enum_values_start = string.Template('static const GEnumValue ${name_snake_case}_types[] = {')
> + # Definition of the GEnumValue variant for each enum control variant
> + # Because the description might have multiple lines it will get indented below
> + enum_values_values = string.Template('''{
> +\tcontrols${vendor_namespace}::${name},
> +${description},
> +\t"${nick}"
> +},''')
> + # End of a GEnumValue definition and definition of the matching _get_type() function
> + enum_values_end = string.Template('''\t{0, NULL, NULL}
> +};
> +
> +#define TYPE_${name_upper} (${name_snake_case}_get_type())
> +static GType ${name_snake_case}_get_type(void)
> +{
> +\tstatic GType ${name_snake_case}_type = 0;
> +
> +\tif (!${name_snake_case}_type)
> +\t\t${name_snake_case}_type = g_enum_register_static("${name}", ${name_snake_case}_types);
> +
> +\treturn ${name_snake_case}_type;
> +}
> +''')
> +
> + # Creation of the type spec for the different types of controls
> + # The description (and the element_spec for the array) might have multiple lines and will get indented below
> + spec_array = string.Template('''gst_param_spec_array(
> +\t"${spec_name}",
> +\t"${nick}",
> +${description},
> +${element_spec},
> +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
> +)''')
> + spec_bool = string.Template('''g_param_spec_boolean(
> +\t"${spec_name}",
> +\t"${nick}",
> +${description},
> +\t${default},
> +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
> +)''')
> + spec_enum = string.Template('''g_param_spec_enum(
> +\t"${spec_name}",
> +\t"${nick}",
> +${description},
> +\tTYPE_${enum},
> +\t${default},
> +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
> +)''')
> + spec_numeric = string.Template('''g_param_spec_${gtype}(
> +\t"${spec_name}",
> +\t"${nick}",
> +${description},
> +\t${min},
> +\t${max},
> +\t${default},
> +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
> +)''')
> +
> + # The g_object_class_install_property() function call for each control
> + install_property = string.Template('''\tg_object_class_install_property(
> +\t\tklass,
> +\t\tlastPropId + controls${vendor_namespace}::${name_upper},
> +${spec}
> +\t);''')
> +
> + # The _get_property() switch cases for each control
> + get_property = string.Template('''case controls${vendor_namespace}::${name_upper}: {
> +\tauto val = controls_.get(controls${vendor_namespace}::${name});
> +\tauto spec = G_PARAM_SPEC_${gtype_upper}(pspec);
> +\tg_value_set_${gtype}(value, val.value_or(spec->default_value));
> +\treturn true;
> +}''')
> + get_array_property = string.Template('''case controls${vendor_namespace}::${name_upper}: {
> +\tauto val = controls_.get(controls${vendor_namespace}::${name});
> +\tif (val) {
> +\t\tfor (size_t i = 0; i < val->size(); ++i) {
> +\t\t\tGValue v = G_VALUE_INIT;
> +\t\t\tg_value_init(&v, G_TYPE_${gtype_upper});
> +\t\t\tg_value_set_${gtype}(&v, (*val)[i]);
> +\t\t\tgst_value_array_append_and_take_value(value, &v);
> +\t\t}
> +\t}
> +\treturn true;
> +}''')
> +
> + # The _set_property() switch cases for each control
> + set_property = string.Template('''case controls${vendor_namespace}::${name_upper}:
> +\tcontrols_.set(controls${vendor_namespace}::${name}, g_value_get_${gtype}(value));
> +\treturn true;''')
> + set_array_property = string.Template('''case controls${vendor_namespace}::${name_upper}: {
> +\tsize_t size = gst_value_array_get_size(value);
> +\tstd::vector<${type}> val(size);
> +\tfor (size_t i = 0; i < size; ++i) {
> +\t\tconst GValue *v = gst_value_array_get_value(value, i);
> +\t\tval[i] = g_value_get_${gtype}(v);
> +\t}
> +\tcontrols_.set(controls${vendor_namespace}::${name}, Span<const ${type}, ${size}>(val.data(), size));
> +\treturn true;
> +}''')
> +
> + enum_def = []
> + install_properties = []
> + get_properties = []
> + set_properties = []
> +
> + for ctrl in controls:
> + is_array = ctrl.size is not None
> + size = ctrl.size
> + if size == 0:
> + size = 'dynamic_extent'
> +
> + # Determine the matching glib type for each C++ type used in the controls
> + gtype = ''
> + if ctrl.is_enum:
> + gtype = 'enum'
> + elif ctrl.element_type == 'bool':
> + gtype = 'boolean'
> + elif ctrl.element_type == 'float':
> + gtype = 'float'
> + elif ctrl.element_type == 'int32_t':
> + gtype = 'int'
> + elif ctrl.element_type == 'int64_t':
> + gtype = 'int64'
> + elif ctrl.element_type == 'uint8_t':
> + gtype = 'uchar'
> + elif ctrl.element_type == 'Rectangle':
> + # TODO: Handle Rectangle
> + continue
> + else:
> + raise RuntimeError(f'The type `{ctrl.element_type}` is unknown')
> +
> + vendor_prefix = ''
> + vendor_namespace = ''
> + if ctrl.vendor != 'libcamera':
> + vendor_prefix = ctrl.vendor + '-'
> + vendor_namespace = '::' + ctrl.vendor
> +
> + name_snake_case = snake_case(ctrl.name)
> + name_upper = name_snake_case.upper()
> +
> + info = {
> + 'name': ctrl.name,
> + 'vendor_namespace': vendor_namespace,
> + 'name_snake_case': name_snake_case,
> + 'name_upper': name_upper,
> + 'spec_name': vendor_prefix + kebab_case(ctrl.name),
> + 'nick': ''.join([c.isupper() and (' ' + c) or c for c in ctrl.name]).strip(' '),
> + 'description': format_description(ctrl.description, indent=1),
> + 'gtype': gtype,
> + 'gtype_upper': gtype.upper(),
> + 'type': ctrl.element_type,
> + 'size': size,
> + }
> +
> + if ctrl.is_enum:
> + enum_def.append(enum_values_start.substitute(info))
> +
> + common_prefix = find_common_prefix([enum.name for enum in ctrl.enum_values])
> +
> + for enum in ctrl.enum_values:
> + values_info = {
> + 'name': enum.name,
> + 'vendor_namespace': vendor_namespace,
> + 'description': format_description(enum.description, indent=1),
> + 'nick': kebab_case(enum.name.removeprefix(common_prefix)),
> + }
> + enum_def.append(indent(enum_values_values.substitute(values_info), 1))
> +
> + enum_def.append(enum_values_end.substitute(info))
> +
> + spec = ''
> + if ctrl.is_enum:
> + spec = spec_enum.substitute({'enum': name_upper, 'default': 0, **info})
> + elif gtype == 'boolean':
> + spec = spec_bool.substitute({'default': 'false', **info})
> + elif gtype == 'float':
> + spec = spec_numeric.substitute({'min': '-G_MAXFLOAT', 'max': 'G_MAXFLOAT', 'default': 0, **info})
> + elif gtype == 'int':
> + spec = spec_numeric.substitute({'min': 'G_MININT', 'max': 'G_MAXINT', 'default': 0, **info})
> + elif gtype == 'int64':
> + spec = spec_numeric.substitute({'min': 'G_MININT64', 'max': 'G_MAXINT64', 'default': 0, **info})
> + elif gtype == 'uchar':
> + spec = spec_numeric.substitute({'min': '0', 'max': 'G_MAXUINT8', 'default': 0, **info})
> +
> + if is_array:
> + spec = spec_array.substitute({'element_spec': indent(spec, 1), **info})
> +
> + install_properties.append(install_property.substitute({'spec': indent(spec, 2), **info}))
> +
> + if is_array:
> + get_properties.append(indent(get_array_property.substitute(info), 1))
> + set_properties.append(indent(set_array_property.substitute(info), 1))
> + else:
> + get_properties.append(indent(get_property.substitute(info), 1))
> + set_properties.append(indent(set_property.substitute(info), 1))
> +
> + return {
> + 'enum_def': '\n'.join(enum_def),
> + 'install_properties': '\n'.join(install_properties),
> + 'get_properties': '\n'.join(get_properties),
> + 'set_properties': '\n'.join(set_properties),
> + }
> +
> +
> +def fill_template(template, data):
> + template = open(template, 'rb').read()
> + template = template.decode('utf-8')
> + template = string.Template(template)
> + return template.substitute(data)
> +
> +
> +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 = open(input, 'rb').read()
> + vendor = yaml.safe_load(data)['vendor']
> + ctrls = yaml.safe_load(data)['controls']
> + controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]
> +
> + data = generate_cpp(controls)
> +
> + data = fill_template(args.template, data)
> +
> + if args.output:
> + output = open(args.output, 'wb')
> + output.write(data.encode('utf-8'))
> + output.close()
> + else:
> + sys.stdout.write(data)
> +
> + return 0
> +
> +
> +if __name__ == '__main__':
> + sys.exit(main(sys.argv))
> diff --git a/utils/meson.build b/utils/meson.build
> index 8e28ada7..89597f7f 100644
> --- a/utils/meson.build
> +++ b/utils/meson.build
> @@ -9,6 +9,7 @@ py_modules += ['yaml']
> gen_controls = files('gen-controls.py')
> gen_formats = files('gen-formats.py')
> gen_header = files('gen-header.sh')
> +gen_gst_controls = files('gen-gst-controls.py')
>
> ## Module signing
> gen_ipa_priv_key = files('gen-ipa-priv-key.sh')
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list