diff options
Diffstat (limited to 'src/gstreamer')
-rw-r--r-- | src/gstreamer/gstlibcamera-controls.cpp.in | 332 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamera-controls.h | 43 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerasrc.cpp | 22 | ||||
-rw-r--r-- | src/gstreamer/meson.build | 10 |
4 files changed, 404 insertions, 3 deletions
diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in new file mode 100644 index 00000000..ace36b71 --- /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_str('\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_str('\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_str('\t\t\t') }}, + {{ spec|indent_str('\t\t\t') }}, + (GParamFlags) (GST_PARAM_CONTROLLABLE | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS) + ) +{%- else %} + {{ spec|indent_str('\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 %zu 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@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()), |