Show a patch.

GET /api/1.1/patches/27126/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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"
    ]
}