Patch Detail
Show a patch.
GET /api/1.1/patches/27126/?format=api
{ "id": 27126, "url": "https://patchwork.libcamera.org/api/1.1/patches/27126/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27126/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260629163017.863145-47-barnabas.pocze@ideasonboard.com>", "date": "2026-06-29T16:30:09", "name": "[RFC,v1,46/54] py: Use camera buffer pool", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "66ac8a47a7e608f0e862b35132213d45f7a69462", "submitter": { "id": 216, "url": "https://patchwork.libcamera.org/api/1.1/people/216/?format=api", "name": "Barnabás Pőcze", "email": "barnabas.pocze@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27126/mbox/", "series": [ { "id": 6025, "url": "https://patchwork.libcamera.org/api/1.1/series/6025/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6025", "date": "2026-06-29T16:29:23", "name": "libcamera: Split requests and buffers", "version": 1, "mbox": "https://patchwork.libcamera.org/series/6025/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/27126/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27126/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 36E1AC3304\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 29 Jun 2026 16:31:39 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C7FE665F9B;\n\tMon, 29 Jun 2026 18:31:38 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 90E3465F6B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:30:31 +0200 (CEST)", "from pb-laptop.local (185.221.140.128.nat.pool.zt.hu\n\t[185.221.140.128])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 624BD1044\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:29:48 +0200 (CEST)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"oFjDtmbe\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782750588;\n\tbh=VXWpR7gmQO/R6CcvpP28AdLvMQoFvogtaR38wHx23MI=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=oFjDtmbeBX6oVHTzLyvET4pjZ96GsW12AFIJ1hvKWfjaoJvEisgi1rD63WBARmSBY\n\tAPCX9sPl0O51J5tw3iJznfe17FDA4aCvCW8GnmfBM5Qzx1gzp0UgwHre65nuZH2z8+\n\tcGKodFxt7IRoDvQtm3nxqyVktAnJvBiDqSO+SeVk=", "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Subject": "[RFC PATCH v1 46/54] py: Use camera buffer pool", "Date": "Mon, 29 Jun 2026 18:30:09 +0200", "Message-ID": "<20260629163017.863145-47-barnabas.pocze@ideasonboard.com>", "X-Mailer": "git-send-email 2.54.0", "In-Reply-To": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>", "References": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "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>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Port to using the camera's buffer pool.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/py/cam/cam.py | 20 +++---\n src/py/examples/simple-cam.py | 13 +++-\n src/py/examples/simple-capture.py | 20 +++---\n src/py/examples/simple-continuous-capture.py | 32 ++++++----\n src/py/libcamera/py_main.cpp | 64 ++++++++++++++------\n 5 files changed, 101 insertions(+), 48 deletions(-)", "diff": "diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\nindex 73764a8af7..1752960030 100755\n--- a/src/py/cam/cam.py\n+++ b/src/py/cam/cam.py\n@@ -205,13 +205,6 @@ class CameraContext:\n \n for buf_num in range(num_bufs):\n request = self.camera.create_request(self.idx)\n-\n- for stream in self.streams:\n- buffers = self.allocator.buffers(stream)\n- buffer = buffers[buf_num]\n-\n- request.add_buffer(stream, buffer)\n-\n requests.append(request)\n \n self.requests = requests\n@@ -223,7 +216,14 @@ class CameraContext:\n self.camera.stop()\n \n def queue_requests(self):\n+ for stream in self.streams:\n+ for buffer in self.allocator.buffers(stream):\n+ self.camera.add_buffer(stream, buffer)\n+\n for request in self.requests:\n+ for stream in self.streams:\n+ request.enable_stream(stream, True)\n+\n self.camera.queue_request(request)\n self.reqs_queued += 1\n \n@@ -305,7 +305,13 @@ class CaptureState:\n # Called from renderer when it has finished with a request\n def request_processed(self, ctx, req):\n if ctx.reqs_queued < ctx.opt_capture:\n+ for (stream, buffer) in req.buffers.items():\n+ ctx.camera.add_buffer(stream, buffer)\n+\n req.reuse()\n+ for stream in ctx.streams:\n+ req.enable_stream(stream, True)\n+\n ctx.camera.queue_request(req)\n ctx.reqs_queued += 1\n \ndiff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py\nindex 1cd1019da9..f2e28a94ef 100755\n--- a/src/py/examples/simple-cam.py\n+++ b/src/py/examples/simple-cam.py\n@@ -73,7 +73,12 @@ def process_request(request):\n # must be mapped by the application\n \n # Re-queue the Request to the camera.\n+ for (stream, buffer) in request.buffers.items():\n+ camera.add_buffer(stream, buffer)\n+\n request.reuse()\n+ request.enable_stream(stream, True)\n+\n camera.queue_request(request)\n \n \n@@ -285,9 +290,6 @@ def main():\n for i in range(len(buffers)):\n request = camera.create_request()\n \n- buffer = buffers[i]\n- request.add_buffer(stream, buffer)\n-\n # Controls can be added to a request on a per frame basis.\n request.set_control(libcam.controls.Brightness, 0.5)\n \n@@ -311,7 +313,12 @@ def main():\n # be waited upon using e.g. Python's selectors.\n \n camera.start()\n+\n+ for buffer in buffers:\n+ camera.add_buffer(stream, buffer)\n+\n for request in requests:\n+ request.enable_stream(stream, True)\n camera.queue_request(request)\n \n sel = selectors.DefaultSelector()\ndiff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py\nindex 4b85408fac..00657f78ad 100755\n--- a/src/py/examples/simple-capture.py\n+++ b/src/py/examples/simple-capture.py\n@@ -69,20 +69,15 @@ def main():\n \n allocator = libcam.FrameBufferAllocator(cam)\n ret = allocator.allocate(stream)\n- assert ret > 0\n-\n- num_bufs = len(allocator.buffers(stream))\n+ buffers = allocator.buffers(stream)\n+ assert ret > 0 and len(buffers) > 0\n \n # Create the requests and assign a buffer for each request\n \n reqs = []\n- for i in range(num_bufs):\n+ for i in range(len(buffers)):\n # Use the buffer index as the cookie\n req = cam.create_request(i)\n-\n- buffer = allocator.buffers(stream)[i]\n- req.add_buffer(stream, buffer)\n-\n reqs.append(req)\n \n # Start the camera\n@@ -96,7 +91,11 @@ def main():\n \n # Queue the requests to the camera\n \n+ for buffer in buffers:\n+ cam.add_buffer(stream, buffer)\n+\n for req in reqs:\n+ req.enable_stream(stream, True)\n cam.queue_request(req)\n frames_queued += 1\n \n@@ -144,7 +143,12 @@ def main():\n # We could create a totally new Request, but it is more efficient\n # to reuse the existing one that we just received.\n if frames_queued < TOTAL_FRAMES:\n+ for (stream, buffer) in req.buffers.items():\n+ cam.add_buffer(stream, buffer)\n+\n req.reuse()\n+ req.enable_stream(stream, True)\n+\n cam.queue_request(req)\n frames_queued += 1\n \ndiff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py\nindex 5ae841519e..96cb79b43f 100755\n--- a/src/py/examples/simple-continuous-capture.py\n+++ b/src/py/examples/simple-continuous-capture.py\n@@ -19,8 +19,10 @@ import sys\n class CameraCaptureContext:\n idx: int\n cam: libcam.Camera\n+ cam_config: libcam.CameraConfiguration\n reqs: list[libcam.Request]\n mfbs: dict[libcam.FrameBuffer, libcamera.utils.MappedFrameBuffer]\n+ buffers: list[libcam.FrameBuffer]\n \n def __init__(self, cam, idx):\n self.idx = idx\n@@ -32,11 +34,11 @@ class CameraCaptureContext:\n \n # Configure the camera\n \n- cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder])\n+ self.cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder])\n \n- stream_config = cam_config.at(0)\n+ stream_config = self.cam_config.at(0)\n \n- cam.configure(cam_config)\n+ cam.configure(self.cam_config)\n \n stream = stream_config.stream\n \n@@ -44,24 +46,19 @@ class CameraCaptureContext:\n \n allocator = libcam.FrameBufferAllocator(cam)\n ret = allocator.allocate(stream)\n- assert ret > 0\n+ self.buffers = allocator.buffers(stream)\n+ assert ret > 0 and len(self.buffers) > 0\n \n- num_bufs = len(allocator.buffers(stream))\n-\n- print(f'cam{idx} ({cam.id}): capturing {num_bufs} buffers with {stream_config}')\n+ print(f'cam{idx} ({cam.id}): capturing {len(self.buffers)} buffers with {stream_config}')\n \n # Create the requests and assign a buffer for each request\n \n self.reqs = []\n self.mfbs = {}\n \n- for i in range(num_bufs):\n+ for buffer in self.buffers:\n # Use the camera index as the \"cookie\"\n req = cam.create_request(idx)\n-\n- buffer = allocator.buffers(stream)[i]\n- req.add_buffer(stream, buffer)\n-\n self.reqs.append(req)\n \n # Save a mmapped buffer so we can calculate the CRC later\n@@ -127,7 +124,12 @@ class CaptureContext:\n # a new Request, we re-use the old one. We need to call req.reuse()\n # to re-initialize the Request before queuing.\n \n+ for (stream, buffer) in req.buffers.items():\n+ cam_ctx.cam.add_buffer(stream, buffer)\n+\n req.reuse()\n+ req.enable_stream(stream, True)\n+\n cam_ctx.cam.queue_request(req)\n \n def handle_key_event(self):\n@@ -139,7 +141,13 @@ class CaptureContext:\n # Queue the requests to the camera\n \n for cam_ctx in self.camera_contexts:\n+ stream = cam_ctx.cam_config.at(0).stream\n+\n+ for buffer in cam_ctx.buffers:\n+ cam_ctx.cam.add_buffer(stream, buffer)\n+\n for req in cam_ctx.reqs:\n+ req.enable_stream(stream, True)\n cam_ctx.cam.queue_request(req)\n \n # Use Selector to wait for events from the camera and from the keyboard\ndiff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp\nindex 3b101f9961..26b348ef0e 100644\n--- a/src/py/libcamera/py_main.cpp\n+++ b/src/py/libcamera/py_main.cpp\n@@ -110,6 +110,16 @@ PYBIND11_MODULE(_libcamera, m)\n \t * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings\n \t */\n \n+\tstruct RequestDeleter : std::default_delete<Request> {\n+\t\tvoid operator()(Request *request)\n+\t\t{\n+\t\t\tfor (const auto &[stream, buffer] : request->buffers())\n+\t\t\t\tpy::cast(buffer).dec_ref();\n+\n+\t\t\tstd::default_delete<Request>::operator()(request);\n+\t\t}\n+\t};\n+\n \tauto pyCameraManager = py::class_<PyCameraManager, std::shared_ptr<PyCameraManager>>(m, \"CameraManager\");\n \tauto pyCamera = py::class_<Camera, PyCameraHolder>(m, \"Camera\");\n \tauto pySensorConfiguration = py::class_<SensorConfiguration>(m, \"SensorConfiguration\");\n@@ -123,7 +133,7 @@ PYBIND11_MODULE(_libcamera, m)\n \tauto pyStream = py::class_<Stream>(m, \"Stream\");\n \tauto pyControlId = py::class_<ControlId>(m, \"ControlId\");\n \tauto pyControlInfo = py::class_<ControlInfo>(m, \"ControlInfo\");\n-\tauto pyRequest = py::class_<Request>(m, \"Request\");\n+\tauto pyRequest = py::class_<Request, std::unique_ptr<Request, RequestDeleter>>(m, \"Request\");\n \tauto pyRequestStatus = py::enum_<Request::Status>(pyRequest, \"Status\");\n \tauto pyRequestReuse = py::enum_<Request::ReuseFlag>(pyRequest, \"Reuse\");\n \tauto pyFrameMetadata = py::class_<FrameMetadata>(m, \"FrameMetadata\");\n@@ -195,10 +205,21 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t}, py::arg(\"controls\") = std::unordered_map<const ControlId *, py::object>())\n \n \t\t.def(\"stop\", [](Camera &self) {\n+\t\t\tstd::vector<FrameBuffer *> queued;\n+\n+\t\t\tself.bufferCompleted.connect(&queued, [&](Request *request, const Stream *, FrameBuffer *buffer) {\n+\t\t\t\tif (!request)\n+\t\t\t\t\tqueued.push_back(buffer);\n+\t\t\t});\n+\n \t\t\tint ret = self.stop();\n \n+\t\t\tself.bufferCompleted.disconnect(&queued);\n \t\t\tself.requestCompleted.disconnect();\n \n+\t\t\tfor (auto *buffer : queued)\n+\t\t\t\tpy::cast(buffer).dec_ref();\n+\n \t\t\tif (ret)\n \t\t\t\tthrow std::system_error(-ret, std::generic_category(),\n \t\t\t\t\t\t\t\"Failed to stop camera\");\n@@ -221,7 +242,7 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t})\n \n \t\t.def(\"create_request\", [](Camera &self, uint64_t cookie) {\n-\t\t\tstd::unique_ptr<Request> req = self.createRequest(cookie);\n+\t\t\tstd::unique_ptr<Request, RequestDeleter> req(self.createRequest(cookie).release());\n \t\t\tif (!req)\n \t\t\t\tthrow std::system_error(ENOMEM, std::generic_category(),\n \t\t\t\t\t\t\t\"Failed to create request\");\n@@ -246,6 +267,20 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\t}\n \t\t})\n \n+\t\t/* \\todo Fence is not supported */\n+\t\t.def(\"add_buffer\", [](Camera &self, const Stream *stream, FrameBuffer *buffer) {\n+\t\t\tpy::object py_buf = py::cast(buffer);\n+\n+\t\t\tpy_buf.inc_ref();\n+\n+\t\t\tint ret = self.addBuffer(stream, buffer);\n+\t\t\tif (ret) {\n+\t\t\t\tpy_buf.dec_ref();\n+\t\t\t\tthrow std::system_error(-ret, std::generic_category(),\n+\t\t\t\t\t\t\t\"Failed to add buffer\");\n+\t\t\t}\n+\t\t})\n+\n \t\t.def_property_readonly(\"streams\", [](Camera &self) {\n \t\t\tpy::set set;\n \t\t\tfor (auto &s : self.streams()) {\n@@ -457,13 +492,6 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t});\n \n \tpyRequest\n-\t\t/* \\todo Fence is not supported, so we cannot expose addBuffer() directly */\n-\t\t.def(\"add_buffer\", [](Request &self, const Stream *stream, FrameBuffer *buffer) {\n-\t\t\tint ret = self.addBuffer(stream, buffer);\n-\t\t\tif (ret)\n-\t\t\t\tthrow std::system_error(-ret, std::generic_category(),\n-\t\t\t\t\t\t\t\"Failed to add buffer\");\n-\t\t}, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */\n \t\t.def_property_readonly(\"status\", &Request::status)\n \t\t.def_property_readonly(\"buffers\", &Request::buffers)\n \t\t.def_property_readonly(\"cookie\", &Request::cookie)\n@@ -485,11 +513,15 @@ PYBIND11_MODULE(_libcamera, m)\n \n \t\t\treturn ret;\n \t\t})\n-\t\t/*\n-\t\t * \\todo As we add a keep_alive to the fb in addBuffers(), we\n-\t\t * can only allow reuse with ReuseBuffers.\n-\t\t */\n-\t\t.def(\"reuse\", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); })\n+\t\t.def(\"reuse\", [](Request &self) {\n+\t\t\tfor (const auto &[stream, buffer] : self.buffers())\n+\t\t\t\tpy::cast(buffer).dec_ref();\n+\n+\t\t\tself.reuse();\n+\t\t})\n+\t\t.def(\"enable_stream\", [](Request &self, const Stream *stream, bool enabled) {\n+\t\t\tself.enableStream(stream, enabled);\n+\t\t})\n \t\t.def(\"__str__\", &Request::toString);\n \n \tpyRequestStatus\n@@ -497,10 +529,6 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t.value(\"Complete\", Request::RequestComplete)\n \t\t.value(\"Cancelled\", Request::RequestCancelled);\n \n-\tpyRequestReuse\n-\t\t.value(\"Default\", Request::ReuseFlag::Default)\n-\t\t.value(\"ReuseBuffers\", Request::ReuseFlag::ReuseBuffers);\n-\n \tpyFrameMetadata\n \t\t.def_readonly(\"status\", &FrameMetadata::status)\n \t\t.def_readonly(\"sequence\", &FrameMetadata::sequence)\n", "prefixes": [ "RFC", "v1", "46/54" ] }