[libcamera-devel] [PATCH v3 11/17] py: New event handling

Tomi Valkeinen tomi.valkeinen at ideasonboard.com
Fri Jul 1 10:45:15 CEST 2022


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 <tomi.valkeinen at ideasonboard.com>
---
 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 <algorithm>
 #include <errno.h>
 #include <sys/eventfd.h>
 #include <system_error>
@@ -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<py::object> PyCameraManager::getReadyRequests()
 {
 	int ret = readFd();
@@ -70,21 +121,125 @@ std::vector<py::object> PyCameraManager::getReadyRequests()
 
 	std::vector<py::object> 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<PyCameraEvent> 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<CameraEvent> events = getEvents();
+
+	LOG(Python, Debug) << "Get " << events.size() << " events";
+
+	std::vector<PyCameraEvent> 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> &camera)
+{
+	return event.camera_ == camera &&
+	       (event.type_ == CameraEventType::RequestCompleted ||
+		event.type_ == CameraEventType::BufferCompleted ||
+		event.type_ == CameraEventType::Disconnect);
+}
+
+std::vector<PyCameraEvent> PyCameraManager::getPyCameraEvents(std::shared_ptr<Camera> camera)
+{
+	MutexLocker guard(eventsMutex_);
+
+	std::vector<PyCameraEvent> 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<Camera> 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<Camera> cam, Request *req)
+{
+	CameraEvent ev(CameraEventType::RequestCompleted, cam);
+	ev.request_ = req;
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleDisconnected(std::shared_ptr<Camera> cam)
+{
+	CameraEvent ev(CameraEventType::Disconnect, cam);
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleCameraAdded(std::shared_ptr<Camera> cam)
+{
+	CameraEvent ev(CameraEventType::CameraAdded, cam);
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleCameraRemoved(std::shared_ptr<Camera> 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<Request *> PyCameraManager::getCompletedRequests()
+std::vector<CameraEvent> PyCameraManager::getEvents()
 {
-	std::vector<Request *> v;
-	MutexLocker guard(completedRequestsMutex_);
-	swap(v, completedRequests_);
+	std::vector<CameraEvent> 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> camera)
+		: type_(type), camera_(camera)
+	{
+	}
+
+	CameraEventType type_ = CameraEventType::Undefined;
+
+	std::shared_ptr<Camera> 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<pybind11::object> getReadyRequests();
+	std::vector<pybind11::object> getReadyRequests(); /* DEPRECATED */
+	std::vector<PyCameraEvent> getPyEvents();
+	std::vector<PyCameraEvent> getPyCameraEvents(std::shared_ptr<Camera> camera);
 
-	void handleRequestCompleted(Request *req);
+	void handleBufferCompleted(std::shared_ptr<Camera> cam, Request *req, FrameBuffer *fb);
+	void handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req);
+	void handleDisconnected(std::shared_ptr<Camera> cam);
+	void handleCameraAdded(std::shared_ptr<Camera> cam);
+	void handleCameraRemoved(std::shared_ptr<Camera> cam);
 
+	bool bufferCompletedEventActive_ = false;
 private:
 	std::unique_ptr<CameraManager> cameraManager_;
 
 	UniqueFD eventFd_;
-	libcamera::Mutex completedRequestsMutex_;
-	std::vector<Request *> completedRequests_
-		LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);
+	libcamera::Mutex eventsMutex_;
+	std::vector<CameraEvent> events_
+		LIBCAMERA_TSA_GUARDED_BY(eventsMutex_);
 
 	void writeFd();
 	int readFd();
-	void pushRequest(Request *req);
-	std::vector<Request *> getCompletedRequests();
+	void pushEvent(const CameraEvent &ev);
+	std::vector<CameraEvent> 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_<PyCameraEvent>(m, "Event");
 	auto pyCameraManager = py::class_<PyCameraManager>(m, "CameraManager");
 	auto pyCamera = py::class_<Camera>(m, "Camera");
 	auto pyCameraConfiguration = py::class_<CameraConfiguration>(m, "CameraConfiguration");
@@ -90,6 +91,21 @@ PYBIND11_MODULE(_libcamera, m)
 	m.def("log_set_level", &logSetLevel);
 
 	/* Classes */
+
+	py::enum_<CameraEventType>(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<PyCameraManager> 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) {
-- 
2.34.1



More information about the libcamera-devel mailing list