{"id":2756,"url":"https://patchwork.libcamera.org/api/patches/2756/?format=json","web_url":"https://patchwork.libcamera.org/patch/2756/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20200129033210.278800-23-nicolas@ndufresne.ca>","date":"2020-01-29T03:32:09","name":"[libcamera-devel,v1,22/23] gst: libcamerasrc: Implement initial streaming","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"adf4604d64678749006b193e1b532cfe11f76f5c","submitter":{"id":30,"url":"https://patchwork.libcamera.org/api/people/30/?format=json","name":"Nicolas Dufresne","email":"nicolas@ndufresne.ca"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/2756/mbox/","series":[{"id":648,"url":"https://patchwork.libcamera.org/api/series/648/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=648","date":"2020-01-29T03:31:47","name":"GStreamer Element for libcamera","version":1,"mbox":"https://patchwork.libcamera.org/series/648/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/2756/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/2756/checks/","tags":{},"headers":{"Return-Path":"<nicolas@ndufresne.ca>","Received":["from bhuna.collabora.co.uk (bhuna.collabora.co.uk [46.235.227.227])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 89840608BE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Jan 2020 04:35:47 +0100 (CET)","from nicolas-tpx395.localdomain (unknown\n\t[IPv6:2002:c0de:c115:0:66fc:8b:2a38:8313])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits))\n\t(No client certificate requested) (Authenticated sender: nicolas)\n\tby bhuna.collabora.co.uk (Postfix) with ESMTPSA id D7B3928EA9F;\n\tWed, 29 Jan 2020 03:35:45 +0000 (GMT)"],"From":"Nicolas Dufresne <nicolas@ndufresne.ca>","To":"libcamera-devel@lists.libcamera.org","Cc":"Nicolas Dufresne <nicolas.dufresne@collabora.com>","Date":"Tue, 28 Jan 2020 22:32:09 -0500","Message-Id":"<20200129033210.278800-23-nicolas@ndufresne.ca>","X-Mailer":"git-send-email 2.24.1","In-Reply-To":"<20200129033210.278800-1-nicolas@ndufresne.ca>","References":"<20200129033210.278800-1-nicolas@ndufresne.ca>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v1 22/23] gst: libcamerasrc: Implement\n\tinitial streaming","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 29 Jan 2020 03:35:47 -0000"},"content":"From: Nicolas Dufresne <nicolas.dufresne@collabora.com>\n\nWith this patch, the element is not able to push buffers to the next\nelement in the graph. The buffers are currently missing any metadata\nlike timestamp, sequence number. The handling of the GstFlowReturn\nfor multiple pads isn't using a GstFlowCombiner as it should.\n\nSigned-off-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>\n---\n src/gstreamer/gstlibcamerapad.cpp |  18 ++-\n src/gstreamer/gstlibcamerapad.h   |   2 +\n src/gstreamer/gstlibcamerasrc.cpp | 191 +++++++++++++++++++++++++++++-\n 3 files changed, 208 insertions(+), 3 deletions(-)","diff":"diff --git a/src/gstreamer/gstlibcamerapad.cpp b/src/gstreamer/gstlibcamerapad.cpp\nindex 4a775e4..7bc44c1 100644\n--- a/src/gstreamer/gstlibcamerapad.cpp\n+++ b/src/gstreamer/gstlibcamerapad.cpp\n@@ -152,7 +152,7 @@ gst_libcamera_pad_push_pending(GstPad *pad)\n {\n \tauto *self = GST_LIBCAMERA_PAD(pad);\n \tGstBuffer *buffer;\n-\tGstFlowReturn ret = GST_FLOW_CUSTOM_SUCCESS;\n+\tGstFlowReturn ret = GST_FLOW_OK;\n \n \t{\n \t\tGST_OBJECT_LOCKER(self);\n@@ -164,3 +164,19 @@ gst_libcamera_pad_push_pending(GstPad *pad)\n \n \treturn ret;\n }\n+\n+bool\n+gst_libcamera_pad_has_pending(GstPad *pad)\n+{\n+\tauto *self = GST_LIBCAMERA_PAD(pad);\n+\tGST_OBJECT_LOCKER(self);\n+\treturn (self->pending_buffers.length > 0);\n+}\n+\n+void\n+gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency)\n+{\n+\tauto *self = GST_LIBCAMERA_PAD(pad);\n+\tGST_OBJECT_LOCKER(self);\n+\tself->latency = latency;\n+}\ndiff --git a/src/gstreamer/gstlibcamerapad.h b/src/gstreamer/gstlibcamerapad.h\nindex d928570..eb24000 100644\n--- a/src/gstreamer/gstlibcamerapad.h\n+++ b/src/gstreamer/gstlibcamerapad.h\n@@ -30,4 +30,6 @@ void gst_libcamera_pad_queue_buffer(GstPad *pad, GstBuffer *buffer);\n \n GstFlowReturn gst_libcamera_pad_push_pending(GstPad *pad);\n \n+bool gst_libcamera_pad_has_pending(GstPad *pad);\n+\n #endif /* __GST_LIBCAMERA_PAD_H__ */\ndiff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\nindex 5fc4393..947a8bf 100644\n--- a/src/gstreamer/gstlibcamerasrc.cpp\n+++ b/src/gstreamer/gstlibcamerasrc.cpp\n@@ -12,8 +12,10 @@\n #include \"gstlibcamerapool.h\"\n #include \"gstlibcamera-utils.h\"\n \n+#include <queue>\n #include <libcamera/camera.h>\n #include <libcamera/camera_manager.h>\n+#include <gst/base/base.h>\n \n using namespace libcamera;\n \n@@ -22,12 +24,73 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);\n \n #define STREAM_LOCKER(obj) g_autoptr(GRecMutexLocker) stream_locker = g_rec_mutex_locker_new(&GST_LIBCAMERA_SRC(obj)->stream_lock)\n \n-/* Used for C++ object with destructors */\n+struct RequestWrap {\n+\tRequestWrap(Request *request);\n+\t~RequestWrap();\n+\n+\tvoid AttachBuffer(GstBuffer *buffer);\n+\tGstBuffer *DetachBuffer(Stream *stream);\n+\n+\t/* For ptr comparision only */\n+\tRequest *request_;\n+\tstd::map<Stream *, GstBuffer *> buffers_;\n+};\n+\n+RequestWrap::RequestWrap(Request *request)\n+\t: request_(request)\n+{\n+}\n+\n+RequestWrap::~RequestWrap()\n+{\n+\tfor (std::pair<Stream * const, GstBuffer *> &item : buffers_) {\n+\t\tif (item.second)\n+\t\t\tgst_buffer_unref(item.second);\n+\t}\n+}\n+\n+void\n+RequestWrap::AttachBuffer(GstBuffer *buffer)\n+{\n+\tFrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);\n+\tStream *stream = gst_libcamera_buffer_get_stream(buffer);\n+\n+\trequest_->addBuffer(stream, fb);\n+\n+\tauto item = buffers_.find(stream);\n+\tif (item != buffers_.end()) {\n+\t\tgst_buffer_unref(item->second);\n+\t\titem->second = buffer;\n+\t} else {\n+\t\tbuffers_[stream] = buffer;\n+\t}\n+}\n+\n+GstBuffer *\n+RequestWrap::DetachBuffer(Stream *stream)\n+{\n+\tGstBuffer *buffer = nullptr;\n+\n+\tauto item = buffers_.find(stream);\n+\tif (item != buffers_.end()) {\n+\t\tbuffer = item->second;\n+\t\titem->second = nullptr;\n+\t}\n+\n+\treturn buffer;\n+}\n+\n+/* Used for C++ object with destructors and callbacks */\n struct GstLibcameraSrcState {\n+\tGstLibcameraSrc *src;\n+\n \tstd::shared_ptr<CameraManager> cm;\n \tstd::shared_ptr<Camera> cam;\n \tstd::unique_ptr<CameraConfiguration> config;\n \tstd::vector<GstPad *> srcpads;\n+\tstd::queue<std::unique_ptr<RequestWrap>> requests;\n+\n+\tvoid requestCompleted(Request *request);\n };\n \n struct _GstLibcameraSrc {\n@@ -40,6 +103,7 @@ struct _GstLibcameraSrc {\n \n \tGstLibcameraSrcState *state;\n \tGstLibcameraAllocator *allocator;\n+\tGstFlowCombiner *flow_combiner;\n };\n \n enum {\n@@ -63,6 +127,41 @@ GstStaticPadTemplate request_src_template = {\n \t\"src_%s\", GST_PAD_SRC, GST_PAD_REQUEST, TEMPLATE_CAPS\n };\n \n+void\n+GstLibcameraSrcState::requestCompleted(Request *request)\n+{\n+\tGST_OBJECT_LOCKER(this->src);\n+\n+\tGST_DEBUG_OBJECT(this->src, \"buffers are ready\");\n+\n+\tstd::unique_ptr<RequestWrap> wrap = std::move(this->requests.front());\n+\tthis->requests.pop();\n+\n+\tg_return_if_fail(wrap->request_ == request);\n+\n+\tif ((request->status() == Request::RequestCancelled)) {\n+\t\tGST_DEBUG_OBJECT(this->src, \"Request was cancelled\");\n+\t\treturn;\n+\t}\n+\n+\tGstBuffer *buffer;\n+\tfor (GstPad *srcpad : this->srcpads) {\n+\t\tStream *stream = gst_libcamera_pad_get_stream(srcpad);\n+\t\tbuffer = wrap->DetachBuffer(stream);\n+\t\tgst_libcamera_pad_queue_buffer(srcpad, buffer);\n+\t}\n+\n+\t{\n+\t\t/* We only want to resume the task if it's paused */\n+\t\tGstTask *task = this->src->task;\n+\t\tGST_OBJECT_LOCKER(task);\n+\t\tif (GST_TASK_STATE(task) == GST_TASK_PAUSED) {\n+\t\t\tGST_TASK_STATE(task) = GST_TASK_STARTED;\n+\t\t\tGST_TASK_SIGNAL(task);\n+\t\t}\n+\t}\n+}\n+\n static bool\n gst_libcamera_src_open(GstLibcameraSrc *self)\n {\n@@ -115,6 +214,8 @@ gst_libcamera_src_open(GstLibcameraSrc *self)\n \t\treturn false;\n \t}\n \n+\tcam->requestCompleted.connect(self->state, &GstLibcameraSrcState::requestCompleted);\n+\n \t/* no need to lock here we didn't start our threads */\n \tself->state->cm = cm;\n \tself->state->cam = cam;\n@@ -126,8 +227,74 @@ static void\n gst_libcamera_src_task_run(gpointer user_data)\n {\n \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n+\tGstLibcameraSrcState *state = self->state;\n+\n+\tRequest *request = new Request(state->cam.get());\n+\tauto wrap = std::make_unique<RequestWrap>(request);\n+\tfor (GstPad *srcpad : state->srcpads) {\n+\t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n+\t\tGstBuffer *buffer;\n+\t\tGstFlowReturn ret;\n+\n+\t\tret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),\n+\t\t\t\t\t\t     &buffer, nullptr);\n+\t\tif (ret != GST_FLOW_OK) {\n+\t\t\t/* RequestWrap does not take ownership, and we won't be\n+\t\t\t * queueing this one due to lack of buffers */\n+\t\t\tdelete request;\n+\t\t\trequest = NULL;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twrap->AttachBuffer(buffer);\n+\t}\n+\n+\tif (request) {\n+\t\tGST_OBJECT_LOCKER(self);\n+\t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n+\t\tstate->cam->queueRequest(request);\n+\t\tstate->requests.push(std::move(wrap));\n+\t}\n+\n+\tGstFlowReturn ret = GST_FLOW_OK;\n+\tgst_flow_combiner_reset(self->flow_combiner);\n+\tfor (GstPad *srcpad : state->srcpads) {\n+\t\tret = gst_libcamera_pad_push_pending(srcpad);\n+\t\tret = gst_flow_combiner_update_pad_flow(self->flow_combiner,\n+\t\t\t\t\t\t\tsrcpad, ret);\n+\t}\n+\n+\t{\n+\t\t/* Here we need to decide if we want to pause or stop the task. This\n+\t\t * needs to happend in lock step with the callback thread which may want\n+\t\t * to resume the task.\n+\t\t */\n+\t\tGST_OBJECT_LOCKER(self);\n+\t\tif (ret != GST_FLOW_OK) {\n+\t\t\tif (ret == GST_FLOW_EOS) {\n+\t\t\t\tg_autoptr(GstEvent) eos = gst_event_new_eos();\n+\t\t\t\tguint32 seqnum = gst_util_seqnum_next();\n+\t\t\t\tgst_event_set_seqnum(eos, seqnum);\n+\t\t\t\tfor (GstPad *srcpad : state->srcpads)\n+\t\t\t\t\tgst_pad_push_event(srcpad, gst_event_ref(eos));\n+\t\t\t} else if (ret != GST_FLOW_FLUSHING) {\n+\t\t\t\tGST_ELEMENT_FLOW_ERROR(self, ret);\n+\t\t\t}\n+\t\t\tgst_task_stop(self->task);\n+\t\t\treturn;\n+\t\t}\n \n-\tGST_DEBUG_OBJECT(self, \"Streaming thread it now capturing\");\n+\t\tgboolean do_pause = true;\n+\t\tfor (GstPad *srcpad : state->srcpads) {\n+\t\t\tif (gst_libcamera_pad_has_pending(srcpad)) {\n+\t\t\t\tdo_pause = false;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (do_pause)\n+\t\t\tgst_task_pause(self->task);\n+\t}\n }\n \n static void\n@@ -137,6 +304,7 @@ gst_libcamera_src_task_enter(GstTask *task, GThread *thread, gpointer user_data)\n \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n \tGstLibcameraSrcState *state = self->state;\n \tGstFlowReturn flow_ret = GST_FLOW_OK;\n+\tgint ret = 0;\n \n \tGST_DEBUG_OBJECT(self, \"Streaming thread has started\");\n \n@@ -219,12 +387,23 @@ gst_libcamera_src_task_enter(GstTask *task, GThread *thread, gpointer user_data)\n \t\treturn;\n \t}\n \n+\tself->flow_combiner = gst_flow_combiner_new();\n \tfor (gsize i = 0; i < state->srcpads.size(); i++) {\n \t\tGstPad *srcpad = state->srcpads[i];\n \t\tconst StreamConfiguration &stream_cfg = state->config->at(i);\n \t\tGstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,\n \t\t\t\t\t\t\t\tstream_cfg.stream());\n \t\tgst_libcamera_pad_set_pool(srcpad, pool);\n+\t\tgst_flow_combiner_add_pad(self->flow_combiner, srcpad);\n+\t}\n+\n+\tret = state->cam->start();\n+\tif (ret) {\n+\t\tGST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,\n+\t\t\t\t  (\"Failed to start the camera: %s\", g_strerror(-ret)),\n+\t\t\t\t  (\"Camera.start() failed with error code %i\", ret));\n+\t\tgst_task_stop(task);\n+\t\treturn;\n \t}\n \n done:\n@@ -254,6 +433,8 @@ gst_libcamera_src_task_leave(GstTask *task, GThread *thread, gpointer user_data)\n \t\tgst_libcamera_pad_set_pool(srcpad, NULL);\n \n \tg_clear_object(&self->allocator);\n+\tg_clear_pointer(&self->flow_combiner,\n+\t\t\t(GDestroyNotify)gst_flow_combiner_free);\n }\n \n static void\n@@ -333,6 +514,9 @@ gst_libcamera_src_change_state(GstElement *element, GstStateChange transition)\n \t\t\treturn GST_STATE_CHANGE_FAILURE;\n \t\tret = GST_STATE_CHANGE_NO_PREROLL;\n \t\tbreak;\n+\tcase GST_STATE_CHANGE_PAUSED_TO_PLAYING:\n+\t\tgst_task_start(self->task);\n+\t\tbreak;\n \tcase GST_STATE_CHANGE_PLAYING_TO_PAUSED:\n \t\tret = GST_STATE_CHANGE_NO_PREROLL;\n \t\tbreak;\n@@ -378,6 +562,9 @@ gst_libcamera_src_init(GstLibcameraSrc *self)\n \n \tstate->srcpads.push_back(gst_pad_new_from_template(templ, \"src\"));\n \tgst_element_add_pad(GST_ELEMENT(self), state->srcpads[0]);\n+\n+\t/* C-style friend */\n+\tstate->src = self;\n \tself->state = state;\n }\n \n","prefixes":["libcamera-devel","v1","22/23"]}