From patchwork Thu Jun 23 14:47:35 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16344 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 F1582BD808 for ; Thu, 23 Jun 2022 14:48:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 63F2265645; Thu, 23 Jun 2022 16:48:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995687; bh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=; 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=bnuu7ipLJU0B6NRWCe5YPLe5CKglRXvpFrR/ypA6vA7PFedD89JDJ1RmZd3SJw+zs j40wvRkBEP0pEPbBckLaVazIMw+xafnuVuJfClOJy2/yOrr5wRNrJ29kubWTVPwwfg rLdSLzRtfwWzidBFRef31QRj0faVGDb/7s9OOUrgNteW81PvAnr/Fs6w8889g2wDe4 RqRreUmac6tqNGLM1oATtUbp4+Qaj1J3WTh4hXdpmtl5lly25UjODhk2CPefhzn84H p5lSjSHzFqa3QEwlK5ut+KPLrvPwohRYH2Qrtht6NaOsCmi9fZp0PGJqI/9REb/qSH ZcN7zdYV9fAVg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 95EB465635 for ; Thu, 23 Jun 2022 16:48:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vUVhrQhh"; 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 EA8586BB; Thu, 23 Jun 2022 16:47:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995680; bh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vUVhrQhhjpNu93j9Fc776/SFwXQF7qxk4PROcGCR4ynLhl/ViAzsG3dwhkOAF1iiS KG6meXzFrC/JHNMldUXF2r1eE7ynTlN1oLB88aXYtZxA57/FuOobhjZ1SMpvvnfG+H Zd8Xjd8vPq4Cr8B6lC6eWfoWZt8jwq7bOMGW/FrU= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:35 +0300 Message-Id: <20220623144736.78537-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 6/7] 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 disconnec 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 | 291 +++++++++++++++++++++++-- src/py/libcamera/py_camera_manager.h | 41 +++- src/py/libcamera/py_main.cpp | 87 +++++++- 3 files changed, 397 insertions(+), 22 deletions(-) diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp index ba45f713..bbadb9ee 100644 --- a/src/py/libcamera/py_camera_manager.cpp +++ b/src/py/libcamera/py_camera_manager.cpp @@ -15,6 +15,37 @@ namespace py = pybind11; using namespace libcamera; +struct CameraEvent { + enum class EventType { + Undefined = 0, + CameraAdded, + CameraRemoved, + Disconnect, + RequestCompleted, + BufferCompleted, + }; + + CameraEvent(EventType type) + : type(type) + { + } + + EventType type; + + std::shared_ptr cam; + + union { + struct { + Request *req; + FrameBuffer *fb; + } buf_completed; + + struct { + Request *req; + } req_completed; + }; +}; + PyCameraManager::PyCameraManager() { int fd = eventfd(0, 0); @@ -56,33 +87,261 @@ py::list PyCameraManager::getCameras() return l; } +/* DEPRECATED */ std::vector PyCameraManager::getReadyRequests(bool nonBlocking) { if (!nonBlocking || hasEvents()) readFd(); - std::vector v; - getRequests(v); + std::vector v; + getEvents(v); std::vector ret; - for (Request *req : v) { - py::object o = py::cast(req); - /* Decrease the ref increased in Camera.queue_request() */ - o.dec_ref(); - ret.push_back(o); + for (const auto &ev : v) { + switch (ev.type) { + case CameraEvent::EventType::RequestCompleted: { + Request *req = ev.req_completed.req; + py::object o = py::cast(req); + /* Decrease the ref increased in Camera.queue_request() */ + o.dec_ref(); + ret.push_back(o); + } + default: + /* ignore */ + break; + } } return ret; } /* Note: Called from another thread */ -void PyCameraManager::handleRequestCompleted(Request *req) +void PyCameraManager::handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb) +{ + CameraEvent ev(CameraEvent::EventType::BufferCompleted); + ev.cam = cam; + ev.buf_completed.req = req; + ev.buf_completed.fb = fb; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleRequestCompleted(std::shared_ptr cam, Request *req) +{ + CameraEvent ev(CameraEvent::EventType::RequestCompleted); + ev.cam = cam; + ev.req_completed.req = req; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleDisconnected(std::shared_ptr cam) { - pushRequest(req); + CameraEvent ev(CameraEvent::EventType::Disconnect); + ev.cam = cam; + + pushEvent(ev); writeFd(); } +/* Note: Called from another thread */ +void PyCameraManager::handleCameraAdded(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::EventType::CameraAdded); + ev.cam = cam; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraRemoved(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::EventType::CameraRemoved); + ev.cam = cam; + + pushEvent(ev); + writeFd(); +} + +void PyCameraManager::dispatchEvents(bool nonBlocking) +{ + if (!nonBlocking || hasEvents()) + readFd(); + + std::vector v; + getEvents(v); + + LOG(Python, Debug) << "Dispatch " << v.size() << " events"; + + for (const auto &ev : v) { + switch (ev.type) { + case CameraEvent::EventType::CameraAdded: { + std::shared_ptr cam = ev.cam; + + if (cameraAddedHandler_) + cameraAddedHandler_(cam); + + break; + } + case CameraEvent::EventType::CameraRemoved: { + std::shared_ptr cam = ev.cam; + + if (cameraRemovedHandler_) + cameraRemovedHandler_(cam); + + break; + } + case CameraEvent::EventType::BufferCompleted: { + std::shared_ptr cam = ev.cam; + + auto cb = getBufferCompleted(cam.get()); + + if (cb) + cb(cam, ev.buf_completed.req, ev.buf_completed.fb); + + break; + } + case CameraEvent::EventType::RequestCompleted: { + std::shared_ptr cam = ev.cam; + + auto cb = getRequestCompleted(cam.get()); + + if (cb) + cb(cam, ev.req_completed.req); + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(ev.req_completed.req); + o.dec_ref(); + + break; + } + case CameraEvent::EventType::Disconnect: { + std::shared_ptr cam = ev.cam; + + auto cb = getDisconnected(cam.get()); + + if (cb) + cb(cam); + + break; + } + default: + assert(false); + } + } +} + +void PyCameraManager::discardEvents() +{ + if (hasEvents()) + readFd(); + + std::vector v; + getEvents(v); + + LOG(Python, Debug) << "Discard " << v.size() << " events"; + + for (const auto &ev : v) { + if (ev.type != CameraEvent::EventType::RequestCompleted) + continue; + + std::shared_ptr cam = ev.cam; + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(ev.req_completed.req); + o.dec_ref(); + } +} + +std::function)> PyCameraManager::getCameraAdded() const +{ + return cameraAddedHandler_; +} + +void PyCameraManager::setCameraAdded(std::function)> fun) +{ + if (cameraAddedHandler_) + cameraAdded.disconnect(); + + cameraAddedHandler_ = fun; + + if (fun) + cameraAdded.connect(this, &PyCameraManager::handleCameraAdded); +} + +std::function)> PyCameraManager::getCameraRemoved() const +{ + return cameraRemovedHandler_; +} + +void PyCameraManager::setCameraRemoved(std::function)> fun) +{ + if (cameraRemovedHandler_) + cameraRemoved.disconnect(); + + cameraRemovedHandler_ = fun; + + if (fun) + 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 *)> fun) +{ + if (fun) + cameraRequestCompletedHandlers_[cam] = fun; + 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 *)> fun) +{ + if (fun) + cameraBufferCompletedHandlers_[cam] = fun; + 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)> fun) +{ + if (fun) + cameraDisconnectHandlers_[cam] = fun; + else + cameraDisconnectHandlers_.erase(cam); +} + void PyCameraManager::writeFd() { uint64_t v = 1; @@ -104,16 +363,18 @@ void PyCameraManager::readFd() throw std::system_error(errno, std::generic_category()); } -void PyCameraManager::pushRequest(Request *req) +void PyCameraManager::pushEvent(const CameraEvent &ev) { - std::lock_guard guard(reqlist_mutex_); - reqList_.push_back(req); + std::lock_guard guard(reqlistMutex_); + cameraEvents_.push_back(ev); + + LOG(Python, Debug) << "Queued events: " << cameraEvents_.size(); } -void PyCameraManager::getRequests(std::vector &v) +void PyCameraManager::getEvents(std::vector &v) { - std::lock_guard guard(reqlist_mutex_); - swap(v, reqList_); + std::lock_guard guard(reqlistMutex_); + swap(v, cameraEvents_); } bool PyCameraManager::hasEvents() diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 2396d236..fd28291b 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -13,6 +13,8 @@ using namespace libcamera; +struct CameraEvent; + class PyCameraManager : public CameraManager { public: @@ -27,15 +29,46 @@ 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(bool nonBlocking = false); + void discardEvents(); + + std::function)> getCameraAdded() const; + void setCameraAdded(std::function)> fun); + + std::function)> getCameraRemoved() const; + void setCameraRemoved(std::function)> fun); + + std::function, Request *)> getRequestCompleted(Camera *cam); + void setRequestCompleted(Camera *cam, std::function, Request *)> fun); + + std::function, Request *, FrameBuffer *)> getBufferCompleted(Camera *cam); + void setBufferCompleted(Camera *cam, std::function, Request *, FrameBuffer *)> fun); + + std::function)> getDisconnected(Camera *cam); + void setDisconnected(Camera *cam, std::function)> fun); + private: int eventFd_ = -1; - std::mutex reqlist_mutex_; + std::mutex reqlistMutex_; std::vector reqList_; + std::vector cameraEvents_; + + std::function)> cameraAddedHandler_; + std::function)> cameraRemovedHandler_; + + std::map, Request *, FrameBuffer *)>> cameraBufferCompletedHandlers_; + std::map, Request *)>> cameraRequestCompletedHandlers_; + std::map)>> cameraDisconnectHandlers_; void writeFd(); void readFd(); - void pushRequest(Request *req); - void getRequests(std::vector &v); - + void pushEvent(const CameraEvent &ev); + void getEvents(std::vector &v); bool hasEvents(); }; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index ee4ecb9b..b6fd9a9d 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -103,8 +103,20 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("cameras", &PyCameraManager::getCameras) .def_property_readonly("event_fd", &PyCameraManager::eventFd) + /* DEPRECATED */ .def("get_ready_requests", &PyCameraManager::getReadyRequests, - py::arg("nonblocking") = false); + py::arg("nonblocking") = false) + .def("dispatch_events", &PyCameraManager::dispatchEvents, + py::arg("nonblocking") = false) + .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) @@ -117,9 +129,13 @@ PYBIND11_MODULE(_libcamera, m) auto cm = gCameraManager.lock(); assert(cm); - self.requestCompleted.connect(&self, [cm=std::move(cm)](Request *req) { + /* + * Note: We always subscribe requestComplete, as the bindings + * use requestComplete event to decrement the Request refcount- + */ - cm->handleRequestCompleted(req); + self.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) { + cm->handleRequestCompleted(camera, req); }); ControlList controlList(self.controls()); @@ -148,6 +164,71 @@ PYBIND11_MODULE(_libcamera, m) return 0; }) + .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 ""; })