From patchwork Wed Jun 29 07:04:13 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16417 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 8F546BE173 for ; Wed, 29 Jun 2022 07:04:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 247B26565F; Wed, 29 Jun 2022 09:04:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1656486298; bh=Bq8k8rOb0O7mkL+i6snxoUc9XlhC/lVk2HbitiQO87o=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=KjQhtcvFrysNDoQOXGrdSx3/767GS5u8uEsP66T3mp+KCflKjDe1l4/sfgQCvDkk8 7TrWsGYJOraXGqQIiezfpEG8QR9V4EPpJLe5uY6/u2EWGuaJXBuW4hiwkgjMub83Gf LRZPLvAdipTbYdA/7bn+EpHS7RX/AG4i/uPpAXPGyz+otEaNaqSJLfgQ25/G5y61TP aMZNk/x5CjBLJZm7cxSkvRXZtR69vFJSLC55yDNdgXv2yaDre/q1W1iu7O/I1UmLUw c8Xam5xMhEr9/zX48s3FoWypK8L0/idEViGNKM3pKwZUZbNcBjEmIhX/wjTo8vazYK QR2hx++HNkbQA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B60E96563A for ; Wed, 29 Jun 2022 09:04:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Tt6OapQP"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0BAE76D1; Wed, 29 Jun 2022 09:04:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1656486286; bh=Bq8k8rOb0O7mkL+i6snxoUc9XlhC/lVk2HbitiQO87o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Tt6OapQP0crZ850xpZkMpjqBpwqVb1YOKHdQNM+U7qz0NQWPFtFyOkCvcLDZphm+u otUNiqJ/LGj0LB9Wk1FV7+G2GNddk4IdWUOzCm6sUy6dZsFO1sCWeJXT2TUxMy1NHg AzMUAMYlaEMzUfWq/hFr3vNe6B++vt8qKvGhGY/8= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Wed, 29 Jun 2022 10:04:13 +0300 Message-Id: <20220629070416.17550-12-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220629070416.17550-1-tomi.valkeinen@ideasonboard.com> References: <20220629070416.17550-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 11/14] py: New event handling X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" At the moment the Python bindings only handle the requestCompleted events. But we have others, bufferCompleted and disconnect from the Camera, and cameraAdded and cameraRemoved from the CameraManager. This makes all those events available to the users. The get_ready_requests() method is now deprecated, but available to keep backward compatibility. The new event handling works as follows: The user sets callbacks to the CameraManager or Cameras (e.g. Camera.buffer_completed). When the event_fd informs of an event, the user must call CameraManager.dispatch_events() which gets the queued events and calls the relevant callbacks for each queued event. Additionally there is CameraManager.discard_events() if the user does not want to process the events but wants to clear the event queue (e.g. after stopping the cameras or when exiting the app). I'm not very happy with this patch. It works fine, but there's a lot of repetition of almost-the-same code. Perhaps some template magics might reduce the repetition, but I also fear that it can easily make the code more difficult to read. TODO: Documentation. Signed-off-by: Tomi Valkeinen --- src/py/libcamera/py_camera_manager.cpp | 250 +++++++++++++++++++++++-- src/py/libcamera/py_camera_manager.h | 64 ++++++- src/py/libcamera/py_main.cpp | 89 ++++++++- 3 files changed, 381 insertions(+), 22 deletions(-) diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp index 3dd8668e..599a9f7e 100644 --- a/src/py/libcamera/py_camera_manager.cpp +++ b/src/py/libcamera/py_camera_manager.cpp @@ -58,6 +58,7 @@ py::list PyCameraManager::cameras() return l; } +/* DEPRECATED */ std::vector PyCameraManager::getReadyRequests() { int ret = readFd(); @@ -70,21 +71,234 @@ std::vector PyCameraManager::getReadyRequests() std::vector py_reqs; - for (Request *request : getCompletedRequests()) { - py::object o = py::cast(request); - /* Decrease the ref increased in Camera.queue_request() */ - o.dec_ref(); - py_reqs.push_back(o); + for (const auto &ev : getEvents()) { + switch (ev.type_) { + case CameraEvent::Type::RequestCompleted: { + py::object o = py::cast(ev.request_); + /* Decrease the ref increased in Camera.queue_request() */ + o.dec_ref(); + py_reqs.push_back(o); + } + default: + /* ignore */ + break; + } } return py_reqs; } /* Note: Called from another thread */ -void PyCameraManager::handleRequestCompleted(Request *req) +void PyCameraManager::handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb) { - pushRequest(req); - writeFd(); + CameraEvent ev(CameraEvent::Type::BufferCompleted, cam); + ev.request_ = req; + ev.fb_ = fb; + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleRequestCompleted(std::shared_ptr cam, Request *req) +{ + CameraEvent ev(CameraEvent::Type::RequestCompleted, cam); + ev.request_ = req; + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleDisconnected(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::Type::Disconnect, cam); + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraAdded(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::Type::CameraAdded, cam); + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraRemoved(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::Type::CameraRemoved, cam); + + pushEvent(ev); +} + +void PyCameraManager::dispatchEvents() +{ + int ret = readFd(); + + if (ret == EAGAIN) { + LOG(Python, Debug) << "No events to dispatch"; + return; + } + + if (ret != 0) + throw std::system_error(ret, std::generic_category()); + + std::vector events = getEvents(); + + LOG(Python, Debug) << "Dispatch " << events.size() << " events"; + + for (const auto &event : events) { + std::shared_ptr camera = event.camera_; + + switch (event.type_) { + case CameraEvent::Type::CameraAdded: { + if (cameraAddedHandler_) + cameraAddedHandler_(camera); + + break; + } + case CameraEvent::Type::CameraRemoved: { + if (cameraRemovedHandler_) + cameraRemovedHandler_(camera); + + break; + } + case CameraEvent::Type::BufferCompleted: { + auto cb = getBufferCompleted(camera.get()); + if (cb) + cb(camera, event.request_, event.fb_); + + break; + } + case CameraEvent::Type::RequestCompleted: { + auto cb = getRequestCompleted(camera.get()); + if (cb) + cb(camera, event.request_); + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(event.request_); + o.dec_ref(); + + break; + } + case CameraEvent::Type::Disconnect: { + auto cb = getDisconnected(camera.get()); + if (cb) + cb(camera); + + break; + } + default: + ASSERT(false); + } + } +} + +void PyCameraManager::discardEvents() +{ + int ret = readFd(); + + if (ret == EAGAIN) + return; + + if (ret != 0) + throw std::system_error(ret, std::generic_category()); + + std::vector v = getEvents(); + + LOG(Python, Debug) << "Discard " << v.size() << " events"; + + for (const auto &ev : v) { + if (ev.type_ != CameraEvent::Type::RequestCompleted) + continue; + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(ev.request_); + o.dec_ref(); + } +} + +std::function)> PyCameraManager::getCameraAdded() const +{ + return cameraAddedHandler_; +} + +void PyCameraManager::setCameraAdded(std::function)> func) +{ + if (cameraAddedHandler_) + cameraManager_->cameraAdded.disconnect(); + + cameraAddedHandler_ = func; + + if (func) + cameraManager_->cameraAdded.connect(this, &PyCameraManager::handleCameraAdded); +} + +std::function)> PyCameraManager::getCameraRemoved() const +{ + return cameraRemovedHandler_; +} + +void PyCameraManager::setCameraRemoved(std::function)> func) +{ + if (cameraRemovedHandler_) + cameraManager_->cameraRemoved.disconnect(); + + cameraRemovedHandler_ = func; + + if (func) + cameraManager_->cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved); +} + +std::function, Request *)> PyCameraManager::getRequestCompleted(Camera *cam) +{ + if (auto it = cameraRequestCompletedHandlers_.find(cam); + it != cameraRequestCompletedHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setRequestCompleted(Camera *cam, std::function, Request *)> func) +{ + if (func) + cameraRequestCompletedHandlers_[cam] = func; + else + cameraRequestCompletedHandlers_.erase(cam); +} + +std::function, Request *, FrameBuffer *)> PyCameraManager::getBufferCompleted(Camera *cam) +{ + if (auto it = cameraBufferCompletedHandlers_.find(cam); + it != cameraBufferCompletedHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setBufferCompleted(Camera *cam, std::function, Request *, FrameBuffer *)> func) +{ + if (func) + cameraBufferCompletedHandlers_[cam] = func; + else + cameraBufferCompletedHandlers_.erase(cam); +} + +std::function)> PyCameraManager::getDisconnected(Camera *cam) +{ + if (auto it = cameraDisconnectHandlers_.find(cam); + it != cameraDisconnectHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setDisconnected(Camera *cam, std::function)> func) +{ + if (func) + cameraDisconnectHandlers_[cam] = func; + else + cameraDisconnectHandlers_.erase(cam); } void PyCameraManager::writeFd() @@ -110,16 +324,22 @@ int PyCameraManager::readFd() return 0; } -void PyCameraManager::pushRequest(Request *req) +void PyCameraManager::pushEvent(const CameraEvent &ev) { - MutexLocker guard(completedRequestsMutex_); - completedRequests_.push_back(req); + MutexLocker guard(cameraEventsMutex_); + cameraEvents_.push_back(ev); + + writeFd(); + + LOG(Python, Debug) << "Queued events: " << cameraEvents_.size(); } -std::vector PyCameraManager::getCompletedRequests() +std::vector PyCameraManager::getEvents() { - std::vector v; - MutexLocker guard(completedRequestsMutex_); - swap(v, completedRequests_); + std::vector v; + + MutexLocker guard(cameraEventsMutex_); + swap(v, cameraEvents_); + return v; } diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 3525057d..aa51a6bc 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -30,16 +30,70 @@ public: void handleRequestCompleted(Request *req); + void handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb); + void handleRequestCompleted(std::shared_ptr cam, Request *req); + void handleDisconnected(std::shared_ptr cam); + void handleCameraAdded(std::shared_ptr cam); + void handleCameraRemoved(std::shared_ptr cam); + + void dispatchEvents(); + void discardEvents(); + + std::function)> getCameraAdded() const; + void setCameraAdded(std::function)> func); + + std::function)> getCameraRemoved() const; + void setCameraRemoved(std::function)> func); + + std::function, Request *)> getRequestCompleted(Camera *cam); + void setRequestCompleted(Camera *cam, std::function, Request *)> func); + + std::function, Request *, FrameBuffer *)> getBufferCompleted(Camera *cam); + void setBufferCompleted(Camera *cam, std::function, Request *, FrameBuffer *)> func); + + std::function)> getDisconnected(Camera *cam); + void setDisconnected(Camera *cam, std::function)> func); + private: + struct CameraEvent { + enum class Type { + Undefined = 0, + CameraAdded, + CameraRemoved, + Disconnect, + RequestCompleted, + BufferCompleted, + }; + + CameraEvent(Type type, std::shared_ptr camera) + : type_(type), camera_(camera) + { + } + + Type type_; + + std::shared_ptr camera_; + + Request *request_ = nullptr; + FrameBuffer *fb_ = nullptr; + }; + std::unique_ptr cameraManager_; UniqueFD eventFd_; - libcamera::Mutex completedRequestsMutex_; - std::vector completedRequests_ - LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_); + libcamera::Mutex cameraEventsMutex_; + std::vector cameraEvents_ + LIBCAMERA_TSA_GUARDED_BY(cameraEvents_); + + std::function)> cameraAddedHandler_; + std::function)> cameraRemovedHandler_; + + std::map, Request *, FrameBuffer *)>> cameraBufferCompletedHandlers_; + std::map, Request *)>> cameraRequestCompletedHandlers_; + std::map)>> cameraDisconnectHandlers_; void writeFd(); int readFd(); - void pushRequest(Request *req); - std::vector getCompletedRequests(); + void pushEvent(const CameraEvent &ev); + std::vector getEvents(); }; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 1ef1384e..a07f06c4 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -107,7 +107,20 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("cameras", &PyCameraManager::cameras) .def_property_readonly("event_fd", &PyCameraManager::eventFd) - .def("get_ready_requests", &PyCameraManager::getReadyRequests); + + /* DEPRECATED */ + .def("get_ready_requests", &PyCameraManager::getReadyRequests) + + .def("dispatch_events", &PyCameraManager::dispatchEvents) + .def("discard_events", &PyCameraManager::discardEvents) + + .def_property("camera_added", + &PyCameraManager::getCameraAdded, + &PyCameraManager::setCameraAdded) + + .def_property("camera_removed", + &PyCameraManager::getCameraRemoved, + &PyCameraManager::setCameraRemoved); pyCamera .def_property_readonly("id", &Camera::id) @@ -131,7 +144,14 @@ PYBIND11_MODULE(_libcamera, m) auto cm = gCameraManager.lock(); ASSERT(cm); - self.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted); + /* + * Note: We always subscribe requestComplete, as the bindings + * use requestComplete event to decrement the Request refcount- + */ + + self.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) { + cm->handleRequestCompleted(camera, req); + }); ControlList controlList(self.controls()); @@ -159,6 +179,71 @@ PYBIND11_MODULE(_libcamera, m) "Failed to start camera"); }) + .def_property("request_completed", + [](Camera &self) { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + return cm->getRequestCompleted(&self); + }, + [](Camera &self, std::function, Request *)> f) { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + cm->setRequestCompleted(&self, f); + + /* + * Note: We do not subscribe requestComplete here, as we + * do that in the start() method. + */ + }) + + .def_property("buffer_completed", + [](Camera &self) -> std::function, Request *, FrameBuffer *)> { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + return cm->getBufferCompleted(&self); + }, + [](Camera &self, std::function, Request *, FrameBuffer *)> f) { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + cm->setBufferCompleted(&self, f); + + self.bufferCompleted.disconnect(); + + if (!f) + return; + + self.bufferCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req, FrameBuffer *fb) { + cm->handleBufferCompleted(camera, req, fb); + }); + }) + + .def_property("disconnected", + [](Camera &self) -> std::function)> { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + return cm->getDisconnected(&self); + }, + [](Camera &self, std::function)> f) { + auto cm = gCameraManager.lock(); + ASSERT(cm); + + cm->setDisconnected(&self, f); + + self.disconnected.disconnect(); + + if (!f) + return; + + self.disconnected.connect(&self, [cm, camera=self.shared_from_this()]() { + cm->handleDisconnected(camera); + }); + }) + .def("__str__", [](Camera &self) { return ""; })