diff options
Diffstat (limited to 'src/gstreamer/gstlibcamerasrc.cpp')
-rw-r--r-- | src/gstreamer/gstlibcamerasrc.cpp | 260 |
1 files changed, 217 insertions, 43 deletions
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index f015c6d2..b34f0897 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerasrc.cpp - GStreamer Capture Element + * GStreamer Capture Element */ /** @@ -37,10 +37,11 @@ #include <gst/base/base.h> +#include "gstlibcamera-controls.h" +#include "gstlibcamera-utils.h" #include "gstlibcameraallocator.h" #include "gstlibcamerapad.h" #include "gstlibcamerapool.h" -#include "gstlibcamera-utils.h" using namespace libcamera; @@ -128,6 +129,7 @@ struct GstLibcameraSrcState { ControlList initControls_; guint group_id_; + GstCameraControls controls_; int queueRequest(); void requestCompleted(Request *request); @@ -142,7 +144,6 @@ struct _GstLibcameraSrc { GstTask *task; gchar *camera_name; - controls::AfModeEnum auto_focus_mode = controls::AfModeManual; std::atomic<GstEvent *> pending_eos; @@ -154,10 +155,15 @@ struct _GstLibcameraSrc { enum { PROP_0, PROP_CAMERA_NAME, - PROP_AUTO_FOCUS_MODE, + 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")) @@ -180,6 +186,9 @@ int GstLibcameraSrcState::queueRequest() if (!request) return -ENOMEM; + /* Apply controls */ + controls_.applyControls(request); + std::unique_ptr<RequestWrap> wrap = std::make_unique<RequestWrap>(std::move(request)); @@ -223,6 +232,9 @@ GstLibcameraSrcState::requestCompleted(Request *request) { GLibLocker locker(&lock_); + + controls_.readMetadata(request); + wrap = std::move(queuedRequests_.front()); queuedRequests_.pop(); } @@ -256,6 +268,55 @@ GstLibcameraSrcState::requestCompleted(Request *request) gst_task_resume(src_->task); } +static void +gst_libcamera_extrapolate_info(GstVideoInfo *info, guint32 stride) +{ + guint i, estride; + gsize offset = 0; + + /* This should be updated if tiled formats get added in the future. */ + for (i = 0; i < GST_VIDEO_INFO_N_PLANES(info); i++) { + estride = gst_video_format_info_extrapolate_stride(info->finfo, i, stride); + info->stride[i] = estride; + info->offset[i] = offset; + offset += estride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info->finfo, i, + GST_VIDEO_INFO_HEIGHT(info)); + } +} + +static GstFlowReturn +gst_libcamera_video_frame_copy(GstBuffer *src, GstBuffer *dest, const GstVideoInfo *dest_info, guint32 stride) +{ + GstVideoInfo src_info = *dest_info; + GstVideoFrame src_frame, dest_frame; + + gst_libcamera_extrapolate_info(&src_info, stride); + src_info.size = gst_buffer_get_size(src); + + if (!gst_video_frame_map(&src_frame, &src_info, src, GST_MAP_READ)) { + GST_ERROR("Could not map src buffer"); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map(&dest_frame, const_cast<GstVideoInfo *>(dest_info), dest, GST_MAP_WRITE)) { + GST_ERROR("Could not map dest buffer"); + gst_video_frame_unmap(&src_frame); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_copy(&dest_frame, &src_frame)) { + GST_ERROR("Could not copy frame"); + gst_video_frame_unmap(&src_frame); + gst_video_frame_unmap(&dest_frame); + return GST_FLOW_ERROR; + } + + gst_video_frame_unmap(&src_frame); + gst_video_frame_unmap(&dest_frame); + + return GST_FLOW_OK; +} + /* Must be called with stream_lock held. */ int GstLibcameraSrcState::processRequest() { @@ -280,11 +341,41 @@ int GstLibcameraSrcState::processRequest() GstFlowReturn ret = GST_FLOW_OK; gst_flow_combiner_reset(src_->flow_combiner); - for (GstPad *srcpad : srcpads_) { + for (gsize i = 0; i < srcpads_.size(); i++) { + GstPad *srcpad = srcpads_[i]; Stream *stream = gst_libcamera_pad_get_stream(srcpad); GstBuffer *buffer = wrap->detachBuffer(stream); FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer); + const StreamConfiguration &stream_cfg = config_->at(i); + GstBufferPool *video_pool = gst_libcamera_pad_get_video_pool(srcpad); + + if (video_pool) { + /* Only set video pool when a copy is needed. */ + GstBuffer *copy = NULL; + const GstVideoInfo info = gst_libcamera_pad_get_video_info(srcpad); + + ret = gst_buffer_pool_acquire_buffer(video_pool, ©, NULL); + if (ret != GST_FLOW_OK) { + gst_buffer_unref(buffer); + GST_ELEMENT_ERROR(src_, RESOURCE, SETTINGS, + ("Failed to acquire buffer"), + ("GstLibcameraSrcState::processRequest() failed: %s", g_strerror(-ret))); + return -EPIPE; + } + + ret = gst_libcamera_video_frame_copy(buffer, copy, &info, stream_cfg.stride); + gst_buffer_unref(buffer); + if (ret != GST_FLOW_OK) { + gst_buffer_unref(copy); + GST_ELEMENT_ERROR(src_, RESOURCE, SETTINGS, + ("Failed to copy buffer"), + ("GstLibcameraSrcState::processRequest() failed: %s", g_strerror(-ret))); + return -EPIPE; + } + + buffer = copy; + } if (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) { GST_BUFFER_PTS(buffer) = wrap->pts_; @@ -377,21 +468,22 @@ 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 '%s'", cam->id().c_str()); @@ -404,6 +496,8 @@ gst_libcamera_src_open(GstLibcameraSrc *self) return false; } + self->state->controls_.setCamera(cam); + cam->requestCompleted.connect(self->state, &GstLibcameraSrcState::requestCompleted); /* No need to lock here, we didn't start our threads yet. */ @@ -418,6 +512,8 @@ 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"); @@ -433,7 +529,7 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) /* Fixate caps and configure the stream. */ caps = gst_caps_make_writable(caps); - gst_libcamera_configure_stream_from_caps(stream_cfg, caps); + gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]); gst_libcamera_get_framerate_from_caps(caps, element_caps); } @@ -461,7 +557,7 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) 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); + 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))) @@ -482,13 +578,70 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) for (gsize i = 0; i < state->srcpads_.size(); i++) { GstPad *srcpad = state->srcpads_[i]; const StreamConfiguration &stream_cfg = state->config_->at(i); + GstBufferPool *video_pool = NULL; + GstVideoInfo info; + + g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg, transfer[i]); + + gst_video_info_from_caps(&info, caps); + gst_libcamera_pad_set_video_info(srcpad, &info); + + /* Stride mismatch between camera stride and that calculated by video-info. */ + if (static_cast<unsigned int>(info.stride[0]) != stream_cfg.stride && + GST_VIDEO_INFO_FORMAT(&info) != GST_VIDEO_FORMAT_ENCODED) { + GstQuery *query = NULL; + const gboolean need_pool = true; + gboolean has_video_meta = false; + + gst_libcamera_extrapolate_info(&info, stream_cfg.stride); + + query = gst_query_new_allocation(caps, need_pool); + if (!gst_pad_peer_query(srcpad, query)) + GST_DEBUG_OBJECT(self, "Didn't get downstream ALLOCATION hints"); + else + has_video_meta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL); + + if (!has_video_meta) { + GstBufferPool *pool = NULL; + + if (gst_query_get_n_allocation_pools(query) > 0) + gst_query_parse_nth_allocation_pool(query, 0, &pool, NULL, NULL, NULL); + + if (pool) + video_pool = pool; + else { + GstStructure *config; + guint min_buffers = 3; + video_pool = gst_video_buffer_pool_new(); + + config = gst_buffer_pool_get_config(video_pool); + gst_buffer_pool_config_set_params(config, caps, info.size, min_buffers, 0); + + GST_DEBUG_OBJECT(self, "Own pool config is %" GST_PTR_FORMAT, config); + + gst_buffer_pool_set_config(GST_BUFFER_POOL_CAST(video_pool), config); + } + + GST_WARNING_OBJECT(self, "Downstream doesn't support video meta, need to copy frame."); + + if (!gst_buffer_pool_set_active(video_pool, true)) { + gst_caps_unref(caps); + GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, + ("Failed to active buffer pool"), + ("gst_libcamera_src_negotiate() failed.")); + return false; + } + } + gst_query_unref(query); + } GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, - stream_cfg.stream()); + stream_cfg.stream(), &info); g_signal_connect_swapped(pool, "buffer-notify", G_CALLBACK(gst_task_resume), self->task); gst_libcamera_pad_set_pool(srcpad, pool); + gst_libcamera_pad_set_video_pool(srcpad, video_pool); /* Clear all reconfigure flags. */ gst_pad_check_reconfigure(srcpad); @@ -657,18 +810,6 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gst_pad_push_event(srcpad, gst_event_new_segment(&segment)); } - if (self->auto_focus_mode != controls::AfModeManual) { - const ControlInfoMap &infoMap = state->cam_->controls(); - if (infoMap.find(&controls::AfMode) != infoMap.end()) { - state->initControls_.set(controls::AfMode, self->auto_focus_mode); - } else { - GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, - ("Failed to enable auto focus"), - ("AfMode not supported by this camera, " - "please retry with 'auto-focus-mode=AfModeManual'")); - } - } - ret = state->cam_->start(&state->initControls_); if (ret) { GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, @@ -730,17 +871,16 @@ 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: g_free(self->camera_name); self->camera_name = g_value_dup_string(value); break; - case PROP_AUTO_FOCUS_MODE: - self->auto_focus_mode = static_cast<controls::AfModeEnum>(g_value_get_enum(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; } } @@ -751,16 +891,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; - case PROP_AUTO_FOCUS_MODE: - g_value_set_enum(value, static_cast<gint>(self->auto_focus_mode)); - 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; } } @@ -864,8 +1003,10 @@ gst_libcamera_src_init(GstLibcameraSrc *self) g_mutex_init(&state->lock_); - state->srcpads_.push_back(gst_pad_new_from_template(templ, "src")); - gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back()); + 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); @@ -896,6 +1037,8 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ, 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)); } @@ -904,6 +1047,8 @@ 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); { @@ -913,6 +1058,12 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad) auto end_iterator = pads.end(); auto pad_iterator = std::find(begin_iterator, end_iterator, pad); + GstBufferPool *video_pool = gst_libcamera_pad_get_video_pool(pad); + if (video_pool) { + gst_buffer_pool_set_active(video_pool, false); + gst_object_unref(video_pool); + } + if (pad_iterator != end_iterator) { g_object_unref(*pad_iterator); pads.erase(pad_iterator); @@ -939,7 +1090,7 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) 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); @@ -955,12 +1106,35 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); - spec = g_param_spec_enum("auto-focus-mode", - "Set auto-focus mode", - "Available options: AfModeManual, " - "AfModeAuto or AfModeContinuous.", - gst_libcamera_auto_focus_get_type(), - static_cast<gint>(controls::AfModeManual), - G_PARAM_WRITABLE); - g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, 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; } |