summaryrefslogtreecommitdiff
path: root/src/gstreamer
diff options
context:
space:
mode:
Diffstat (limited to 'src/gstreamer')
-rw-r--r--src/gstreamer/gstlibcamera-controls.cpp.in332
-rw-r--r--src/gstreamer/gstlibcamera-controls.h43
-rw-r--r--src/gstreamer/gstlibcamera-utils.cpp540
-rw-r--r--src/gstreamer/gstlibcamera-utils.h36
-rw-r--r--src/gstreamer/gstlibcamera.cpp4
-rw-r--r--src/gstreamer/gstlibcameraallocator.cpp57
-rw-r--r--src/gstreamer/gstlibcameraallocator.h11
-rw-r--r--src/gstreamer/gstlibcamerapad.cpp61
-rw-r--r--src/gstreamer/gstlibcamerapad.h13
-rw-r--r--src/gstreamer/gstlibcamerapool.cpp56
-rw-r--r--src/gstreamer/gstlibcamerapool.h10
-rw-r--r--src/gstreamer/gstlibcameraprovider.cpp57
-rw-r--r--src/gstreamer/gstlibcameraprovider.h8
-rw-r--r--src/gstreamer/gstlibcamerasrc.cpp758
-rw-r--r--src/gstreamer/gstlibcamerasrc.h7
-rw-r--r--src/gstreamer/meson.build76
16 files changed, 1630 insertions, 439 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/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index a3cb0746..a466b305 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -3,61 +3,311 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamera-utils.c - GStreamer libcamera Utility Function
+ * GStreamer libcamera Utility Function
*/
#include "gstlibcamera-utils.h"
-#include <linux/drm_fourcc.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/formats.h>
using namespace libcamera;
static struct {
GstVideoFormat gst_format;
- guint drm_fourcc;
+ PixelFormat format;
} format_map[] = {
- { GST_VIDEO_FORMAT_ENCODED, DRM_FORMAT_MJPEG },
- { GST_VIDEO_FORMAT_RGB, DRM_FORMAT_BGR888 },
- { GST_VIDEO_FORMAT_BGR, DRM_FORMAT_RGB888 },
- { GST_VIDEO_FORMAT_ARGB, DRM_FORMAT_BGRA8888 },
- { GST_VIDEO_FORMAT_NV12, DRM_FORMAT_NV12 },
- { GST_VIDEO_FORMAT_NV21, DRM_FORMAT_NV21 },
- { GST_VIDEO_FORMAT_NV16, DRM_FORMAT_NV16 },
- { GST_VIDEO_FORMAT_NV61, DRM_FORMAT_NV61 },
- { GST_VIDEO_FORMAT_NV24, DRM_FORMAT_NV24 },
- { GST_VIDEO_FORMAT_UYVY, DRM_FORMAT_UYVY },
- { GST_VIDEO_FORMAT_VYUY, DRM_FORMAT_VYUY },
- { GST_VIDEO_FORMAT_YUY2, DRM_FORMAT_YUYV },
- { GST_VIDEO_FORMAT_YVYU, DRM_FORMAT_YVYU },
+ /* Compressed */
+ { GST_VIDEO_FORMAT_ENCODED, formats::MJPEG },
+
+ /* Bayer formats, gstreamer only supports 8-bit */
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB16 },
+
+ /* Monochrome */
+ { GST_VIDEO_FORMAT_GRAY8, formats::R8 },
+ { GST_VIDEO_FORMAT_GRAY16_LE, formats::R16 },
+
+ /* RGB16 */
+ { GST_VIDEO_FORMAT_RGB16, formats::RGB565 },
+
+ /* RGB24 */
+ { GST_VIDEO_FORMAT_RGB, formats::BGR888 },
+ { GST_VIDEO_FORMAT_BGR, formats::RGB888 },
+
+ /* RGB32 */
+ { GST_VIDEO_FORMAT_BGRx, formats::XRGB8888 },
+ { GST_VIDEO_FORMAT_RGBx, formats::XBGR8888 },
+ { GST_VIDEO_FORMAT_xBGR, formats::RGBX8888 },
+ { GST_VIDEO_FORMAT_xRGB, formats::BGRX8888 },
+ { GST_VIDEO_FORMAT_BGRA, formats::ARGB8888 },
+ { GST_VIDEO_FORMAT_RGBA, formats::ABGR8888 },
+ { GST_VIDEO_FORMAT_ABGR, formats::RGBA8888 },
+ { GST_VIDEO_FORMAT_ARGB, formats::BGRA8888 },
+
+ /* YUV Semiplanar */
+ { GST_VIDEO_FORMAT_NV12, formats::NV12 },
+ { GST_VIDEO_FORMAT_NV21, formats::NV21 },
+ { GST_VIDEO_FORMAT_NV16, formats::NV16 },
+ { GST_VIDEO_FORMAT_NV61, formats::NV61 },
+ { GST_VIDEO_FORMAT_NV24, formats::NV24 },
+
+ /* YUV Planar */
+ { GST_VIDEO_FORMAT_I420, formats::YUV420 },
+ { GST_VIDEO_FORMAT_YV12, formats::YVU420 },
+ { GST_VIDEO_FORMAT_Y42B, formats::YUV422 },
+
+ /* YUV Packed */
+ { GST_VIDEO_FORMAT_UYVY, formats::UYVY },
+ { GST_VIDEO_FORMAT_VYUY, formats::VYUY },
+ { GST_VIDEO_FORMAT_YUY2, formats::YUYV },
+ { GST_VIDEO_FORMAT_YVYU, formats::YVYU },
+
/* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
};
+static GstVideoColorimetry
+colorimetry_from_colorspace(const ColorSpace &colorSpace, GstVideoTransferFunction transfer)
+{
+ GstVideoColorimetry colorimetry;
+
+ switch (colorSpace.primaries) {
+ case ColorSpace::Primaries::Raw:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
+ break;
+ case ColorSpace::Primaries::Smpte170m:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
+ break;
+ case ColorSpace::Primaries::Rec709:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
+ break;
+ case ColorSpace::Primaries::Rec2020:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
+ break;
+ }
+
+ switch (colorSpace.transferFunction) {
+ case ColorSpace::TransferFunction::Linear:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
+ break;
+ case ColorSpace::TransferFunction::Srgb:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
+ break;
+ case ColorSpace::TransferFunction::Rec709:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
+ if (transfer != GST_VIDEO_TRANSFER_UNKNOWN)
+ colorimetry.transfer = transfer;
+ break;
+ }
+
+ switch (colorSpace.ycbcrEncoding) {
+ case ColorSpace::YcbcrEncoding::None:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec601:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec709:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec2020:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
+ break;
+ }
+
+ switch (colorSpace.range) {
+ case ColorSpace::Range::Full:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
+ break;
+ case ColorSpace::Range::Limited:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
+ break;
+ }
+
+ return colorimetry;
+}
+
+static std::optional<ColorSpace>
+colorspace_from_colorimetry(const GstVideoColorimetry &colorimetry,
+ GstVideoTransferFunction *transfer)
+{
+ std::optional<ColorSpace> colorspace = ColorSpace::Raw;
+
+ switch (colorimetry.primaries) {
+ case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
+ /* Unknown primaries map to raw colorspace in gstreamer */
+ return ColorSpace::Raw;
+ case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
+ colorspace->primaries = ColorSpace::Primaries::Smpte170m;
+ break;
+ case GST_VIDEO_COLOR_PRIMARIES_BT709:
+ colorspace->primaries = ColorSpace::Primaries::Rec709;
+ break;
+ case GST_VIDEO_COLOR_PRIMARIES_BT2020:
+ colorspace->primaries = ColorSpace::Primaries::Rec2020;
+ break;
+ default:
+ GST_WARNING("Colorimetry primaries %d not mapped in gstlibcamera",
+ colorimetry.primaries);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.transfer) {
+ /* Transfer function mappings inspired from v4l2src plugin */
+ case GST_VIDEO_TRANSFER_GAMMA18:
+ case GST_VIDEO_TRANSFER_GAMMA20:
+ case GST_VIDEO_TRANSFER_GAMMA22:
+ case GST_VIDEO_TRANSFER_GAMMA28:
+ GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
+ [[fallthrough]];
+ case GST_VIDEO_TRANSFER_GAMMA10:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
+ break;
+ case GST_VIDEO_TRANSFER_SRGB:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
+ break;
+#if GST_CHECK_VERSION(1, 18, 0)
+ case GST_VIDEO_TRANSFER_BT601:
+ case GST_VIDEO_TRANSFER_BT2020_10:
+#endif
+ case GST_VIDEO_TRANSFER_BT2020_12:
+ case GST_VIDEO_TRANSFER_BT709:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
+ *transfer = colorimetry.transfer;
+ break;
+ default:
+ GST_WARNING("Colorimetry transfer function %d not mapped in gstlibcamera",
+ colorimetry.transfer);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.matrix) {
+ case GST_VIDEO_COLOR_MATRIX_RGB:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
+ break;
+ /* FCC is about the same as BT601 with less digit */
+ case GST_VIDEO_COLOR_MATRIX_FCC:
+ case GST_VIDEO_COLOR_MATRIX_BT601:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
+ break;
+ case GST_VIDEO_COLOR_MATRIX_BT709:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
+ break;
+ case GST_VIDEO_COLOR_MATRIX_BT2020:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
+ break;
+ default:
+ GST_WARNING("Colorimetry matrix %d not mapped in gstlibcamera",
+ colorimetry.matrix);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.range) {
+ case GST_VIDEO_COLOR_RANGE_0_255:
+ colorspace->range = ColorSpace::Range::Full;
+ break;
+ case GST_VIDEO_COLOR_RANGE_16_235:
+ colorspace->range = ColorSpace::Range::Limited;
+ break;
+ default:
+ GST_WARNING("Colorimetry range %d not mapped in gstlibcamera",
+ colorimetry.range);
+ return std::nullopt;
+ }
+
+ return colorspace;
+}
+
static GstVideoFormat
-drm_to_gst_format(guint drm_fourcc)
+pixel_format_to_gst_format(const PixelFormat &format)
{
for (const auto &item : format_map) {
- if (item.drm_fourcc == drm_fourcc)
+ if (item.format == format)
return item.gst_format;
}
return GST_VIDEO_FORMAT_UNKNOWN;
}
-static guint
-gst_format_to_drm(GstVideoFormat gst_format)
+static PixelFormat
+gst_format_to_pixel_format(GstVideoFormat gst_format)
{
if (gst_format == GST_VIDEO_FORMAT_ENCODED)
- return DRM_FORMAT_INVALID;
+ return PixelFormat{};
for (const auto &item : format_map)
if (item.gst_format == gst_format)
- return item.drm_fourcc;
- return DRM_FORMAT_INVALID;
+ return item.format;
+ return PixelFormat{};
+}
+
+static const struct {
+ PixelFormat format;
+ const gchar *name;
+} bayer_map[]{
+ { formats::SBGGR8, "bggr" },
+ { formats::SGBRG8, "gbrg" },
+ { formats::SGRBG8, "grbg" },
+ { formats::SRGGB8, "rggb" },
+ { formats::SBGGR10, "bggr10le" },
+ { formats::SGBRG10, "gbrg10le" },
+ { formats::SGRBG10, "grbg10le" },
+ { formats::SRGGB10, "rggb10le" },
+ { formats::SBGGR12, "bggr12le" },
+ { formats::SGBRG12, "gbrg12le" },
+ { formats::SGRBG12, "grbg12le" },
+ { formats::SRGGB12, "rggb12le" },
+ { formats::SBGGR14, "bggr14le" },
+ { formats::SGBRG14, "gbrg14le" },
+ { formats::SGRBG14, "grbg14le" },
+ { formats::SRGGB14, "rggb14le" },
+ { formats::SBGGR16, "bggr16le" },
+ { formats::SGBRG16, "gbrg16le" },
+ { formats::SGRBG16, "grbg16le" },
+ { formats::SRGGB16, "rggb16le" },
+};
+
+static const gchar *
+bayer_format_to_string(PixelFormat format)
+{
+ for (auto &b : bayer_map) {
+ if (b.format == format)
+ return b.name;
+ }
+ return nullptr;
+}
+
+static PixelFormat
+bayer_format_from_string(const gchar *name)
+{
+ for (auto &b : bayer_map) {
+ if (strcmp(b.name, name) == 0)
+ return b.format;
+ }
+ return PixelFormat{};
}
static GstStructure *
-bare_structure_from_fourcc(guint fourcc)
+bare_structure_from_format(const PixelFormat &format)
{
- GstVideoFormat gst_format = drm_to_gst_format(fourcc);
+ GstVideoFormat gst_format = pixel_format_to_gst_format(format);
if (gst_format == GST_VIDEO_FORMAT_UNKNOWN)
return nullptr;
@@ -66,9 +316,17 @@ bare_structure_from_fourcc(guint fourcc)
return gst_structure_new("video/x-raw", "format", G_TYPE_STRING,
gst_video_format_to_string(gst_format), nullptr);
- switch (fourcc) {
- case DRM_FORMAT_MJPEG:
+ switch (format) {
+ case formats::MJPEG:
return gst_structure_new_empty("image/jpeg");
+
+ case formats::SBGGR8:
+ case formats::SGBRG8:
+ case formats::SGRBG8:
+ case formats::SRGGB8:
+ return gst_structure_new("video/x-bayer", "format", G_TYPE_STRING,
+ bayer_format_to_string(format), nullptr);
+
default:
return nullptr;
}
@@ -80,7 +338,7 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
GstCaps *caps = gst_caps_new_empty();
for (PixelFormat pixelformat : formats.pixelformats()) {
- g_autoptr(GstStructure) bare_s = bare_structure_from_fourcc(pixelformat);
+ g_autoptr(GstStructure) bare_s = bare_structure_from_format(pixelformat);
if (!bare_s) {
GST_WARNING("Unsupported DRM format %" GST_FOURCC_FORMAT,
@@ -103,13 +361,21 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
GValue val = G_VALUE_INIT;
g_value_init(&val, GST_TYPE_INT_RANGE);
- gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
- gst_structure_set_value(s, "width", &val);
- gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
- gst_structure_set_value(s, "height", &val);
+ if (range.min.width == range.max.width) {
+ gst_structure_set(s, "width", G_TYPE_INT, range.min.width, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
+ gst_structure_set_value(s, "width", &val);
+ }
+ if (range.min.height == range.max.height) {
+ gst_structure_set(s, "height", G_TYPE_INT, range.min.height, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
+ gst_structure_set_value(s, "height", &val);
+ }
g_value_unset(&val);
- gst_caps_append_structure(caps, s);
+ caps = gst_caps_merge_structure(caps, s);
}
}
@@ -117,33 +383,88 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
}
GstCaps *
-gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg)
+gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg,
+ GstVideoTransferFunction transfer)
{
GstCaps *caps = gst_caps_new_empty();
- GstStructure *s = bare_structure_from_fourcc(stream_cfg.pixelFormat);
+ GstStructure *s = bare_structure_from_format(stream_cfg.pixelFormat);
gst_structure_set(s,
"width", G_TYPE_INT, stream_cfg.size.width,
"height", G_TYPE_INT, stream_cfg.size.height,
nullptr);
+
+ if (stream_cfg.colorSpace) {
+ GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value(), transfer);
+ g_autofree gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
+
+ if (colorimetry_str)
+ gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
+ else
+ g_error("Got invalid colorimetry from ColorSpace: %s",
+ ColorSpace::toString(stream_cfg.colorSpace).c_str());
+ }
+
gst_caps_append_structure(caps, s);
return caps;
}
-void
-gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
- GstCaps *caps)
+void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
+ GstCaps *caps, GstVideoTransferFunction *transfer)
{
- GstVideoFormat gst_format = drm_to_gst_format(stream_cfg.pixelFormat);
+ GstVideoFormat gst_format = pixel_format_to_gst_format(stream_cfg.pixelFormat);
+ guint i;
+ gint best_fixed = -1, best_in_range = -1;
+ GstStructure *s;
+
+ /*
+ * These are delta weight computed from:
+ * ABS(width - stream_cfg.size.width) * ABS(height - stream_cfg.size.height)
+ */
+ guint best_fixed_delta = G_MAXUINT;
+ guint best_in_range_delta = G_MAXUINT;
/* First fixate the caps using default configuration value. */
g_assert(gst_caps_is_writable(caps));
- caps = gst_caps_truncate(caps);
- GstStructure *s = gst_caps_get_structure(caps, 0);
- gst_structure_fixate_field_nearest_int(s, "width", stream_cfg.size.width);
- gst_structure_fixate_field_nearest_int(s, "height", stream_cfg.size.height);
+ /* Lookup the structure for a close match to the stream_cfg.size */
+ for (i = 0; i < gst_caps_get_size(caps); i++) {
+ s = gst_caps_get_structure(caps, i);
+ gint width, height;
+ guint delta;
+
+ if (gst_structure_has_field_typed(s, "width", G_TYPE_INT) &&
+ gst_structure_has_field_typed(s, "height", G_TYPE_INT)) {
+ gst_structure_get_int(s, "width", &width);
+ gst_structure_get_int(s, "height", &height);
+
+ delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);
+
+ if (delta < best_fixed_delta) {
+ best_fixed_delta = delta;
+ best_fixed = i;
+ }
+ } else {
+ gst_structure_fixate_field_nearest_int(s, "width", stream_cfg.size.width);
+ gst_structure_fixate_field_nearest_int(s, "height", stream_cfg.size.height);
+ gst_structure_get_int(s, "width", &width);
+ gst_structure_get_int(s, "height", &height);
+
+ delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);
+
+ if (delta < best_in_range_delta) {
+ best_in_range_delta = delta;
+ best_in_range = i;
+ }
+ }
+ }
+
+ /* Prefer reliable fixed value over ranges */
+ if (best_fixed >= 0)
+ s = gst_caps_get_structure(caps, best_fixed);
+ else
+ s = gst_caps_get_structure(caps, best_in_range);
if (gst_structure_has_name(s, "video/x-raw")) {
const gchar *format = gst_video_format_to_string(gst_format);
@@ -154,9 +475,12 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
if (gst_structure_has_name(s, "video/x-raw")) {
const gchar *format = gst_structure_get_string(s, "format");
gst_format = gst_video_format_from_string(format);
- stream_cfg.pixelFormat = PixelFormat(gst_format_to_drm(gst_format));
+ stream_cfg.pixelFormat = gst_format_to_pixel_format(gst_format);
+ } else if (gst_structure_has_name(s, "video/x-bayer")) {
+ const gchar *format = gst_structure_get_string(s, "format");
+ stream_cfg.pixelFormat = bayer_format_from_string(format);
} else if (gst_structure_has_name(s, "image/jpeg")) {
- stream_cfg.pixelFormat = PixelFormat(DRM_FORMAT_MJPEG);
+ stream_cfg.pixelFormat = formats::MJPEG;
} else {
g_critical("Unsupported media type: %s", gst_structure_get_name(s));
}
@@ -166,15 +490,131 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
gst_structure_get_int(s, "height", &height);
stream_cfg.size.width = width;
stream_cfg.size.height = height;
+
+ /* Configure colorimetry */
+ if (gst_structure_has_field(s, "colorimetry")) {
+ const gchar *colorimetry_str = gst_structure_get_string(s, "colorimetry");
+ GstVideoColorimetry colorimetry;
+
+ if (!gst_video_colorimetry_from_string(&colorimetry, colorimetry_str))
+ g_critical("Invalid colorimetry %s", colorimetry_str);
+
+ stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry, transfer);
+ }
}
-void
-gst_libcamera_resume_task(GstTask *task)
+void gst_libcamera_get_framerate_from_caps(GstCaps *caps,
+ GstStructure *element_caps)
+{
+ GstStructure *s = gst_caps_get_structure(caps, 0);
+ /*
+ * Default to 30 fps. If the "framerate" fraction is invalid below,
+ * libcamerasrc will set 30fps as the framerate.
+ */
+ gint fps_n = 30, fps_d = 1;
+
+ if (gst_structure_has_field_typed(s, "framerate", GST_TYPE_FRACTION)) {
+ if (!gst_structure_get_fraction(s, "framerate", &fps_n, &fps_d))
+ GST_WARNING("Invalid framerate in the caps");
+ }
+
+ gst_structure_set(element_caps, "framerate", GST_TYPE_FRACTION,
+ fps_n, fps_d, nullptr);
+}
+
+void gst_libcamera_clamp_and_set_frameduration(ControlList &initCtrls,
+ const ControlInfoMap &cam_ctrls,
+ GstStructure *element_caps)
+{
+ gint fps_caps_n, fps_caps_d;
+
+ if (!gst_structure_has_field_typed(element_caps, "framerate", GST_TYPE_FRACTION))
+ return;
+
+ auto iterFrameDuration = cam_ctrls.find(&controls::FrameDurationLimits);
+ if (iterFrameDuration == cam_ctrls.end()) {
+ GST_WARNING("FrameDurationLimits not found in camera controls.");
+ return;
+ }
+
+ const GValue *framerate = gst_structure_get_value(element_caps, "framerate");
+
+ fps_caps_n = gst_value_get_fraction_numerator(framerate);
+ fps_caps_d = gst_value_get_fraction_denominator(framerate);
+
+ int64_t target_duration = (fps_caps_d * 1000000.0) / fps_caps_n;
+ int64_t min_frame_duration = iterFrameDuration->second.min().get<int64_t>();
+ int64_t max_frame_duration = iterFrameDuration->second.max().get<int64_t>();
+
+ int64_t frame_duration = std::clamp(target_duration,
+ min_frame_duration,
+ max_frame_duration);
+
+ if (frame_duration != target_duration) {
+ gint framerate_clamped = 1000000 / frame_duration;
+
+ /*
+ * Update the clamped framerate which then will be exposed in
+ * downstream caps.
+ */
+ gst_structure_set(element_caps, "framerate", GST_TYPE_FRACTION,
+ framerate_clamped, 1, nullptr);
+ }
+
+ initCtrls.set(controls::FrameDurationLimits,
+ { frame_duration, frame_duration });
+}
+
+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GstStructure *element_caps)
+{
+ const GValue *framerate = gst_structure_get_value(element_caps, "framerate");
+ if (!GST_VALUE_HOLDS_FRACTION(framerate))
+ return;
+
+ GstStructure *s = gst_caps_get_structure(caps, 0);
+ gint fps_caps_n, fps_caps_d;
+
+ fps_caps_n = gst_value_get_fraction_numerator(framerate);
+ fps_caps_d = gst_value_get_fraction_denominator(framerate);
+
+ gst_structure_set(s, "framerate", GST_TYPE_FRACTION, fps_caps_n, fps_caps_d, nullptr);
+}
+
+#if !GST_CHECK_VERSION(1, 17, 1)
+gboolean
+gst_task_resume(GstTask *task)
{
/* We only want to resume the task if it's paused. */
GLibLocker lock(GST_OBJECT(task));
- if (GST_TASK_STATE(task) == GST_TASK_PAUSED) {
- GST_TASK_STATE(task) = GST_TASK_STARTED;
- GST_TASK_SIGNAL(task);
+ if (GST_TASK_STATE(task) != GST_TASK_PAUSED)
+ return FALSE;
+
+ GST_TASK_STATE(task) = GST_TASK_STARTED;
+ GST_TASK_SIGNAL(task);
+ return TRUE;
+}
+#endif
+
+G_LOCK_DEFINE_STATIC(cm_singleton_lock);
+static std::weak_ptr<CameraManager> cm_singleton_ptr;
+
+std::shared_ptr<CameraManager>
+gst_libcamera_get_camera_manager(int &ret)
+{
+ std::shared_ptr<CameraManager> cm;
+
+ G_LOCK(cm_singleton_lock);
+
+ cm = cm_singleton_ptr.lock();
+ if (!cm) {
+ cm = std::make_shared<CameraManager>();
+ cm_singleton_ptr = cm;
+ ret = cm->start();
+ } else {
+ ret = 0;
}
+
+ G_UNLOCK(cm_singleton_lock);
+
+ return cm;
}
diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h
index 2b3f26b6..4978987c 100644
--- a/src/gstreamer/gstlibcamera-utils.h
+++ b/src/gstreamer/gstlibcamera-utils.h
@@ -3,22 +3,40 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamera-utils.h - GStreamer libcamera Utility Functions
+ * GStreamer libcamera Utility Functions
*/
-#ifndef __GST_LIBCAMERA_UTILS_H__
-#define __GST_LIBCAMERA_UTILS_H__
+#pragma once
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/stream.h>
#include <gst/gst.h>
#include <gst/video/video.h>
-#include <libcamera/stream.h>
-
GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &formats);
-GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg);
+GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg,
+ GstVideoTransferFunction transfer);
void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,
- GstCaps *caps);
-void gst_libcamera_resume_task(GstTask *task);
+ GstCaps *caps, GstVideoTransferFunction *transfer);
+void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps);
+void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,
+ const libcamera::ControlInfoMap &camera_controls,
+ GstStructure *element_caps);
+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GstStructure *element_caps);
+
+#if !GST_CHECK_VERSION(1, 16, 0)
+static inline void gst_clear_event(GstEvent **event_ptr)
+{
+ g_clear_pointer(event_ptr, gst_mini_object_unref);
+}
+#endif
+
+#if !GST_CHECK_VERSION(1, 17, 1)
+gboolean gst_task_resume(GstTask *task);
+#endif
+std::shared_ptr<libcamera::CameraManager> gst_libcamera_get_camera_manager(int &ret);
/**
* \class GLibLocker
@@ -69,5 +87,3 @@ public:
private:
GRecMutex *mutex_;
};
-
-#endif /* __GST_LIBCAMERA_UTILS_H__ */
diff --git a/src/gstreamer/gstlibcamera.cpp b/src/gstreamer/gstlibcamera.cpp
index 81c7bb19..bff98979 100644
--- a/src/gstreamer/gstlibcamera.cpp
+++ b/src/gstreamer/gstlibcamera.cpp
@@ -3,7 +3,7 @@
* Copyright (C) 2019, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamera.c - GStreamer plugin
+ * GStreamer plugin
*/
#include "gstlibcameraprovider.h"
@@ -24,4 +24,4 @@ plugin_init(GstPlugin *plugin)
GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR,
libcamera, "libcamera capture plugin",
- plugin_init, VERSION, "LGPL", PACKAGE, "https://libcamera.org");
+ plugin_init, VERSION, "LGPL", PACKAGE, "https://libcamera.org")
diff --git a/src/gstreamer/gstlibcameraallocator.cpp b/src/gstreamer/gstlibcameraallocator.cpp
index 1d5959c0..d4492d99 100644
--- a/src/gstreamer/gstlibcameraallocator.cpp
+++ b/src/gstreamer/gstlibcameraallocator.cpp
@@ -3,11 +3,13 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcameraallocator.cpp - GStreamer Custom Allocator
+ * GStreamer Custom Allocator
*/
#include "gstlibcameraallocator.h"
+#include <utility>
+
#include <libcamera/camera.h>
#include <libcamera/framebuffer_allocator.h>
#include <libcamera/stream.h>
@@ -52,8 +54,10 @@ FrameWrap::FrameWrap(GstAllocator *allocator, FrameBuffer *buffer,
outstandingPlanes_(0)
{
for (const FrameBuffer::Plane &plane : buffer->planes()) {
- GstMemory *mem = gst_fd_allocator_alloc(allocator, plane.fd.fd(), plane.length,
+ GstMemory *mem = gst_fd_allocator_alloc(allocator, plane.fd.get(),
+ plane.offset + plane.length,
GST_FD_MEMORY_FLAG_DONT_CLOSE);
+ gst_memory_resize(mem, plane.offset, plane.length);
gst_mini_object_set_qdata(GST_MINI_OBJECT(mem), getQuark(), this, nullptr);
GST_MINI_OBJECT(mem)->dispose = gst_libcamera_allocator_release;
g_object_unref(mem->allocator);
@@ -70,7 +74,7 @@ FrameWrap::~FrameWrap()
}
}
-GQuark FrameWrap::getQuark(void)
+GQuark FrameWrap::getQuark()
{
static gsize frame_quark = 0;
@@ -98,25 +102,33 @@ struct _GstLibcameraAllocator {
* FrameWrap.
*/
GHashTable *pools;
+ /*
+ * The camera manager represents the library, which needs to be kept
+ * alive until all the memory has been released.
+ */
+ std::shared_ptr<CameraManager> *cm_ptr;
};
G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator,
- GST_TYPE_DMABUF_ALLOCATOR);
+ GST_TYPE_DMABUF_ALLOCATOR)
static gboolean
gst_libcamera_allocator_release(GstMiniObject *mini_object)
{
GstMemory *mem = GST_MEMORY_CAST(mini_object);
GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(mem->allocator);
- GLibLocker lock(GST_OBJECT(self));
- auto *frame = reinterpret_cast<FrameWrap *>(gst_mini_object_get_qdata(mini_object, FrameWrap::getQuark()));
- gst_memory_ref(mem);
+ {
+ GLibLocker lock(GST_OBJECT(self));
+ auto *frame = reinterpret_cast<FrameWrap *>(gst_mini_object_get_qdata(mini_object, FrameWrap::getQuark()));
- if (frame->releasePlane()) {
- auto *pool = reinterpret_cast<GQueue *>(g_hash_table_lookup(self->pools, frame->stream_));
- g_return_val_if_fail(pool, TRUE);
- g_queue_push_tail(pool, frame);
+ gst_memory_ref(mem);
+
+ if (frame->releasePlane()) {
+ auto *pool = reinterpret_cast<GQueue *>(g_hash_table_lookup(self->pools, frame->stream_));
+ g_return_val_if_fail(pool, TRUE);
+ g_queue_push_tail(pool, frame);
+ }
}
/* Keep last in case we are holding on the last allocator ref. */
@@ -168,6 +180,9 @@ gst_libcamera_allocator_finalize(GObject *object)
delete self->fb_allocator;
+ /* Keep last. */
+ delete self->cm_ptr;
+
G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object);
}
@@ -183,17 +198,23 @@ gst_libcamera_allocator_class_init(GstLibcameraAllocatorClass *klass)
}
GstLibcameraAllocator *
-gst_libcamera_allocator_new(std::shared_ptr<Camera> camera)
+gst_libcamera_allocator_new(std::shared_ptr<Camera> camera,
+ CameraConfiguration *config_)
{
- auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR,
- nullptr));
+ g_autoptr(GstLibcameraAllocator) self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR,
+ nullptr));
+ gint ret;
+
+ self->cm_ptr = new std::shared_ptr<CameraManager>(gst_libcamera_get_camera_manager(ret));
+ if (ret)
+ return nullptr;
self->fb_allocator = new FrameBufferAllocator(camera);
- for (Stream *stream : camera->streams()) {
- gint ret;
+ for (StreamConfiguration &streamCfg : *config_) {
+ Stream *stream = streamCfg.stream();
ret = self->fb_allocator->allocate(stream);
- if (ret == 0)
+ if (ret <= 0)
return nullptr;
GQueue *pool = g_queue_new();
@@ -207,7 +228,7 @@ gst_libcamera_allocator_new(std::shared_ptr<Camera> camera)
g_hash_table_insert(self->pools, stream, pool);
}
- return self;
+ return std::exchange(self, nullptr);
}
bool
diff --git a/src/gstreamer/gstlibcameraallocator.h b/src/gstreamer/gstlibcameraallocator.h
index befdcad6..1a6ba346 100644
--- a/src/gstreamer/gstlibcameraallocator.h
+++ b/src/gstreamer/gstlibcameraallocator.h
@@ -3,22 +3,23 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcameraallocator.h - GStreamer Custom Allocator
+ * GStreamer Custom Allocator
*/
-#ifndef __GST_LIBCAMERA_ALLOCATOR_H__
-#define __GST_LIBCAMERA_ALLOCATOR_H__
+#pragma once
#include <gst/gst.h>
#include <gst/allocators/allocators.h>
+#include <libcamera/camera.h>
#include <libcamera/stream.h>
#define GST_TYPE_LIBCAMERA_ALLOCATOR gst_libcamera_allocator_get_type()
G_DECLARE_FINAL_TYPE(GstLibcameraAllocator, gst_libcamera_allocator,
GST_LIBCAMERA, ALLOCATOR, GstDmaBufAllocator)
-GstLibcameraAllocator *gst_libcamera_allocator_new(std::shared_ptr<libcamera::Camera> camera);
+GstLibcameraAllocator *gst_libcamera_allocator_new(std::shared_ptr<libcamera::Camera> camera,
+ libcamera::CameraConfiguration *config_);
bool gst_libcamera_allocator_prepare_buffer(GstLibcameraAllocator *self,
libcamera::Stream *stream,
@@ -28,5 +29,3 @@ gsize gst_libcamera_allocator_get_pool_size(GstLibcameraAllocator *allocator,
libcamera::Stream *stream);
libcamera::FrameBuffer *gst_libcamera_memory_get_frame_buffer(GstMemory *mem);
-
-#endif /* __GST_LIBCAMERA_ALLOCATOR_H__ */
diff --git a/src/gstreamer/gstlibcamerapad.cpp b/src/gstreamer/gstlibcamerapad.cpp
index e184495a..7b22aebe 100644
--- a/src/gstreamer/gstlibcamerapad.cpp
+++ b/src/gstreamer/gstlibcamerapad.cpp
@@ -3,7 +3,7 @@
* Copyright (C) 2019, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerapad.cpp - GStreamer Capture Pad
+ * GStreamer Capture Pad
*/
#include "gstlibcamerapad.h"
@@ -18,7 +18,6 @@ struct _GstLibcameraPad {
GstPad parent;
StreamRole role;
GstLibcameraPool *pool;
- GQueue pending_buffers;
GstClockTime latency;
};
@@ -27,7 +26,7 @@ enum {
PROP_STREAM_ROLE
};
-G_DEFINE_TYPE(GstLibcameraPad, gst_libcamera_pad, GST_TYPE_PAD);
+G_DEFINE_TYPE(GstLibcameraPad, gst_libcamera_pad, GST_TYPE_PAD)
static void
gst_libcamera_pad_set_property(GObject *object, guint prop_id,
@@ -55,7 +54,7 @@ gst_libcamera_pad_get_property(GObject *object, guint prop_id, GValue *value,
switch (prop_id) {
case PROP_STREAM_ROLE:
- g_value_set_enum(value, self->role);
+ g_value_set_enum(value, static_cast<gint>(self->role));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -84,13 +83,23 @@ gst_libcamera_pad_init(GstLibcameraPad *self)
}
static GType
-gst_libcamera_stream_role_get_type(void)
+gst_libcamera_stream_role_get_type()
{
static GType type = 0;
static const GEnumValue values[] = {
- { StillCapture, "libcamera::StillCapture", "still-capture" },
- { VideoRecording, "libcamera::VideoRecording", "video-recording" },
- { Viewfinder, "libcamera::Viewfinder", "view-finder" },
+ {
+ static_cast<gint>(StreamRole::StillCapture),
+ "libcamera::StillCapture",
+ "still-capture",
+ }, {
+ static_cast<gint>(StreamRole::VideoRecording),
+ "libcamera::VideoRecording",
+ "video-recording",
+ }, {
+ static_cast<gint>(StreamRole::Viewfinder),
+ "libcamera::Viewfinder",
+ "view-finder",
+ },
{ 0, NULL, NULL }
};
@@ -111,7 +120,7 @@ gst_libcamera_pad_class_init(GstLibcameraPadClass *klass)
auto *spec = g_param_spec_enum("stream-role", "Stream Role",
"The selected stream role",
gst_libcamera_stream_role_get_type(),
- VideoRecording,
+ static_cast<gint>(StreamRole::VideoRecording),
(GParamFlags)(GST_PARAM_MUTABLE_READY
| G_PARAM_CONSTRUCT
| G_PARAM_READWRITE
@@ -156,40 +165,6 @@ gst_libcamera_pad_get_stream(GstPad *pad)
}
void
-gst_libcamera_pad_queue_buffer(GstPad *pad, GstBuffer *buffer)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GLibLocker lock(GST_OBJECT(self));
-
- g_queue_push_head(&self->pending_buffers, buffer);
-}
-
-GstFlowReturn
-gst_libcamera_pad_push_pending(GstPad *pad)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GstBuffer *buffer;
-
- {
- GLibLocker lock(GST_OBJECT(self));
- buffer = GST_BUFFER(g_queue_pop_tail(&self->pending_buffers));
- }
-
- if (!buffer)
- return GST_FLOW_OK;
-
- return gst_pad_push(pad, buffer);
-}
-
-bool
-gst_libcamera_pad_has_pending(GstPad *pad)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GLibLocker lock(GST_OBJECT(self));
- return self->pending_buffers.length > 0;
-}
-
-void
gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency)
{
auto *self = GST_LIBCAMERA_PAD(pad);
diff --git a/src/gstreamer/gstlibcamerapad.h b/src/gstreamer/gstlibcamerapad.h
index 779f2d13..630c168a 100644
--- a/src/gstreamer/gstlibcamerapad.h
+++ b/src/gstreamer/gstlibcamerapad.h
@@ -3,11 +3,10 @@
* Copyright (C) 2019, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerapad.h - GStreamer Capture Element
+ * GStreamer Capture Element
*/
-#ifndef __GST_LIBCAMERA_PAD_H__
-#define __GST_LIBCAMERA_PAD_H__
+#pragma once
#include "gstlibcamerapool.h"
@@ -26,12 +25,4 @@ void gst_libcamera_pad_set_pool(GstPad *pad, GstLibcameraPool *pool);
libcamera::Stream *gst_libcamera_pad_get_stream(GstPad *pad);
-void gst_libcamera_pad_queue_buffer(GstPad *pad, GstBuffer *buffer);
-
-GstFlowReturn gst_libcamera_pad_push_pending(GstPad *pad);
-
-bool gst_libcamera_pad_has_pending(GstPad *pad);
-
void gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency);
-
-#endif /* __GST_LIBCAMERA_PAD_H__ */
diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp
index 8f536169..9cd7eccb 100644
--- a/src/gstreamer/gstlibcamerapool.cpp
+++ b/src/gstreamer/gstlibcamerapool.cpp
@@ -3,11 +3,13 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerapool.cpp - GStreamer Buffer Pool
+ * GStreamer Buffer Pool
*/
#include "gstlibcamerapool.h"
+#include <deque>
+
#include <libcamera/stream.h>
#include "gstlibcamera-utils.h"
@@ -24,24 +26,43 @@ static guint signals[N_SIGNALS];
struct _GstLibcameraPool {
GstBufferPool parent;
- GstAtomicQueue *queue;
+ std::deque<GstBuffer *> *queue;
GstLibcameraAllocator *allocator;
Stream *stream;
};
-G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL);
+G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL)
+
+static GstBuffer *
+gst_libcamera_pool_pop_buffer(GstLibcameraPool *self)
+{
+ GLibLocker lock(GST_OBJECT(self));
+ GstBuffer *buf;
+
+ if (self->queue->empty())
+ return nullptr;
+
+ buf = self->queue->front();
+ self->queue->pop_front();
+
+ return buf;
+}
static GstFlowReturn
gst_libcamera_pool_acquire_buffer(GstBufferPool *pool, GstBuffer **buffer,
- GstBufferPoolAcquireParams *params)
+ [[maybe_unused]] GstBufferPoolAcquireParams *params)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- GstBuffer *buf = GST_BUFFER(gst_atomic_queue_pop(self->queue));
+ GstBuffer *buf = gst_libcamera_pool_pop_buffer(self);
+
if (!buf)
return GST_FLOW_ERROR;
- if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf))
+ if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf)) {
+ GLibLocker lock(GST_OBJECT(self));
+ self->queue->push_back(buf);
return GST_FLOW_ERROR;
+ }
*buffer = buf;
return GST_FLOW_OK;
@@ -62,9 +83,13 @@ static void
gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- bool do_notify = gst_atomic_queue_length(self->queue) == 0;
+ bool do_notify;
- gst_atomic_queue_push(self->queue, buffer);
+ {
+ GLibLocker lock(GST_OBJECT(self));
+ do_notify = self->queue->empty();
+ self->queue->push_back(buffer);
+ }
if (do_notify)
g_signal_emit(self, signals[SIGNAL_BUFFER_NOTIFY], 0);
@@ -73,7 +98,7 @@ gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
static void
gst_libcamera_pool_init(GstLibcameraPool *self)
{
- self->queue = gst_atomic_queue_new(4);
+ self->queue = new std::deque<GstBuffer *>();
}
static void
@@ -82,10 +107,10 @@ gst_libcamera_pool_finalize(GObject *object)
GstLibcameraPool *self = GST_LIBCAMERA_POOL(object);
GstBuffer *buf;
- while ((buf = GST_BUFFER(gst_atomic_queue_pop(self->queue))))
+ while ((buf = gst_libcamera_pool_pop_buffer(self)))
gst_buffer_unref(buf);
- gst_atomic_queue_unref(self->queue);
+ delete self->queue;
g_object_unref(self->allocator);
G_OBJECT_CLASS(gst_libcamera_pool_parent_class)->finalize(object);
@@ -120,7 +145,7 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)
gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream);
for (gsize i = 0; i < pool_size; i++) {
GstBuffer *buffer = gst_buffer_new();
- gst_atomic_queue_push(pool->queue, buffer);
+ pool->queue->push_back(buffer);
}
return pool;
@@ -132,13 +157,6 @@ gst_libcamera_pool_get_stream(GstLibcameraPool *self)
return self->stream;
}
-Stream *
-gst_libcamera_buffer_get_stream(GstBuffer *buffer)
-{
- auto *self = (GstLibcameraPool *)buffer->pool;
- return self->stream;
-}
-
FrameBuffer *
gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer)
{
diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h
index a3f1b685..2a7a9c77 100644
--- a/src/gstreamer/gstlibcamerapool.h
+++ b/src/gstreamer/gstlibcamerapool.h
@@ -3,14 +3,13 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerapool.h - GStreamer Buffer Pool
+ * GStreamer Buffer Pool
*
* This is a partial implementation of GstBufferPool intended for internal use
* only. This pool cannot be configured or activated.
*/
-#ifndef __GST_LIBCAMERA_POOL_H__
-#define __GST_LIBCAMERA_POOL_H__
+#pragma once
#include "gstlibcameraallocator.h"
@@ -26,9 +25,4 @@ GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator,
libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool *self);
-libcamera::Stream *gst_libcamera_buffer_get_stream(GstBuffer *buffer);
-
libcamera::FrameBuffer *gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer);
-
-
-#endif /* __GST_LIBCAMERA_POOL_H__ */
diff --git a/src/gstreamer/gstlibcameraprovider.cpp b/src/gstreamer/gstlibcameraprovider.cpp
index 914ed4fb..5da96ea3 100644
--- a/src/gstreamer/gstlibcameraprovider.cpp
+++ b/src/gstreamer/gstlibcameraprovider.cpp
@@ -3,9 +3,11 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcameraprovider.c - GStreamer Device Provider
+ * GStreamer Device Provider
*/
+#include <array>
+
#include "gstlibcameraprovider.h"
#include <libcamera/camera.h>
@@ -35,14 +37,14 @@ enum {
#define GST_TYPE_LIBCAMERA_DEVICE gst_libcamera_device_get_type()
G_DECLARE_FINAL_TYPE(GstLibcameraDevice, gst_libcamera_device,
- GST_LIBCAMERA, DEVICE, GstDevice);
+ GST_LIBCAMERA, DEVICE, GstDevice)
struct _GstLibcameraDevice {
GstDevice parent;
gchar *name;
};
-G_DEFINE_TYPE(GstLibcameraDevice, gst_libcamera_device, GST_TYPE_DEVICE);
+G_DEFINE_TYPE(GstLibcameraDevice, gst_libcamera_device, GST_TYPE_DEVICE)
static GstElement *
gst_libcamera_device_create_element(GstDevice *device, const gchar *name)
@@ -89,7 +91,7 @@ gst_libcamera_device_set_property(GObject *object, guint prop_id,
}
static void
-gst_libcamera_device_init(GstLibcameraDevice *self)
+gst_libcamera_device_init([[maybe_unused]] GstLibcameraDevice *self)
{
}
@@ -101,7 +103,7 @@ gst_libcamera_device_finalize(GObject *object)
g_free(self->name);
- G_OBJECT_GET_CLASS(klass)->finalize(object);
+ G_OBJECT_CLASS(klass)->finalize(object);
}
static void
@@ -126,12 +128,15 @@ gst_libcamera_device_class_init(GstLibcameraDeviceClass *klass)
static GstDevice *
gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)
{
+ static const std::array roles{ StreamRole::VideoRecording };
g_autoptr(GstCaps) caps = gst_caps_new_empty();
- const gchar *name = camera->name().c_str();
- StreamRoles roles;
+ const gchar *name = camera->id().c_str();
- roles.push_back(StreamRole::VideoRecording);
std::unique_ptr<CameraConfiguration> config = camera->generateConfiguration(roles);
+ if (!config || config->size() != roles.size()) {
+ GST_ERROR("Failed to generate a default configuration for %s", name);
+ return nullptr;
+ }
for (const StreamConfiguration &stream_cfg : *config) {
GstCaps *sub_caps = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
@@ -158,19 +163,18 @@ gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)
struct _GstLibcameraProvider {
GstDeviceProvider parent;
- CameraManager *cm;
};
G_DEFINE_TYPE_WITH_CODE(GstLibcameraProvider, gst_libcamera_provider,
GST_TYPE_DEVICE_PROVIDER,
GST_DEBUG_CATEGORY_INIT(provider_debug, "libcamera-provider", 0,
- "libcamera Device Provider"));
+ "libcamera Device Provider"))
static GList *
gst_libcamera_provider_probe(GstDeviceProvider *provider)
{
GstLibcameraProvider *self = GST_LIBCAMERA_PROVIDER(provider);
- CameraManager *cm = self->cm;
+ std::shared_ptr<CameraManager> cm;
GList *devices = nullptr;
gint ret;
@@ -181,7 +185,7 @@ gst_libcamera_provider_probe(GstDeviceProvider *provider)
* gains monitoring support. Meanwhile we need to cycle start()/stop()
* to ensure every probe() calls return the latest list.
*/
- ret = cm->start();
+ cm = gst_libcamera_get_camera_manager(ret);
if (ret) {
GST_ERROR_OBJECT(self, "Failed to retrieve device list: %s",
g_strerror(-ret));
@@ -189,13 +193,19 @@ gst_libcamera_provider_probe(GstDeviceProvider *provider)
}
for (const std::shared_ptr<Camera> &camera : cm->cameras()) {
- GST_INFO_OBJECT(self, "Found camera '%s'", camera->name().c_str());
+ GST_INFO_OBJECT(self, "Found camera '%s'", camera->id().c_str());
+
+ GstDevice *dev = gst_libcamera_device_new(camera);
+ if (!dev) {
+ GST_ERROR_OBJECT(self, "Failed to add camera '%s'",
+ camera->id().c_str());
+ return nullptr;
+ }
+
devices = g_list_append(devices,
- g_object_ref_sink(gst_libcamera_device_new(camera)));
+ g_object_ref_sink(dev));
}
- cm->stop();
-
return devices;
}
@@ -204,31 +214,16 @@ gst_libcamera_provider_init(GstLibcameraProvider *self)
{
GstDeviceProvider *provider = GST_DEVICE_PROVIDER(self);
- self->cm = new CameraManager();
-
/* Avoid devices being duplicated. */
gst_device_provider_hide_provider(provider, "v4l2deviceprovider");
}
static void
-gst_libcamera_provider_finalize(GObject *object)
-{
- GstLibcameraProvider *self = GST_LIBCAMERA_PROVIDER(object);
- gpointer klass = gst_libcamera_provider_parent_class;
-
- delete self->cm;
-
- return G_OBJECT_GET_CLASS(klass)->finalize(object);
-}
-
-static void
gst_libcamera_provider_class_init(GstLibcameraProviderClass *klass)
{
GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS(klass);
- GObjectClass *object_class = G_OBJECT_CLASS(klass);
provider_class->probe = gst_libcamera_provider_probe;
- object_class->finalize = gst_libcamera_provider_finalize;
gst_device_provider_class_set_metadata(provider_class,
"libcamera Device Provider",
diff --git a/src/gstreamer/gstlibcameraprovider.h b/src/gstreamer/gstlibcameraprovider.h
index bdd19db8..19708b9d 100644
--- a/src/gstreamer/gstlibcameraprovider.h
+++ b/src/gstreamer/gstlibcameraprovider.h
@@ -3,11 +3,10 @@
* Copyright (C) 2020, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcameraprovider.h - GStreamer Device Provider
+ * GStreamer Device Provider
*/
-#ifndef __GST_LIBCAMERA_PROVIDER_H__
-#define __GST_LIBCAMERA_PROVIDER_H__
+#pragma once
#include <gst/gst.h>
@@ -18,6 +17,3 @@ G_DECLARE_FINAL_TYPE(GstLibcameraProvider, gst_libcamera_provider,
GST_LIBCAMERA, PROVIDER, GstDeviceProvider)
G_END_DECLS
-
-#endif /* __GST_LIBCAMERA_PROVIDER_H__ */
-
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 9755922a..5e9e843d 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -3,16 +3,14 @@
* Copyright (C) 2019, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerasrc.cpp - GStreamer Capture Element
+ * GStreamer Capture Element
*/
/**
* \todo The following is a list of items that needs implementation in the GStreamer plugin
* - Implement GstElement::send_event
- * + Allowing application to send EOS
* + Allowing application to use FLUSH/FLUSH_STOP
* + Prevent the main thread from accessing streaming thread
- * - Implement renegotiation (even if slow)
* - Implement GstElement::request-new-pad (multi stream)
* + Evaluate if a single streaming thread is fine
* - Add application driven request (snapshot)
@@ -25,26 +23,25 @@
* - Add timestamp support
* - Use unique names to select the camera devices
* - Add GstVideoMeta support (strides and offsets)
- *
- * \todo libcamera UVC drivers picks the lowest possible resolution first, this
- * should be fixed so that we get a decent resolution and framerate for the
- * role by default.
*/
#include "gstlibcamerasrc.h"
+#include <atomic>
#include <queue>
#include <vector>
-#include <gst/base/base.h>
-
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
+#include <libcamera/control_ids.h>
+#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;
@@ -52,19 +49,21 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
#define GST_CAT_DEFAULT source_debug
struct RequestWrap {
- RequestWrap(Request *request);
+ RequestWrap(std::unique_ptr<Request> request);
~RequestWrap();
- void attachBuffer(GstBuffer *buffer);
+ void attachBuffer(Stream *stream, GstBuffer *buffer);
GstBuffer *detachBuffer(Stream *stream);
- /* For ptr comparison only. */
- Request *request_;
+ std::unique_ptr<Request> request_;
std::map<Stream *, GstBuffer *> buffers_;
+
+ GstClockTime latency_;
+ GstClockTime pts_;
};
-RequestWrap::RequestWrap(Request *request)
- : request_(request)
+RequestWrap::RequestWrap(std::unique_ptr<Request> request)
+ : request_(std::move(request)), latency_(0), pts_(GST_CLOCK_TIME_NONE)
{
}
@@ -76,10 +75,9 @@ RequestWrap::~RequestWrap()
}
}
-void RequestWrap::attachBuffer(GstBuffer *buffer)
+void RequestWrap::attachBuffer(Stream *stream, GstBuffer *buffer)
{
FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);
- Stream *stream = gst_libcamera_buffer_get_stream(buffer);
request_->addBuffer(stream, fb);
@@ -109,13 +107,34 @@ GstBuffer *RequestWrap::detachBuffer(Stream *stream)
struct GstLibcameraSrcState {
GstLibcameraSrc *src_;
- std::unique_ptr<CameraManager> cm_;
+ std::shared_ptr<CameraManager> cm_;
std::shared_ptr<Camera> cam_;
std::unique_ptr<CameraConfiguration> config_;
- std::vector<GstPad *> srcpads_;
- std::queue<std::unique_ptr<RequestWrap>> requests_;
+ std::vector<GstPad *> srcpads_; /* Protected by stream_lock */
+
+ /*
+ * Contention on this lock_ must be minimized, as it has to be taken in
+ * the realtime-sensitive requestCompleted() handler to protect
+ * queuedRequests_ and completedRequests_.
+ *
+ * stream_lock must be taken before lock_ in contexts where both locks
+ * need to be taken. In particular, this means that the lock_ must not
+ * be held while calling into other graph elements (e.g. when calling
+ * gst_pad_query()).
+ */
+ GMutex lock_;
+ std::queue<std::unique_ptr<RequestWrap>> queuedRequests_;
+ std::queue<std::unique_ptr<RequestWrap>> completedRequests_;
+
+ ControlList initControls_;
+ guint group_id_;
+ GstCameraControls controls_;
+
+ int queueRequest();
void requestCompleted(Request *request);
+ int processRequest();
+ void clearRequests();
};
struct _GstLibcameraSrc {
@@ -126,6 +145,8 @@ struct _GstLibcameraSrc {
gchar *camera_name;
+ std::atomic<GstEvent *> pending_eos;
+
GstLibcameraSrcState *state;
GstLibcameraAllocator *allocator;
GstFlowCombiner *flow_combiner;
@@ -133,14 +154,20 @@ struct _GstLibcameraSrc {
enum {
PROP_0,
- PROP_CAMERA_NAME
+ PROP_CAMERA_NAME,
+ PROP_LAST
};
+static void gst_libcamera_src_child_proxy_init(gpointer g_iface,
+ gpointer iface_data);
+
G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT,
+ G_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY,
+ gst_libcamera_src_child_proxy_init)
GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0,
- "libcamera Source"));
+ "libcamera Source"))
-#define TEMPLATE_CAPS GST_STATIC_CAPS("video/x-raw; image/jpeg")
+#define TEMPLATE_CAPS GST_STATIC_CAPS("video/x-raw; image/jpeg; video/x-bayer")
/* For the simple case, we have a src pad that is always present. */
GstStaticPadTemplate src_template = {
@@ -149,43 +176,131 @@ GstStaticPadTemplate src_template = {
/* More pads can be requested in state < PAUSED */
GstStaticPadTemplate request_src_template = {
- "src_%s", GST_PAD_SRC, GST_PAD_REQUEST, TEMPLATE_CAPS
+ "src_%u", GST_PAD_SRC, GST_PAD_REQUEST, TEMPLATE_CAPS
};
+/* Must be called with stream_lock held. */
+int GstLibcameraSrcState::queueRequest()
+{
+ std::unique_ptr<Request> request = cam_->createRequest();
+ if (!request)
+ return -ENOMEM;
+
+ /* Apply controls */
+ controls_.applyControls(request);
+
+ std::unique_ptr<RequestWrap> wrap =
+ std::make_unique<RequestWrap>(std::move(request));
+
+ for (GstPad *srcpad : srcpads_) {
+ Stream *stream = gst_libcamera_pad_get_stream(srcpad);
+ GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
+ GstBuffer *buffer;
+ GstFlowReturn ret;
+
+ ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
+ &buffer, nullptr);
+ if (ret != GST_FLOW_OK) {
+ /*
+ * RequestWrap has ownership of the request, and we
+ * won't be queueing this one due to lack of buffers.
+ */
+ return -ENOBUFS;
+ }
+
+ wrap->attachBuffer(stream, buffer);
+ }
+
+ GST_TRACE_OBJECT(src_, "Requesting buffers");
+ cam_->queueRequest(wrap->request_.get());
+
+ {
+ GLibLocker locker(&lock_);
+ queuedRequests_.push(std::move(wrap));
+ }
+
+ /* The RequestWrap will be deleted in the completion handler. */
+ return 0;
+}
+
void
GstLibcameraSrcState::requestCompleted(Request *request)
{
- GLibLocker lock(GST_OBJECT(src_));
-
GST_DEBUG_OBJECT(src_, "buffers are ready");
- std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
- requests_.pop();
+ std::unique_ptr<RequestWrap> wrap;
- g_return_if_fail(wrap->request_ == request);
+ {
+ GLibLocker locker(&lock_);
+
+ controls_.readMetadata(request);
+
+ wrap = std::move(queuedRequests_.front());
+ queuedRequests_.pop();
+ }
+
+ g_return_if_fail(wrap->request_.get() == request);
if ((request->status() == Request::RequestCancelled)) {
GST_DEBUG_OBJECT(src_, "Request was cancelled");
return;
}
- GstBuffer *buffer;
+ if (GST_ELEMENT_CLOCK(src_)) {
+ int64_t timestamp = request->metadata().get(controls::SensorTimestamp).value_or(0);
+
+ GstClockTime gst_base_time = GST_ELEMENT(src_)->base_time;
+ GstClockTime gst_now = gst_clock_get_time(GST_ELEMENT_CLOCK(src_));
+ /* \todo Need to expose which reference clock the timestamp relates to. */
+ GstClockTime sys_now = g_get_monotonic_time() * 1000;
+
+ /* Deduced from: sys_now - sys_base_time == gst_now - gst_base_time */
+ GstClockTime sys_base_time = sys_now - (gst_now - gst_base_time);
+ wrap->pts_ = timestamp - sys_base_time;
+ wrap->latency_ = sys_now - timestamp;
+ }
+
+ {
+ GLibLocker locker(&lock_);
+ completedRequests_.push(std::move(wrap));
+ }
+
+ gst_task_resume(src_->task);
+}
+
+/* Must be called with stream_lock held. */
+int GstLibcameraSrcState::processRequest()
+{
+ std::unique_ptr<RequestWrap> wrap;
+ int err = 0;
+
+ {
+ GLibLocker locker(&lock_);
+
+ if (!completedRequests_.empty()) {
+ wrap = std::move(completedRequests_.front());
+ completedRequests_.pop();
+ }
+
+ if (completedRequests_.empty())
+ err = -ENOBUFS;
+ }
+
+ if (!wrap)
+ return -ENOBUFS;
+
+ GstFlowReturn ret = GST_FLOW_OK;
+ gst_flow_combiner_reset(src_->flow_combiner);
+
for (GstPad *srcpad : srcpads_) {
Stream *stream = gst_libcamera_pad_get_stream(srcpad);
- buffer = wrap->detachBuffer(stream);
+ GstBuffer *buffer = wrap->detachBuffer(stream);
FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);
- if (GST_ELEMENT_CLOCK(src_)) {
- GstClockTime gst_base_time = GST_ELEMENT(src_)->base_time;
- GstClockTime gst_now = gst_clock_get_time(GST_ELEMENT_CLOCK(src_));
- /* \todo Need to expose which reference clock the timestamp relates to. */
- GstClockTime sys_now = g_get_monotonic_time() * 1000;
-
- /* Deduced from: sys_now - sys_base_time == gst_now - gst_base_time */
- GstClockTime sys_base_time = sys_now - (gst_now - gst_base_time);
- GST_BUFFER_PTS(buffer) = fb->metadata().timestamp - sys_base_time;
- gst_libcamera_pad_set_latency(srcpad, sys_now - fb->metadata().timestamp);
+ if (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) {
+ GST_BUFFER_PTS(buffer) = wrap->pts_;
+ gst_libcamera_pad_set_latency(srcpad, wrap->latency_);
} else {
GST_BUFFER_PTS(buffer) = 0;
}
@@ -193,22 +308,72 @@ GstLibcameraSrcState::requestCompleted(Request *request)
GST_BUFFER_OFFSET(buffer) = fb->metadata().sequence;
GST_BUFFER_OFFSET_END(buffer) = fb->metadata().sequence;
- gst_libcamera_pad_queue_buffer(srcpad, buffer);
+ ret = gst_pad_push(srcpad, buffer);
+ ret = gst_flow_combiner_update_pad_flow(src_->flow_combiner,
+ srcpad, ret);
+ }
+
+ switch (ret) {
+ case GST_FLOW_OK:
+ break;
+
+ case GST_FLOW_NOT_NEGOTIATED: {
+ bool reconfigure = false;
+ for (GstPad *srcpad : srcpads_) {
+ if (gst_pad_needs_reconfigure(srcpad)) {
+ reconfigure = true;
+ break;
+ }
+ }
+
+ /* If no pads need a reconfiguration something went wrong. */
+ if (!reconfigure)
+ err = -EPIPE;
+
+ break;
+ }
+
+ case GST_FLOW_EOS: {
+ g_autoptr(GstEvent) eos = gst_event_new_eos();
+ guint32 seqnum = gst_util_seqnum_next();
+ gst_event_set_seqnum(eos, seqnum);
+ for (GstPad *srcpad : srcpads_)
+ gst_pad_push_event(srcpad, gst_event_ref(eos));
+
+ err = -EPIPE;
+ break;
}
- gst_libcamera_resume_task(this->src_->task);
+ case GST_FLOW_FLUSHING:
+ err = -EPIPE;
+ break;
+
+ default:
+ GST_ELEMENT_FLOW_ERROR(src_, ret);
+
+ err = -EPIPE;
+ break;
+ }
+
+ return err;
+}
+
+void GstLibcameraSrcState::clearRequests()
+{
+ GLibLocker locker(&lock_);
+ completedRequests_ = {};
}
static bool
gst_libcamera_src_open(GstLibcameraSrc *self)
{
- std::unique_ptr<CameraManager> cm = std::make_unique<CameraManager>();
+ std::shared_ptr<CameraManager> cm;
std::shared_ptr<Camera> cam;
- gint ret = 0;
+ gint ret;
GST_DEBUG_OBJECT(self, "Opening camera device ...");
- ret = cm->start();
+ cm = gst_libcamera_get_camera_manager(ret);
if (ret) {
GST_ELEMENT_ERROR(self, LIBRARY, INIT,
("Failed listing cameras."),
@@ -224,137 +389,257 @@ gst_libcamera_src_open(GstLibcameraSrc *self)
}
if (camera_name) {
- cam = cm->get(self->camera_name);
+ cam = cm->get(camera_name);
if (!cam) {
GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
- ("Could not find a camera named '%s'.", self->camera_name),
+ ("Could not find a camera named '%s'.", camera_name),
("libcamera::CameraMananger::get() returned nullptr"));
return false;
}
} else {
- if (cm->cameras().empty()) {
+ auto cameras = cm->cameras();
+ if (cameras.empty()) {
GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
("Could not find any supported camera on this system."),
("libcamera::CameraMananger::cameras() is empty"));
return false;
}
- cam = cm->cameras()[0];
+ cam = cameras[0];
}
- GST_INFO_OBJECT(self, "Using camera named '%s'", cam->name().c_str());
+ GST_INFO_OBJECT(self, "Using camera '%s'", cam->id().c_str());
ret = cam->acquire();
if (ret) {
GST_ELEMENT_ERROR(self, RESOURCE, BUSY,
- ("Camera name '%s' is already in use.", cam->name().c_str()),
+ ("Camera '%s' is already in use.", cam->id().c_str()),
("libcamera::Camera::acquire() failed: %s", g_strerror(ret)));
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. */
- self->state->cm_ = std::move(cm);
+ self->state->cm_ = cm;
self->state->cam_ = cam;
return true;
}
+/* Must be called with stream_lock held. */
+static bool
+gst_libcamera_src_negotiate(GstLibcameraSrc *self)
+{
+ GstLibcameraSrcState *state = self->state;
+ std::vector<GstVideoTransferFunction> transfer(state->srcpads_.size(),
+ GST_VIDEO_TRANSFER_UNKNOWN);
+
+ g_autoptr(GstStructure) element_caps = gst_structure_new_empty("caps");
+
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ StreamConfiguration &stream_cfg = state->config_->at(i);
+
+ /* Retrieve the supported caps. */
+ g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
+ g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter);
+ if (gst_caps_is_empty(caps))
+ return false;
+
+ /* Fixate caps and configure the stream. */
+ caps = gst_caps_make_writable(caps);
+ gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]);
+ gst_libcamera_get_framerate_from_caps(caps, element_caps);
+ }
+
+ /* Validate the configuration. */
+ if (state->config_->validate() == CameraConfiguration::Invalid)
+ return false;
+
+ int ret = state->cam_->configure(state->config_.get());
+ if (ret) {
+ GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
+ ("Failed to configure camera: %s", g_strerror(-ret)),
+ ("Camera::configure() failed with error code %i", ret));
+ return false;
+ }
+
+ /* Check frame duration bounds within controls::FrameDurationLimits */
+ gst_libcamera_clamp_and_set_frameduration(state->initControls_,
+ state->cam_->controls(), element_caps);
+
+ /*
+ * Regardless if it has been modified, create clean caps and push the
+ * caps event. Downstream will decide if the caps are acceptable.
+ */
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ const StreamConfiguration &stream_cfg = state->config_->at(i);
+
+ g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg, transfer[i]);
+ gst_libcamera_framerate_to_caps(caps, element_caps);
+
+ if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps)))
+ return false;
+ }
+
+ if (self->allocator)
+ g_clear_object(&self->allocator);
+
+ self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get());
+ if (!self->allocator) {
+ GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
+ ("Failed to allocate memory"),
+ ("gst_libcamera_allocator_new() failed."));
+ return false;
+ }
+
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ const StreamConfiguration &stream_cfg = state->config_->at(i);
+
+ GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,
+ stream_cfg.stream());
+ g_signal_connect_swapped(pool, "buffer-notify",
+ G_CALLBACK(gst_task_resume), self->task);
+
+ gst_libcamera_pad_set_pool(srcpad, pool);
+
+ /* Clear all reconfigure flags. */
+ gst_pad_check_reconfigure(srcpad);
+ }
+
+ return true;
+}
+
static void
gst_libcamera_src_task_run(gpointer user_data)
{
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
GstLibcameraSrcState *state = self->state;
- Request *request = state->cam_->createRequest();
- auto wrap = std::make_unique<RequestWrap>(request);
- for (GstPad *srcpad : state->srcpads_) {
- GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
- GstBuffer *buffer;
- GstFlowReturn ret;
+ /*
+ * Start by pausing the task. The task may also get resumed by the
+ * buffer-notify signal when new buffers are queued back to the pool,
+ * or by the request completion handler when a new request has
+ * completed. Both will resume the task after adding the buffers or
+ * request to their respective lists, which are checked below to decide
+ * if the task needs to be resumed for another iteration. This is thus
+ * guaranteed to be race-free, the lock taken by gst_task_pause() and
+ * gst_task_resume() serves as a memory barrier.
+ */
+ gst_task_pause(self->task);
- ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
- &buffer, nullptr);
- if (ret != GST_FLOW_OK) {
- /*
- * RequestWrap does not take ownership, and we won't be
- * queueing this one due to lack of buffers.
- */
- delete request;
- request = nullptr;
- break;
- }
+ bool doResume = false;
- wrap->attachBuffer(buffer);
- }
+ g_autoptr(GstEvent) event = self->pending_eos.exchange(nullptr);
+ if (event) {
+ for (GstPad *srcpad : state->srcpads_)
+ gst_pad_push_event(srcpad, gst_event_ref(event));
- if (request) {
- GLibLocker lock(GST_OBJECT(self));
- GST_TRACE_OBJECT(self, "Requesting buffers");
- state->cam_->queueRequest(request);
- state->requests_.push(std::move(wrap));
+ return;
}
- GstFlowReturn ret = GST_FLOW_OK;
- gst_flow_combiner_reset(self->flow_combiner);
+ /* Check if a srcpad requested a renegotiation. */
+ bool reconfigure = false;
for (GstPad *srcpad : state->srcpads_) {
- ret = gst_libcamera_pad_push_pending(srcpad);
- ret = gst_flow_combiner_update_pad_flow(self->flow_combiner,
- srcpad, ret);
+ if (gst_pad_check_reconfigure(srcpad)) {
+ /* Check if the caps even need changing. */
+ g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);
+ if (!gst_pad_peer_query_accept_caps(srcpad, caps)) {
+ reconfigure = true;
+ break;
+ }
+ }
}
- {
- /*
- * Here we need to decide if we want to pause or stop the task. This
- * needs to happen in lock step with the callback thread which may want
- * to resume the task.
- */
- GLibLocker lock(GST_OBJECT(self));
- if (ret != GST_FLOW_OK) {
- if (ret == GST_FLOW_EOS) {
- g_autoptr(GstEvent) eos = gst_event_new_eos();
- guint32 seqnum = gst_util_seqnum_next();
- gst_event_set_seqnum(eos, seqnum);
- for (GstPad *srcpad : state->srcpads_)
- gst_pad_push_event(srcpad, gst_event_ref(eos));
- } else if (ret != GST_FLOW_FLUSHING) {
- GST_ELEMENT_FLOW_ERROR(self, ret);
- }
+ if (reconfigure) {
+ state->cam_->stop();
+ state->clearRequests();
+
+ if (!gst_libcamera_src_negotiate(self)) {
+ GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
gst_task_stop(self->task);
- return;
}
- bool do_pause = true;
- for (GstPad *srcpad : state->srcpads_) {
- if (gst_libcamera_pad_has_pending(srcpad)) {
- do_pause = false;
- break;
- }
- }
+ state->cam_->start(&state->initControls_);
+ }
- if (do_pause)
- gst_task_pause(self->task);
+ /*
+ * Create and queue one request. If no buffers are available the
+ * function returns -ENOBUFS, which we ignore here as that's not a
+ * fatal error.
+ */
+ int ret = state->queueRequest();
+ switch (ret) {
+ case 0:
+ /*
+ * The request was successfully queued, there may be enough
+ * buffers to create a new one. Don't pause the task to give it
+ * another try.
+ */
+ doResume = true;
+ break;
+
+ case -ENOMEM:
+ GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
+ ("Failed to allocate request for camera '%s'.",
+ state->cam_->id().c_str()),
+ ("libcamera::Camera::createRequest() failed"));
+ gst_task_stop(self->task);
+ return;
+
+ case -ENOBUFS:
+ default:
+ break;
}
+
+ /*
+ * Process one completed request, if available, and record if further
+ * requests are ready for processing.
+ */
+ ret = state->processRequest();
+ switch (ret) {
+ case 0:
+ /* Another completed request is available, resume the task. */
+ doResume = true;
+ break;
+
+ case -EPIPE:
+ gst_task_stop(self->task);
+ return;
+
+ case -ENOBUFS:
+ default:
+ break;
+ }
+
+ /* Resume the task for another iteration if needed. */
+ if (doResume)
+ gst_task_resume(self->task);
}
static void
-gst_libcamera_src_task_enter(GstTask *task, GThread *thread, gpointer user_data)
+gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
+ gpointer user_data)
{
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
GLibRecLocker lock(&self->stream_lock);
GstLibcameraSrcState *state = self->state;
- GstFlowReturn flow_ret = GST_FLOW_OK;
gint ret;
GST_DEBUG_OBJECT(self, "Streaming thread has started");
- guint group_id = gst_util_group_id_next();
- StreamRoles roles;
+ gint stream_id_num = 0;
+ std::vector<StreamRole> roles;
for (GstPad *srcpad : state->srcpads_) {
/* Create stream-id and push stream-start. */
- g_autofree gchar *stream_id = gst_pad_create_stream_id(srcpad, GST_ELEMENT(self), nullptr);
+ g_autofree gchar *stream_id_intermediate = g_strdup_printf("%i%i", state->group_id_, stream_id_num++);
+ g_autofree gchar *stream_id = gst_pad_create_stream_id(srcpad, GST_ELEMENT(self), stream_id_intermediate);
GstEvent *event = gst_event_new_stream_start(stream_id);
- gst_event_set_group_id(event, group_id);
+ gst_event_set_group_id(event, state->group_id_);
gst_pad_push_event(srcpad, event);
/* Collect the streams roles for the next iteration. */
@@ -363,90 +648,33 @@ gst_libcamera_src_task_enter(GstTask *task, GThread *thread, gpointer user_data)
/* Generate the stream configurations, there should be one per pad. */
state->config_ = state->cam_->generateConfiguration(roles);
- /*
- * \todo Check if camera may increase or decrease the number of streams
- * regardless of the number of roles.
- */
- g_assert(state->config_->size() == state->srcpads_.size());
-
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- StreamConfiguration &stream_cfg = state->config_->at(i);
-
- /* Retrieve the supported caps. */
- g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
- g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter);
- if (gst_caps_is_empty(caps)) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- break;
- }
-
- /* Fixate caps and configure the stream. */
- caps = gst_caps_make_writable(caps);
- gst_libcamera_configure_stream_from_caps(stream_cfg, caps);
- }
-
- if (flow_ret != GST_FLOW_OK)
- goto done;
-
- /* Validate the configuration. */
- if (state->config_->validate() == CameraConfiguration::Invalid) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- goto done;
- }
-
- /*
- * Regardless if it has been modified, create clean caps and push the
- * caps event. Downstream will decide if the caps are acceptable.
- */
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- const StreamConfiguration &stream_cfg = state->config_->at(i);
-
- g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);
- if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- break;
- }
-
- /* Send an open segment event with time format. */
- GstSegment segment;
- gst_segment_init(&segment, GST_FORMAT_TIME);
- gst_pad_push_event(srcpad, gst_event_new_segment(&segment));
- }
-
- ret = state->cam_->configure(state->config_.get());
- if (ret) {
+ if (state->config_ == nullptr) {
GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
- ("Failed to configure camera: %s", g_strerror(-ret)),
- ("Camera::configure() failed with error code %i", ret));
+ ("Failed to generate camera configuration from roles"),
+ ("Camera::generateConfiguration() returned nullptr"));
gst_task_stop(task);
return;
}
+ g_assert(state->config_->size() == state->srcpads_.size());
- self->allocator = gst_libcamera_allocator_new(state->cam_);
- if (!self->allocator) {
- GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
- ("Failed to allocate memory"),
- ("gst_libcamera_allocator_new() failed."));
+ if (!gst_libcamera_src_negotiate(self)) {
+ state->initControls_.clear();
+ GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
gst_task_stop(task);
return;
}
self->flow_combiner = gst_flow_combiner_new();
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- const StreamConfiguration &stream_cfg = state->config_->at(i);
- GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,
- stream_cfg.stream());
- g_signal_connect_swapped(pool, "buffer-notify",
- G_CALLBACK(gst_libcamera_resume_task), task);
-
- gst_libcamera_pad_set_pool(srcpad, pool);
+ for (GstPad *srcpad : state->srcpads_) {
gst_flow_combiner_add_pad(self->flow_combiner, srcpad);
+
+ /* Send an open segment event with time format. */
+ GstSegment segment;
+ gst_segment_init(&segment, GST_FORMAT_TIME);
+ gst_pad_push_event(srcpad, gst_event_new_segment(&segment));
}
- ret = state->cam_->start();
+ ret = state->cam_->start(&state->initControls_);
if (ret) {
GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
("Failed to start the camera: %s", g_strerror(-ret)),
@@ -454,20 +682,12 @@ gst_libcamera_src_task_enter(GstTask *task, GThread *thread, gpointer user_data)
gst_task_stop(task);
return;
}
-
-done:
- switch (flow_ret) {
- case GST_FLOW_NOT_NEGOTIATED:
- GST_ELEMENT_FLOW_ERROR(self, flow_ret);
- gst_task_stop(task);
- break;
- default:
- break;
- }
}
static void
-gst_libcamera_src_task_leave(GstTask *task, GThread *thread, gpointer user_data)
+gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task,
+ [[maybe_unused]] GThread *thread,
+ gpointer user_data)
{
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
GstLibcameraSrcState *state = self->state;
@@ -475,9 +695,13 @@ gst_libcamera_src_task_leave(GstTask *task, GThread *thread, gpointer user_data)
GST_DEBUG_OBJECT(self, "Streaming thread is about to stop");
state->cam_->stop();
+ state->clearRequests();
- for (GstPad *srcpad : state->srcpads_)
- gst_libcamera_pad_set_pool(srcpad, nullptr);
+ {
+ GLibRecLocker locker(&self->stream_lock);
+ for (GstPad *srcpad : state->srcpads_)
+ gst_libcamera_pad_set_pool(srcpad, nullptr);
+ }
g_clear_object(&self->allocator);
g_clear_pointer(&self->flow_combiner,
@@ -492,15 +716,16 @@ gst_libcamera_src_close(GstLibcameraSrc *self)
GST_DEBUG_OBJECT(self, "Releasing resources");
+ state->config_.reset();
+
ret = state->cam_->release();
if (ret) {
GST_ELEMENT_WARNING(self, RESOURCE, BUSY,
- ("Camera name '%s' is still in use.", state->cam_->name().c_str()),
+ ("Camera '%s' is still in use.", state->cam_->id().c_str()),
("libcamera::Camera.release() failed: %s", g_strerror(-ret)));
}
state->cam_.reset();
- state->cm_->stop();
state->cm_.reset();
}
@@ -510,6 +735,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:
@@ -517,7 +743,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;
}
}
@@ -528,13 +755,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;
}
}
@@ -557,6 +786,7 @@ gst_libcamera_src_change_state(GstElement *element, GstStateChange transition)
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
/* This needs to be called after pads activation.*/
+ self->state->group_id_ = gst_util_group_id_next();
if (!gst_task_pause(self->task))
return GST_STATE_CHANGE_FAILURE;
ret = GST_STATE_CHANGE_NO_PREROLL;
@@ -587,6 +817,27 @@ gst_libcamera_src_change_state(GstElement *element, GstStateChange transition)
return ret;
}
+static gboolean
+gst_libcamera_src_send_event(GstElement *element, GstEvent *event)
+{
+ GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+ gboolean ret = FALSE;
+
+ switch (GST_EVENT_TYPE(event)) {
+ case GST_EVENT_EOS: {
+ GstEvent *oldEvent = self->pending_eos.exchange(event);
+ gst_clear_event(&oldEvent);
+ ret = TRUE;
+ break;
+ }
+ default:
+ gst_event_unref(event);
+ break;
+ }
+
+ return ret;
+}
+
static void
gst_libcamera_src_finalize(GObject *object)
{
@@ -595,6 +846,7 @@ gst_libcamera_src_finalize(GObject *object)
g_rec_mutex_clear(&self->stream_lock);
g_clear_object(&self->task);
+ g_mutex_clear(&self->state->lock_);
g_free(self->camera_name);
delete self->state;
@@ -613,14 +865,71 @@ gst_libcamera_src_init(GstLibcameraSrc *self)
gst_task_set_leave_callback(self->task, gst_libcamera_src_task_leave, self, nullptr);
gst_task_set_lock(self->task, &self->stream_lock);
- state->srcpads_.push_back(gst_pad_new_from_template(templ, "src"));
- gst_element_add_pad(GST_ELEMENT(self), state->srcpads_[0]);
+ g_mutex_init(&state->lock_);
+
+ GstPad *pad = gst_pad_new_from_template(templ, "src");
+ state->srcpads_.push_back(pad);
+ gst_element_add_pad(GST_ELEMENT(self), pad);
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
+ GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE);
/* C-style friend. */
state->src_ = self;
self->state = state;
}
+static GstPad *
+gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ,
+ const gchar *name, [[maybe_unused]] const GstCaps *caps)
+{
+ GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+ g_autoptr(GstPad) pad = NULL;
+
+ GST_DEBUG_OBJECT(self, "new request pad created");
+
+ pad = gst_pad_new_from_template(templ, name);
+ g_object_ref_sink(pad);
+
+ if (gst_element_add_pad(element, pad)) {
+ GLibRecLocker lock(&self->stream_lock);
+ self->state->srcpads_.push_back(reinterpret_cast<GstPad *>(g_object_ref(pad)));
+ } else {
+ GST_ELEMENT_ERROR(element, STREAM, FAILED,
+ ("Internal data stream error."),
+ ("Could not add pad to element"));
+ return NULL;
+ }
+
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
+ return reinterpret_cast<GstPad *>(g_steal_pointer(&pad));
+}
+
+static void
+gst_libcamera_src_release_pad(GstElement *element, GstPad *pad)
+{
+ GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+
+ gst_child_proxy_child_removed(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
+ GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad);
+
+ {
+ GLibRecLocker lock(&self->stream_lock);
+ std::vector<GstPad *> &pads = self->state->srcpads_;
+ auto begin_iterator = pads.begin();
+ auto end_iterator = pads.end();
+ auto pad_iterator = std::find(begin_iterator, end_iterator, pad);
+
+ if (pad_iterator != end_iterator) {
+ g_object_unref(*pad_iterator);
+ pads.erase(pad_iterator);
+ }
+ }
+ gst_element_remove_pad(element, pad);
+}
+
static void
gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
{
@@ -631,12 +940,15 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
object_class->get_property = gst_libcamera_src_get_property;
object_class->finalize = gst_libcamera_src_finalize;
+ element_class->request_new_pad = gst_libcamera_src_request_new_pad;
+ element_class->release_pad = gst_libcamera_src_release_pad;
element_class->change_state = gst_libcamera_src_change_state;
+ element_class->send_event = gst_libcamera_src_send_event;
gst_element_class_set_metadata(element_class,
"libcamera Source", "Source/Video",
"Linux Camera source using libcamera",
- "Nicolas Dufresne <nicolas.dufresne@collabora.com");
+ "Nicolas Dufresne <nicolas.dufresne@collabora.com>");
gst_element_class_add_static_pad_template_with_gtype(element_class,
&src_template,
GST_TYPE_LIBCAMERA_PAD);
@@ -651,4 +963,36 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec);
+
+ GstCameraControls::installProperties(object_class, PROP_LAST);
+}
+
+/* GstChildProxy implementation */
+static GObject *
+gst_libcamera_src_child_proxy_get_child_by_index(GstChildProxy *child_proxy,
+ guint index)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ GObject *obj = nullptr;
+
+ obj = reinterpret_cast<GObject *>(g_list_nth_data(GST_ELEMENT(child_proxy)->srcpads, index));
+ if (obj)
+ gst_object_ref(obj);
+
+ return obj;
+}
+
+static guint
+gst_libcamera_src_child_proxy_get_children_count(GstChildProxy *child_proxy)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ return GST_ELEMENT_CAST(child_proxy)->numsrcpads;
+}
+
+static void
+gst_libcamera_src_child_proxy_init(gpointer g_iface, [[maybe_unused]] gpointer iface_data)
+{
+ GstChildProxyInterface *iface = reinterpret_cast<GstChildProxyInterface *>(g_iface);
+ iface->get_child_by_index = gst_libcamera_src_child_proxy_get_child_by_index;
+ iface->get_children_count = gst_libcamera_src_child_proxy_get_children_count;
}
diff --git a/src/gstreamer/gstlibcamerasrc.h b/src/gstreamer/gstlibcamerasrc.h
index 0144cbc4..a27db9ca 100644
--- a/src/gstreamer/gstlibcamerasrc.h
+++ b/src/gstreamer/gstlibcamerasrc.h
@@ -3,11 +3,10 @@
* Copyright (C) 2019, Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
- * gstlibcamerasrc.h - GStreamer Capture Element
+ * GStreamer Capture Element
*/
-#ifndef __GST_LIBCAMERA_SRC_H__
-#define __GST_LIBCAMERA_SRC_H__
+#pragma once
#include <gst/gst.h>
@@ -18,5 +17,3 @@ G_DECLARE_FINAL_TYPE(GstLibcameraSrc, gst_libcamera_src,
GST_LIBCAMERA, SRC, GstElement)
G_END_DECLS
-
-#endif /* __GST_LIBCAMERA_SRC_H__ */
diff --git a/src/gstreamer/meson.build b/src/gstreamer/meson.build
index e119e472..6b7e53b5 100644
--- a/src/gstreamer/meson.build
+++ b/src/gstreamer/meson.build
@@ -1,3 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+
+glib_dep = dependency('glib-2.0', required : get_option('gstreamer'))
+
+gst_dep_version = '>=1.14.0'
+gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_dep_version,
+ required : get_option('gstreamer'))
+gstallocator_dep = dependency('gstreamer-allocators-1.0', version : gst_dep_version,
+ required : get_option('gstreamer'))
+
+if not glib_dep.found() or not gstvideo_dep.found() or not gstallocator_dep.found()
+ gst_enabled = false
+ subdir_done()
+endif
+
+gst_enabled = true
+
libcamera_gst_sources = [
'gstlibcamera-utils.cpp',
'gstlibcamera.cpp',
@@ -8,33 +25,46 @@ 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()),
+ '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40',
]
-glib_dep = dependency('glib-2.0', required : get_option('gstreamer'))
+# The G_DECLARE_FINAL_TYPE macro creates static inline functions that were
+# not marked as possibly unused prior to GLib v2.63.0. This causes clang to
+# complain about the ones we are not using. Silence the -Wunused-function
+# warning in that case.
+if cc.get_id() == 'clang' and glib_dep.version().version_compare('<2.63.0')
+ libcamera_gst_cpp_args += ['-Wno-unused-function']
+endif
-gst_dep_version = '>=1.14.0'
-gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_dep_version,
- required : get_option('gstreamer'))
-gstallocator_dep = dependency('gstreamer-allocators-1.0', version : gst_dep_version,
- required : get_option('gstreamer'))
+libcamera_gst = shared_library('gstlibcamera',
+ libcamera_gst_sources,
+ cpp_args : libcamera_gst_cpp_args,
+ dependencies : [libcamera_public, gstvideo_dep, gstallocator_dep],
+ install : true,
+ install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
+)
-if glib_dep.found() and gstvideo_dep.found() and gstallocator_dep.found()
- # The G_DECLARE_FINAL_TYPE macro creates static inline functions that were
- # not marked as possibly unused prior to GLib v2.63.0. This causes clang to
- # complain about the ones we are not using. Silence the -Wunused-function
- # warning in that case.
- if cc.get_id() == 'clang' and glib_dep.version().version_compare('<2.63.0')
- libcamera_gst_cpp_args += [ '-Wno-unused-function' ]
- endif
-
- libcamera_gst = shared_library('gstlibcamera',
- libcamera_gst_sources,
- cpp_args : libcamera_gst_cpp_args,
- dependencies : [libcamera_dep, gstvideo_dep, gstallocator_dep],
- install: true,
- install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
- )
-endif
+# Make the plugin visible to GStreamer inside meson devenv.
+fs = import('fs')
+gst_plugin_path = fs.parent(libcamera_gst.full_path())
+
+gst_env = environment()
+gst_env.prepend('GST_PLUGIN_PATH', gst_plugin_path)
+
+# Avoid polluting the system registry.
+gst_env.set('GST_REGISTRY', gst_plugin_path / 'registry.data')
+
+meson.add_devenv(gst_env)