[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