[libcamera-devel] [PATCH v7 13/13] py: implement MappedFrameBuffer

Tomi Valkeinen tomi.valkeinen at ideasonboard.com
Thu May 5 12:41:04 CEST 2022


Instead of just exposing plain mmap via fb.mmap(planenum), implement a
MappedFrameBuffer class, similar to C++'s MappedFrameBuffer.
MappedFrameBuffer mmaps the underlying filedescriptors and provides
Python memoryviews for each plane.

As an example, to save a Framebuffer to a file:

with fb.mmap() as mfb:
	with open(filename, "wb") as f:
		for p in mfb.planes:
			f.write(p)

The objects in mfb.planes are memoryviews that cover only the plane in
question.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
---
 src/py/cam/cam.py            | 11 +++---
 src/py/cam/cam_qt.py         |  6 +--
 src/py/libcamera/__init__.py | 72 +++++++++++++++++++++++++++++++++++-
 src/py/libcamera/pymain.cpp  |  7 ++++
 4 files changed, 86 insertions(+), 10 deletions(-)

diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
index 4efa6459..c0ebb186 100755
--- a/src/py/cam/cam.py
+++ b/src/py/cam/cam.py
@@ -329,9 +329,9 @@ def request_handler(state, ctx, req):
 
         crcs = []
         if ctx["opt-crc"]:
-            with fb.mmap(0) as b:
-                crc = binascii.crc32(b)
-                crcs.append(crc)
+            with fb.mmap() as mfb:
+                plane_crcs = [binascii.crc32(p) for p in mfb.planes]
+                crcs.append(plane_crcs)
 
         meta = fb.metadata
 
@@ -347,10 +347,11 @@ def request_handler(state, ctx, req):
                 print(f"\t{ctrl} = {val}")
 
         if ctx["opt-save-frames"]:
-            with fb.mmap(0) as b:
+            with fb.mmap() as mfb:
                 filename = "frame-{}-{}-{}.data".format(ctx["id"], stream_name, ctx["reqs-completed"])
                 with open(filename, "wb") as f:
-                    f.write(b)
+                    for p in mfb.planes:
+                        f.write(p)
 
     state["renderer"].request_handler(ctx, req)
 
diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
index 30fb7a1d..d394987b 100644
--- a/src/py/cam/cam_qt.py
+++ b/src/py/cam/cam_qt.py
@@ -324,17 +324,17 @@ class MainWindow(QtWidgets.QWidget):
         controlsLayout.addStretch()
 
     def buf_to_qpixmap(self, stream, fb):
-        with fb.mmap(0) as b:
+        with fb.mmap() as mfb:
             cfg = stream.configuration
             w, h = cfg.size
             pitch = cfg.stride
 
             if cfg.pixelFormat == "MJPEG":
-                img = Image.open(BytesIO(b))
+                img = Image.open(BytesIO(mfb.planes[0]))
                 qim = ImageQt(img).copy()
                 pix = QtGui.QPixmap.fromImage(qim)
             else:
-                data = np.array(b, dtype=np.uint8)
+                data = np.array(mfb.planes[0], dtype=np.uint8)
                 rgb = to_rgb(cfg.pixelFormat, cfg.size, data)
 
                 if rgb is None:
diff --git a/src/py/libcamera/__init__.py b/src/py/libcamera/__init__.py
index cd7512a2..caf06af7 100644
--- a/src/py/libcamera/__init__.py
+++ b/src/py/libcamera/__init__.py
@@ -1,12 +1,80 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 # Copyright (C) 2021, Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
 
+from os import lseek, SEEK_END
 from ._libcamera import *
 import mmap
 
 
-def __FrameBuffer__mmap(self, plane):
-    return mmap.mmap(self.fd(plane), self.length(plane), mmap.MAP_SHARED, mmap.PROT_READ)
+def __FrameBuffer__mmap(self):
+    return MappedFrameBuffer(self)
 
 
 FrameBuffer.mmap = __FrameBuffer__mmap
+
+
+class MappedFrameBuffer:
+    def __init__(self, fb):
+        self.fb = fb
+
+        # Collect information about the buffers
+
+        bufinfos = {}
+
+        for i in range(fb.num_planes):
+            fd = fb.fd(i)
+
+            if fd not in bufinfos:
+                buflen = lseek(fd, 0, SEEK_END)
+                bufinfos[fd] = {"maplen": 0, "buflen": buflen}
+            else:
+                buflen = bufinfos[fd]["buflen"]
+
+            if fb.offset(i) > buflen or fb.offset(i) + fb.length(i) > buflen:
+                raise RuntimeError(f"plane is out of buffer: buffer length={buflen}, "
+                                   f"plane offset={fb.offset(i)}, plane length={fb.length(i)}")
+
+            bufinfos[fd]["maplen"] = max(bufinfos[fd]["maplen"], fb.offset(i) + fb.length(i))
+
+        # mmap the buffers
+
+        maps = []
+
+        for fd, info in bufinfos.items():
+            map = mmap.mmap(fd, info["maplen"], mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
+            info["map"] = map
+            maps.append(map)
+
+        self.maps = tuple(maps)
+
+        # Create memoryviews for the planes
+
+        planes = []
+
+        for i in range(fb.num_planes):
+            fd = fb.fd(i)
+            info = bufinfos[fd]
+
+            mv = memoryview(info["map"])
+
+            start = fb.offset(i)
+            end = fb.offset(i) + fb.length(i)
+
+            mv = mv[start:end]
+
+            planes.append(mv)
+
+        self.planes = tuple(planes)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_traceback):
+        for p in self.planes:
+            p.release()
+
+        for mm in self.maps:
+            mm.close()
+
+    def planes(self):
+        return self.planes
diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
index b9b52f6b..73d29479 100644
--- a/src/py/libcamera/pymain.cpp
+++ b/src/py/libcamera/pymain.cpp
@@ -439,6 +439,9 @@ PYBIND11_MODULE(_libcamera, m)
 			return new FrameBuffer(v, cookie);
 		}))
 		.def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal)
+		.def_property_readonly("num_planes", [](const FrameBuffer &self) {
+			return self.planes().size();
+		})
 		.def("length", [](FrameBuffer &self, uint32_t idx) {
 			const FrameBuffer::Plane &plane = self.planes()[idx];
 			return plane.length;
@@ -447,6 +450,10 @@ PYBIND11_MODULE(_libcamera, m)
 			const FrameBuffer::Plane &plane = self.planes()[idx];
 			return plane.fd.get();
 		})
+		.def("offset", [](FrameBuffer &self, uint32_t idx) {
+			const FrameBuffer::Plane &plane = self.planes()[idx];
+			return plane.offset;
+		})
 		.def_property("cookie", &FrameBuffer::cookie, &FrameBuffer::setCookie);
 
 	pyStream
-- 
2.34.1



More information about the libcamera-devel mailing list