From patchwork Fri Jul 1 08:45:15 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16504 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 4A889BD808 for ; Fri, 1 Jul 2022 08:45:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0517B656AA; Fri, 1 Jul 2022 10:45:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1656665156; bh=/YkbYWzQtsOn9IeurNkp3sgIkzOM+11dUkx+At5lrSU=; 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=D/Ujq4GO06hgQtQ9QGI65W4BJnFeyOMHb9hEZeKEadTiJrnYOGjXHcPx7iYoeh1EN CYY5FhhPyOkpFjZ0TywUJuTCHAQi2OhDO7KYENiIXKTlCAuANKDqK+W/APjwVGrgGd LHDshrtjgip0NGpLWw1kdz/WFzGMPFFisLoHqeIZhCTcINOrs9CttH8oWRHf8kgoV4 FS+tr0Btc6FsDrF7GbdKqEDxxkjPhjQcUlK0KE6CAfFQXLLjpVbGZU4aP4Vz9x4rOu ulwBPlJDgmzmn2BaWO4e17MTczsTsgN4N5rx1ikK138nvLdYLLJlZgoFYskc5zH1uA xCbRPwZWnyDtA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F407265652 for ; Fri, 1 Jul 2022 10:45:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vHNdKkBp"; 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 74E0725C; Fri, 1 Jul 2022 10:45:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1656665145; bh=/YkbYWzQtsOn9IeurNkp3sgIkzOM+11dUkx+At5lrSU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vHNdKkBpuugy+5Po2MIvTxankoLgX9qazH4VxlUj0nDNnlvjz57J9Syc0CjAmQ+6E YnPWHHifctGF+CC8U0Gw9pbO8kag9LKWeaSlVDrc88wncHkp8oYU8fhglf3Rg5QHTe izdr+qxxwJg9xqUFpmcRl0refcd6Ol/hgPznYytI= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 1 Jul 2022 11:45:15 +0300 Message-Id: <20220701084521.31831-12-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220701084521.31831-1-tomi.valkeinen@ideasonboard.com> References: <20220701084521.31831-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 11/17] 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 patch 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 like get_ready_requests(), but instead of returning only Requests from requestCompleted events, it returns all event types using a new Event class. Additionally camera.stop() has been changed to return events for that camera. This serves two purposes: first, it removes the requestCompleted events from the event queue, thus preventing the old events being returned when the camera is started again, and second, it allows the user to process those events if it so wants. All other event types are always collected and returned, except bufferCompleted which needs to be enabled by setting the CameraManager.buffer_completed_active to True. This is to reduce the overhead a bit. It is not clear if this is a necessary optimization or not. TODO: Documentation. Signed-off-by: Tomi Valkeinen --- src/py/libcamera/py_camera_manager.cpp | 191 +++++++++++++++++++++++-- src/py/libcamera/py_camera_manager.h | 64 ++++++++- src/py/libcamera/py_main.cpp | 45 +++++- 3 files changed, 276 insertions(+), 24 deletions(-) diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp index 3dd8668e..d1d63690 100644 --- a/src/py/libcamera/py_camera_manager.cpp +++ b/src/py/libcamera/py_camera_manager.cpp @@ -5,6 +5,7 @@ #include "py_camera_manager.h" +#include #include #include #include @@ -33,11 +34,17 @@ PyCameraManager::PyCameraManager() if (ret) throw std::system_error(-ret, std::generic_category(), "Failed to start CameraManager"); + + cameraManager_->cameraAdded.connect(this, &PyCameraManager::handleCameraAdded); + cameraManager_->cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved); } PyCameraManager::~PyCameraManager() { LOG(Python, Debug) << "~PyCameraManager()"; + + cameraManager_->cameraAdded.disconnect(); + cameraManager_->cameraRemoved.disconnect(); } py::list PyCameraManager::cameras() @@ -58,6 +65,50 @@ py::list PyCameraManager::cameras() return l; } +PyCameraEvent PyCameraManager::convertEvent(const CameraEvent &event) +{ + PyCameraEvent pyevent; + + pyevent.type_ = event.type_; + + /* + * We need to set a keep-alive here so that the camera keeps the + * camera manager alive. + */ + py::object py_cm = py::cast(this); + py::object py_cam = py::cast(event.camera_); + py::detail::keep_alive_impl(py_cam, py_cm); + + pyevent.camera_ = py_cam; + + switch (event.type_) { + case CameraEventType::CameraAdded: + case CameraEventType::CameraRemoved: + case CameraEventType::Disconnect: + /* No additional parameters to add */ + break; + + case CameraEventType::BufferCompleted: { + pyevent.request_ = py::cast(event.request_); + pyevent.fb_ = py::cast(event.fb_); + break; + } + case CameraEventType::RequestCompleted: { + pyevent.request_ = py::cast(event.request_); + + /* Decrease the ref increased in Camera.queue_request() */ + pyevent.request_.dec_ref(); + + break; + } + default: + ASSERT(false); + } + + return pyevent; +} + +/* DEPRECATED */ std::vector PyCameraManager::getReadyRequests() { int ret = readFd(); @@ -70,21 +121,125 @@ 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()) { + if (ev.type_ != CameraEventType::RequestCompleted) + continue; + + PyCameraEvent pyev = convertEvent(ev); + py_reqs.push_back(pyev.request_); } return py_reqs; } +std::vector PyCameraManager::getPyEvents() +{ + int ret = readFd(); + + if (ret == EAGAIN) { + LOG(Python, Debug) << "No events"; + return {}; + } + + if (ret != 0) + throw std::system_error(ret, std::generic_category()); + + std::vector events = getEvents(); + + LOG(Python, Debug) << "Get " << events.size() << " events"; + + std::vector pyevents(events.size()); + + std::transform(events.begin(), events.end(), pyevents.begin(), + [this](const CameraEvent &ev) { + return convertEvent(ev); + }); + + return pyevents; +} + +static bool isCameraSpecificEvent(const CameraEvent &event, std::shared_ptr &camera) +{ + return event.camera_ == camera && + (event.type_ == CameraEventType::RequestCompleted || + event.type_ == CameraEventType::BufferCompleted || + event.type_ == CameraEventType::Disconnect); +} + +std::vector PyCameraManager::getPyCameraEvents(std::shared_ptr camera) +{ + MutexLocker guard(eventsMutex_); + + std::vector pyevents; + + /* Get events related to the given camera */ + + for (const auto &event : events_) { + if (!isCameraSpecificEvent(event, camera)) + continue; + + PyCameraEvent pyev = convertEvent(event); + pyevents.push_back(pyev); + } + + /* Drop the events from the events_ list */ + + events_.erase(std::remove_if(events_.begin(), events_.end(), + [&camera](const CameraEvent &ev) { + return isCameraSpecificEvent(ev, camera); + }), + events_.end()); + + LOG(Python, Debug) << "Get " << pyevents.size() << " camera events, " + << events_.size() << " unhandled events left"; + + return pyevents; +} + /* Note: Called from another thread */ -void PyCameraManager::handleRequestCompleted(Request *req) +void PyCameraManager::handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb) { - pushRequest(req); - writeFd(); + if (!bufferCompletedEventActive_) + return; + + CameraEvent ev(CameraEventType::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(CameraEventType::RequestCompleted, cam); + ev.request_ = req; + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleDisconnected(std::shared_ptr cam) +{ + CameraEvent ev(CameraEventType::Disconnect, cam); + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraAdded(std::shared_ptr cam) +{ + CameraEvent ev(CameraEventType::CameraAdded, cam); + + pushEvent(ev); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraRemoved(std::shared_ptr cam) +{ + CameraEvent ev(CameraEventType::CameraRemoved, cam); + + pushEvent(ev); } void PyCameraManager::writeFd() @@ -110,16 +265,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(eventsMutex_); + events_.push_back(ev); + + writeFd(); + + LOG(Python, Debug) << "Queued events: " << events_.size(); } -std::vector PyCameraManager::getCompletedRequests() +std::vector PyCameraManager::getEvents() { - std::vector v; - MutexLocker guard(completedRequestsMutex_); - swap(v, completedRequests_); + std::vector v; + + MutexLocker guard(eventsMutex_); + swap(v, events_); + return v; } diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 3525057d..b313ba9b 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -13,6 +13,47 @@ using namespace libcamera; +enum class CameraEventType { + Undefined = 0, + CameraAdded, + CameraRemoved, + Disconnect, + RequestCompleted, + BufferCompleted, +}; + +/* + * This event struct is used internally to queue the events we receive from + * other threads. + */ +struct CameraEvent { + CameraEvent(CameraEventType type, std::shared_ptr camera) + : type_(type), camera_(camera) + { + } + + CameraEventType type_ = CameraEventType::Undefined; + + std::shared_ptr camera_; + + Request *request_ = nullptr; + FrameBuffer *fb_ = nullptr; +}; + +/* + * This event struct is passed to Python. We need to use pybind11::object here + * instead of a C++ pointer so that we keep a ref to the Request, and a + * keep-alive from the camera to the camera manager. + */ +struct PyCameraEvent { + CameraEventType type_ = CameraEventType::Undefined; + + pybind11::object camera_; + + pybind11::object request_; + pybind11::object fb_; +}; + class PyCameraManager { public: @@ -26,20 +67,29 @@ public: int eventFd() const { return eventFd_.get(); } - std::vector getReadyRequests(); + std::vector getReadyRequests(); /* DEPRECATED */ + std::vector getPyEvents(); + std::vector getPyCameraEvents(std::shared_ptr camera); - 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); + bool bufferCompletedEventActive_ = false; private: std::unique_ptr cameraManager_; UniqueFD eventFd_; - libcamera::Mutex completedRequestsMutex_; - std::vector completedRequests_ - LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_); + libcamera::Mutex eventsMutex_; + std::vector events_ + LIBCAMERA_TSA_GUARDED_BY(eventsMutex_); void writeFd(); int readFd(); - void pushRequest(Request *req); - std::vector getCompletedRequests(); + void pushEvent(const CameraEvent &ev); + std::vector getEvents(); + + PyCameraEvent convertEvent(const CameraEvent &event); }; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 6cd99919..b4f756d7 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -58,6 +58,7 @@ PYBIND11_MODULE(_libcamera, m) * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings */ + auto pyEvent = py::class_(m, "Event"); auto pyCameraManager = py::class_(m, "CameraManager"); auto pyCamera = py::class_(m, "Camera"); auto pyCameraConfiguration = py::class_(m, "CameraConfiguration"); @@ -90,6 +91,21 @@ PYBIND11_MODULE(_libcamera, m) m.def("log_set_level", &logSetLevel); /* Classes */ + + py::enum_(pyEvent, "Type") + .value("Undefined", CameraEventType::Undefined) + .value("CameraAdded", CameraEventType::CameraAdded) + .value("CameraRemoved", CameraEventType::CameraRemoved) + .value("Disconnect", CameraEventType::Disconnect) + .value("RequestCompleted", CameraEventType::RequestCompleted) + .value("BufferCompleted", CameraEventType::BufferCompleted); + + pyEvent + .def_readonly("type", &PyCameraEvent::type_) + .def_readonly("camera", &PyCameraEvent::camera_) + .def_readonly("request", &PyCameraEvent::request_) + .def_readonly("fb", &PyCameraEvent::fb_); + pyCameraManager .def_static("singleton", []() { std::shared_ptr cm = gCameraManager.lock(); @@ -107,7 +123,13 @@ 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("get_events", &PyCameraManager::getPyEvents) + + .def_readwrite("buffer_completed_active", &PyCameraManager::bufferCompletedEventActive_); pyCamera .def_property_readonly("id", &Camera::id) @@ -131,7 +153,17 @@ PYBIND11_MODULE(_libcamera, m) auto cm = gCameraManager.lock(); ASSERT(cm); - self.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted); + self.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) { + cm->handleRequestCompleted(camera, req); + }); + + self.bufferCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req, FrameBuffer *fb) { + cm->handleBufferCompleted(camera, req, fb); + }); + + self.disconnected.connect(&self, [cm, camera=self.shared_from_this()]() { + cm->handleDisconnected(camera); + }); ControlList controlList(self.controls()); @@ -152,11 +184,20 @@ PYBIND11_MODULE(_libcamera, m) int ret = self.stop(); self.requestCompleted.disconnect(); + self.bufferCompleted.disconnect(); + self.disconnected.disconnect(); + + auto cm = gCameraManager.lock(); + ASSERT(cm); + + auto l = cm->getPyCameraEvents(self.shared_from_this()); /* \todo Should we just ignore the error? */ if (ret) throw std::system_error(-ret, std::generic_category(), "Failed to start camera"); + + return l; }) .def("__str__", [](Camera &self) {