{"id":15796,"url":"https://patchwork.libcamera.org/api/patches/15796/?format=json","web_url":"https://patchwork.libcamera.org/patch/15796/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20220505104104.70841-14-tomi.valkeinen@ideasonboard.com>","date":"2022-05-05T10:41:04","name":"[libcamera-devel,v7,13/13] py: implement MappedFrameBuffer","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"c58e2834beb7a5ec187330bb7b61159cca820a7d","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/?format=json","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/15796/mbox/","series":[{"id":3093,"url":"https://patchwork.libcamera.org/api/series/3093/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=3093","date":"2022-05-05T10:40:51","name":"Python bindings","version":7,"mbox":"https://patchwork.libcamera.org/series/3093/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/15796/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/15796/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 D089CC3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  5 May 2022 10:41:39 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 73A4E6564A;\n\tThu,  5 May 2022 12:41:39 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9A2F865664\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  5 May 2022 12:41:32 +0200 (CEST)","from deskari.lan (91-156-85-209.elisa-laajakaista.fi\n\t[91.156.85.209])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0A80D4A8;\n\tThu,  5 May 2022 12:41:31 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1651747299;\n\tbh=pmogevaeNcSjdHQ7Hgy5H641piE912B+uxhkbOw+v34=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=YnTP/T/aXaDhBGNbZWPFBUAgwsr0z5x23AsQEkRYXYnhGPdGNHTz169d43PDcl2vj\n\t1gaCrMZChuwBJMYY7d0AX/S13aTqRlIMChACoG8G1pxWciHxoga+g/Nspbydl+88ec\n\tX94UiuFgeSQl9L5LGXFkTMICGLagmIENDTRRnKjzMa7uVc2jcJjMENd8nPzVaZpYOr\n\t+WGxGd56cuRkP7hjBTTuEsUFf7F/a6794v1s2y5k0/XTIqIldArzec7UJw+g955zes\n\tckzteEx0WVI67iEM4Q5wFWEEHAvW2xabybG0jDMOXkPmc6TD0w1q9rhmpKfZiLXAw4\n\t5b+FMNuNsmo+A==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1651747292;\n\tbh=pmogevaeNcSjdHQ7Hgy5H641piE912B+uxhkbOw+v34=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=R47LJtFqKkaxe4CnB8vC67c8c6HTABikZ2J9EKEP+cqF/YAeBb1FrjHl740TgH4Sj\n\tgdmxtRtNxDMaMnE7Be6I62Qx4UpRWy2v5KGLPCs5dCv56ZLYMDl+DOqGZqjrLGBE8/\n\t1NQxJRm4a4lAJNwRXOqWfgKSUsBtMVoCP9xa0BTE="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"R47LJtFq\"; dkim-atps=neutral","To":"libcamera-devel@lists.libcamera.org,\n\tDavid Plowman <david.plowman@raspberrypi.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tJacopo Mondi <jacopo@jmondi.org>","Date":"Thu,  5 May 2022 13:41:04 +0300","Message-Id":"<20220505104104.70841-14-tomi.valkeinen@ideasonboard.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20220505104104.70841-1-tomi.valkeinen@ideasonboard.com>","References":"<20220505104104.70841-1-tomi.valkeinen@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v7 13/13] py: implement MappedFrameBuffer","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>","From":"Tomi Valkeinen via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Instead of just exposing plain mmap via fb.mmap(planenum), implement a\nMappedFrameBuffer class, similar to C++'s MappedFrameBuffer.\nMappedFrameBuffer mmaps the underlying filedescriptors and provides\nPython memoryviews for each plane.\n\nAs an example, to save a Framebuffer to a file:\n\nwith fb.mmap() as mfb:\n\twith open(filename, \"wb\") as f:\n\t\tfor p in mfb.planes:\n\t\t\tf.write(p)\n\nThe objects in mfb.planes are memoryviews that cover only the plane in\nquestion.\n\nSigned-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n---\n src/py/cam/cam.py            | 11 +++---\n src/py/cam/cam_qt.py         |  6 +--\n src/py/libcamera/__init__.py | 72 +++++++++++++++++++++++++++++++++++-\n src/py/libcamera/pymain.cpp  |  7 ++++\n 4 files changed, 86 insertions(+), 10 deletions(-)","diff":"diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\nindex 4efa6459..c0ebb186 100755\n--- a/src/py/cam/cam.py\n+++ b/src/py/cam/cam.py\n@@ -329,9 +329,9 @@ def request_handler(state, ctx, req):\n \n         crcs = []\n         if ctx[\"opt-crc\"]:\n-            with fb.mmap(0) as b:\n-                crc = binascii.crc32(b)\n-                crcs.append(crc)\n+            with fb.mmap() as mfb:\n+                plane_crcs = [binascii.crc32(p) for p in mfb.planes]\n+                crcs.append(plane_crcs)\n \n         meta = fb.metadata\n \n@@ -347,10 +347,11 @@ def request_handler(state, ctx, req):\n                 print(f\"\\t{ctrl} = {val}\")\n \n         if ctx[\"opt-save-frames\"]:\n-            with fb.mmap(0) as b:\n+            with fb.mmap() as mfb:\n                 filename = \"frame-{}-{}-{}.data\".format(ctx[\"id\"], stream_name, ctx[\"reqs-completed\"])\n                 with open(filename, \"wb\") as f:\n-                    f.write(b)\n+                    for p in mfb.planes:\n+                        f.write(p)\n \n     state[\"renderer\"].request_handler(ctx, req)\n \ndiff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py\nindex 30fb7a1d..d394987b 100644\n--- a/src/py/cam/cam_qt.py\n+++ b/src/py/cam/cam_qt.py\n@@ -324,17 +324,17 @@ class MainWindow(QtWidgets.QWidget):\n         controlsLayout.addStretch()\n \n     def buf_to_qpixmap(self, stream, fb):\n-        with fb.mmap(0) as b:\n+        with fb.mmap() as mfb:\n             cfg = stream.configuration\n             w, h = cfg.size\n             pitch = cfg.stride\n \n             if cfg.pixelFormat == \"MJPEG\":\n-                img = Image.open(BytesIO(b))\n+                img = Image.open(BytesIO(mfb.planes[0]))\n                 qim = ImageQt(img).copy()\n                 pix = QtGui.QPixmap.fromImage(qim)\n             else:\n-                data = np.array(b, dtype=np.uint8)\n+                data = np.array(mfb.planes[0], dtype=np.uint8)\n                 rgb = to_rgb(cfg.pixelFormat, cfg.size, data)\n \n                 if rgb is None:\ndiff --git a/src/py/libcamera/__init__.py b/src/py/libcamera/__init__.py\nindex cd7512a2..caf06af7 100644\n--- a/src/py/libcamera/__init__.py\n+++ b/src/py/libcamera/__init__.py\n@@ -1,12 +1,80 @@\n # SPDX-License-Identifier: LGPL-2.1-or-later\n # Copyright (C) 2021, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n \n+from os import lseek, SEEK_END\n from ._libcamera import *\n import mmap\n \n \n-def __FrameBuffer__mmap(self, plane):\n-    return mmap.mmap(self.fd(plane), self.length(plane), mmap.MAP_SHARED, mmap.PROT_READ)\n+def __FrameBuffer__mmap(self):\n+    return MappedFrameBuffer(self)\n \n \n FrameBuffer.mmap = __FrameBuffer__mmap\n+\n+\n+class MappedFrameBuffer:\n+    def __init__(self, fb):\n+        self.fb = fb\n+\n+        # Collect information about the buffers\n+\n+        bufinfos = {}\n+\n+        for i in range(fb.num_planes):\n+            fd = fb.fd(i)\n+\n+            if fd not in bufinfos:\n+                buflen = lseek(fd, 0, SEEK_END)\n+                bufinfos[fd] = {\"maplen\": 0, \"buflen\": buflen}\n+            else:\n+                buflen = bufinfos[fd][\"buflen\"]\n+\n+            if fb.offset(i) > buflen or fb.offset(i) + fb.length(i) > buflen:\n+                raise RuntimeError(f\"plane is out of buffer: buffer length={buflen}, \"\n+                                   f\"plane offset={fb.offset(i)}, plane length={fb.length(i)}\")\n+\n+            bufinfos[fd][\"maplen\"] = max(bufinfos[fd][\"maplen\"], fb.offset(i) + fb.length(i))\n+\n+        # mmap the buffers\n+\n+        maps = []\n+\n+        for fd, info in bufinfos.items():\n+            map = mmap.mmap(fd, info[\"maplen\"], mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)\n+            info[\"map\"] = map\n+            maps.append(map)\n+\n+        self.maps = tuple(maps)\n+\n+        # Create memoryviews for the planes\n+\n+        planes = []\n+\n+        for i in range(fb.num_planes):\n+            fd = fb.fd(i)\n+            info = bufinfos[fd]\n+\n+            mv = memoryview(info[\"map\"])\n+\n+            start = fb.offset(i)\n+            end = fb.offset(i) + fb.length(i)\n+\n+            mv = mv[start:end]\n+\n+            planes.append(mv)\n+\n+        self.planes = tuple(planes)\n+\n+    def __enter__(self):\n+        return self\n+\n+    def __exit__(self, exc_type, exc_value, exc_traceback):\n+        for p in self.planes:\n+            p.release()\n+\n+        for mm in self.maps:\n+            mm.close()\n+\n+    def planes(self):\n+        return self.planes\ndiff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp\nindex b9b52f6b..73d29479 100644\n--- a/src/py/libcamera/pymain.cpp\n+++ b/src/py/libcamera/pymain.cpp\n@@ -439,6 +439,9 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\treturn new FrameBuffer(v, cookie);\n \t\t}))\n \t\t.def_property_readonly(\"metadata\", &FrameBuffer::metadata, py::return_value_policy::reference_internal)\n+\t\t.def_property_readonly(\"num_planes\", [](const FrameBuffer &self) {\n+\t\t\treturn self.planes().size();\n+\t\t})\n \t\t.def(\"length\", [](FrameBuffer &self, uint32_t idx) {\n \t\t\tconst FrameBuffer::Plane &plane = self.planes()[idx];\n \t\t\treturn plane.length;\n@@ -447,6 +450,10 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\tconst FrameBuffer::Plane &plane = self.planes()[idx];\n \t\t\treturn plane.fd.get();\n \t\t})\n+\t\t.def(\"offset\", [](FrameBuffer &self, uint32_t idx) {\n+\t\t\tconst FrameBuffer::Plane &plane = self.planes()[idx];\n+\t\t\treturn plane.offset;\n+\t\t})\n \t\t.def_property(\"cookie\", &FrameBuffer::cookie, &FrameBuffer::setCookie);\n \n \tpyStream\n","prefixes":["libcamera-devel","v7","13/13"]}