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/gstlibcamera-utils.cpp | 540 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamera-utils.h | 36 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamera.cpp | 4 | ||||
-rw-r--r-- | src/gstreamer/gstlibcameraallocator.cpp | 57 | ||||
-rw-r--r-- | src/gstreamer/gstlibcameraallocator.h | 11 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerapad.cpp | 61 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerapad.h | 13 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerapool.cpp | 56 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerapool.h | 10 | ||||
-rw-r--r-- | src/gstreamer/gstlibcameraprovider.cpp | 57 | ||||
-rw-r--r-- | src/gstreamer/gstlibcameraprovider.h | 8 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerasrc.cpp | 758 | ||||
-rw-r--r-- | src/gstreamer/gstlibcamerasrc.h | 7 | ||||
-rw-r--r-- | src/gstreamer/meson.build | 76 |
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) |