@@ -205,13 +205,6 @@ class CameraContext:
for buf_num in range(num_bufs):
request = self.camera.create_request(self.idx)
-
- for stream in self.streams:
- buffers = self.allocator.buffers(stream)
- buffer = buffers[buf_num]
-
- request.add_buffer(stream, buffer)
-
requests.append(request)
self.requests = requests
@@ -223,7 +216,14 @@ class CameraContext:
self.camera.stop()
def queue_requests(self):
+ for stream in self.streams:
+ for buffer in self.allocator.buffers(stream):
+ self.camera.add_buffer(stream, buffer)
+
for request in self.requests:
+ for stream in self.streams:
+ request.enable_stream(stream, True)
+
self.camera.queue_request(request)
self.reqs_queued += 1
@@ -305,7 +305,13 @@ class CaptureState:
# Called from renderer when it has finished with a request
def request_processed(self, ctx, req):
if ctx.reqs_queued < ctx.opt_capture:
+ for (stream, buffer) in req.buffers.items():
+ ctx.camera.add_buffer(stream, buffer)
+
req.reuse()
+ for stream in ctx.streams:
+ req.enable_stream(stream, True)
+
ctx.camera.queue_request(req)
ctx.reqs_queued += 1
@@ -73,7 +73,12 @@ def process_request(request):
# must be mapped by the application
# Re-queue the Request to the camera.
+ for (stream, buffer) in request.buffers.items():
+ camera.add_buffer(stream, buffer)
+
request.reuse()
+ request.enable_stream(stream, True)
+
camera.queue_request(request)
@@ -285,9 +290,6 @@ def main():
for i in range(len(buffers)):
request = camera.create_request()
- buffer = buffers[i]
- request.add_buffer(stream, buffer)
-
# Controls can be added to a request on a per frame basis.
request.set_control(libcam.controls.Brightness, 0.5)
@@ -311,7 +313,12 @@ def main():
# be waited upon using e.g. Python's selectors.
camera.start()
+
+ for buffer in buffers:
+ camera.add_buffer(stream, buffer)
+
for request in requests:
+ request.enable_stream(stream, True)
camera.queue_request(request)
sel = selectors.DefaultSelector()
@@ -69,20 +69,15 @@ def main():
allocator = libcam.FrameBufferAllocator(cam)
ret = allocator.allocate(stream)
- assert ret > 0
-
- num_bufs = len(allocator.buffers(stream))
+ buffers = allocator.buffers(stream)
+ assert ret > 0 and len(buffers) > 0
# Create the requests and assign a buffer for each request
reqs = []
- for i in range(num_bufs):
+ for i in range(len(buffers)):
# Use the buffer index as the cookie
req = cam.create_request(i)
-
- buffer = allocator.buffers(stream)[i]
- req.add_buffer(stream, buffer)
-
reqs.append(req)
# Start the camera
@@ -96,7 +91,11 @@ def main():
# Queue the requests to the camera
+ for buffer in buffers:
+ cam.add_buffer(stream, buffer)
+
for req in reqs:
+ req.enable_stream(stream, True)
cam.queue_request(req)
frames_queued += 1
@@ -144,7 +143,12 @@ def main():
# We could create a totally new Request, but it is more efficient
# to reuse the existing one that we just received.
if frames_queued < TOTAL_FRAMES:
+ for (stream, buffer) in req.buffers.items():
+ cam.add_buffer(stream, buffer)
+
req.reuse()
+ req.enable_stream(stream, True)
+
cam.queue_request(req)
frames_queued += 1
@@ -19,8 +19,10 @@ import sys
class CameraCaptureContext:
idx: int
cam: libcam.Camera
+ cam_config: libcam.CameraConfiguration
reqs: list[libcam.Request]
mfbs: dict[libcam.FrameBuffer, libcamera.utils.MappedFrameBuffer]
+ buffers: list[libcam.FrameBuffer]
def __init__(self, cam, idx):
self.idx = idx
@@ -32,11 +34,11 @@ class CameraCaptureContext:
# Configure the camera
- cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder])
+ self.cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder])
- stream_config = cam_config.at(0)
+ stream_config = self.cam_config.at(0)
- cam.configure(cam_config)
+ cam.configure(self.cam_config)
stream = stream_config.stream
@@ -44,24 +46,19 @@ class CameraCaptureContext:
allocator = libcam.FrameBufferAllocator(cam)
ret = allocator.allocate(stream)
- assert ret > 0
+ self.buffers = allocator.buffers(stream)
+ assert ret > 0 and len(self.buffers) > 0
- num_bufs = len(allocator.buffers(stream))
-
- print(f'cam{idx} ({cam.id}): capturing {num_bufs} buffers with {stream_config}')
+ print(f'cam{idx} ({cam.id}): capturing {len(self.buffers)} buffers with {stream_config}')
# Create the requests and assign a buffer for each request
self.reqs = []
self.mfbs = {}
- for i in range(num_bufs):
+ for buffer in self.buffers:
# Use the camera index as the "cookie"
req = cam.create_request(idx)
-
- buffer = allocator.buffers(stream)[i]
- req.add_buffer(stream, buffer)
-
self.reqs.append(req)
# Save a mmapped buffer so we can calculate the CRC later
@@ -127,7 +124,12 @@ class CaptureContext:
# a new Request, we re-use the old one. We need to call req.reuse()
# to re-initialize the Request before queuing.
+ for (stream, buffer) in req.buffers.items():
+ cam_ctx.cam.add_buffer(stream, buffer)
+
req.reuse()
+ req.enable_stream(stream, True)
+
cam_ctx.cam.queue_request(req)
def handle_key_event(self):
@@ -139,7 +141,13 @@ class CaptureContext:
# Queue the requests to the camera
for cam_ctx in self.camera_contexts:
+ stream = cam_ctx.cam_config.at(0).stream
+
+ for buffer in cam_ctx.buffers:
+ cam_ctx.cam.add_buffer(stream, buffer)
+
for req in cam_ctx.reqs:
+ req.enable_stream(stream, True)
cam_ctx.cam.queue_request(req)
# Use Selector to wait for events from the camera and from the keyboard
@@ -110,6 +110,16 @@ PYBIND11_MODULE(_libcamera, m)
* https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings
*/
+ struct RequestDeleter : std::default_delete<Request> {
+ void operator()(Request *request)
+ {
+ for (const auto &[stream, buffer] : request->buffers())
+ py::cast(buffer).dec_ref();
+
+ std::default_delete<Request>::operator()(request);
+ }
+ };
+
auto pyCameraManager = py::class_<PyCameraManager, std::shared_ptr<PyCameraManager>>(m, "CameraManager");
auto pyCamera = py::class_<Camera, PyCameraHolder>(m, "Camera");
auto pySensorConfiguration = py::class_<SensorConfiguration>(m, "SensorConfiguration");
@@ -123,7 +133,7 @@ PYBIND11_MODULE(_libcamera, m)
auto pyStream = py::class_<Stream>(m, "Stream");
auto pyControlId = py::class_<ControlId>(m, "ControlId");
auto pyControlInfo = py::class_<ControlInfo>(m, "ControlInfo");
- auto pyRequest = py::class_<Request>(m, "Request");
+ auto pyRequest = py::class_<Request, std::unique_ptr<Request, RequestDeleter>>(m, "Request");
auto pyRequestStatus = py::enum_<Request::Status>(pyRequest, "Status");
auto pyRequestReuse = py::enum_<Request::ReuseFlag>(pyRequest, "Reuse");
auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
@@ -195,10 +205,21 @@ PYBIND11_MODULE(_libcamera, m)
}, py::arg("controls") = std::unordered_map<const ControlId *, py::object>())
.def("stop", [](Camera &self) {
+ std::vector<FrameBuffer *> queued;
+
+ self.bufferCompleted.connect(&queued, [&](Request *request, const Stream *, FrameBuffer *buffer) {
+ if (!request)
+ queued.push_back(buffer);
+ });
+
int ret = self.stop();
+ self.bufferCompleted.disconnect(&queued);
self.requestCompleted.disconnect();
+ for (auto *buffer : queued)
+ py::cast(buffer).dec_ref();
+
if (ret)
throw std::system_error(-ret, std::generic_category(),
"Failed to stop camera");
@@ -221,7 +242,7 @@ PYBIND11_MODULE(_libcamera, m)
})
.def("create_request", [](Camera &self, uint64_t cookie) {
- std::unique_ptr<Request> req = self.createRequest(cookie);
+ std::unique_ptr<Request, RequestDeleter> req(self.createRequest(cookie).release());
if (!req)
throw std::system_error(ENOMEM, std::generic_category(),
"Failed to create request");
@@ -246,6 +267,20 @@ PYBIND11_MODULE(_libcamera, m)
}
})
+ /* \todo Fence is not supported */
+ .def("add_buffer", [](Camera &self, const Stream *stream, FrameBuffer *buffer) {
+ py::object py_buf = py::cast(buffer);
+
+ py_buf.inc_ref();
+
+ int ret = self.addBuffer(stream, buffer);
+ if (ret) {
+ py_buf.dec_ref();
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to add buffer");
+ }
+ })
+
.def_property_readonly("streams", [](Camera &self) {
py::set set;
for (auto &s : self.streams()) {
@@ -457,13 +492,6 @@ PYBIND11_MODULE(_libcamera, m)
});
pyRequest
- /* \todo Fence is not supported, so we cannot expose addBuffer() directly */
- .def("add_buffer", [](Request &self, const Stream *stream, FrameBuffer *buffer) {
- int ret = self.addBuffer(stream, buffer);
- if (ret)
- throw std::system_error(-ret, std::generic_category(),
- "Failed to add buffer");
- }, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */
.def_property_readonly("status", &Request::status)
.def_property_readonly("buffers", &Request::buffers)
.def_property_readonly("cookie", &Request::cookie)
@@ -485,11 +513,15 @@ PYBIND11_MODULE(_libcamera, m)
return ret;
})
- /*
- * \todo As we add a keep_alive to the fb in addBuffers(), we
- * can only allow reuse with ReuseBuffers.
- */
- .def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); })
+ .def("reuse", [](Request &self) {
+ for (const auto &[stream, buffer] : self.buffers())
+ py::cast(buffer).dec_ref();
+
+ self.reuse();
+ })
+ .def("enable_stream", [](Request &self, const Stream *stream, bool enabled) {
+ self.enableStream(stream, enabled);
+ })
.def("__str__", &Request::toString);
pyRequestStatus
@@ -497,10 +529,6 @@ PYBIND11_MODULE(_libcamera, m)
.value("Complete", Request::RequestComplete)
.value("Cancelled", Request::RequestCancelled);
- pyRequestReuse
- .value("Default", Request::ReuseFlag::Default)
- .value("ReuseBuffers", Request::ReuseFlag::ReuseBuffers);
-
pyFrameMetadata
.def_readonly("status", &FrameMetadata::status)
.def_readonly("sequence", &FrameMetadata::sequence)
Port to using the camera's buffer pool. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> --- src/py/cam/cam.py | 20 +++--- src/py/examples/simple-cam.py | 13 +++- src/py/examples/simple-capture.py | 20 +++--- src/py/examples/simple-continuous-capture.py | 32 ++++++---- src/py/libcamera/py_main.cpp | 64 ++++++++++++++------ 5 files changed, 101 insertions(+), 48 deletions(-)