Message ID | 20220524114610.41848-11-tomi.valkeinen@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Tomi, Thank you for the patch. On Tue, May 24, 2022 at 02:46:01PM +0300, Tomi Valkeinen wrote: > Add an example to showcase the more-or-less minimal capture case. Niiiice :-) This is a really good start. I'd like more comments, but that can be done on top. > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> > --- > src/py/examples/simple-capture.py | 153 ++++++++++++++++++++++++++++++ > 1 file changed, 153 insertions(+) > create mode 100755 src/py/examples/simple-capture.py > > diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py > new file mode 100755 > index 00000000..45df9e7a > --- /dev/null > +++ b/src/py/examples/simple-capture.py > @@ -0,0 +1,153 @@ > +#!/usr/bin/env python3 > + > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> > + > +# A simple minimal capture example showing: > +# - How to setup the camera > +# - Capture frames in a blocking manner > +# - Memory map the frames > +# - How to stop the camera > + > +import argparse > +import binascii > +import libcamera as libcam > +import libcamera.utils > +import sys > + > + > +def main(): > + parser = argparse.ArgumentParser() > + parser.add_argument('-c', '--camera', type=str, default='1', > + help='Camera index number (starting from 1) or part of the name') > + parser.add_argument('-f', '--format', type=str, help='Pixel format') > + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') > + args = parser.parse_args() > + > + cm = libcam.CameraManager.singleton() > + > + try: > + if args.camera.isnumeric(): > + cam_idx = int(args.camera) > + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) > + else: > + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) > + except Exception: > + print(f'Failed to find camera "{args.camera}"') > + return -1 > + > + # Acquire the camera for our use > + > + ret = cam.acquire() > + assert ret == 0 > + > + # Configure the camera > + > + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) I'm going to bikeshed a bit as this will be used as a starting point by many people, sorry. As the Python coding style uses snake_case, should this be cam_config ? The next variable would be stream_config. I'd also use the ViewFinder role to match the C++ simple-cam. > + > + streamconfig = camconfig.at(0) > + > + if args.format: > + fmt = libcam.PixelFormat(args.format) > + streamconfig.pixel_format = fmt > + > + if args.size: > + w, h = [int(v) for v in args.size.split('x')] > + streamconfig.size = libcam.Size(w, h) > + > + ret = cam.configure(camconfig) > + assert ret == 0 > + > + stream = streamconfig.stream > + > + # Allocate the buffers for capture > + > + allocator = libcam.FrameBufferAllocator(cam) > + ret = allocator.allocate(stream) > + assert ret > 0 > + > + num_bufs = len(allocator.buffers(stream)) > + > + print(f'Capturing {num_bufs} frames with {streamconfig}') This can be done on top, but I'd like to capture more frames (100 for instance), to show how to requeue requests. > + > + # Create the requests and assign a buffer for each request > + > + reqs = [] > + for i in range(num_bufs): > + # Use the buffer index as the cookie > + req = cam.create_request(i) > + > + buffer = allocator.buffers(stream)[i] > + ret = req.add_buffer(stream, buffer) > + assert ret == 0 > + > + reqs.append(req) > + > + # Start the camera > + > + ret = cam.start() > + assert ret == 0 > + > + # Queue the requests to the camera > + > + for req in reqs: > + ret = cam.queue_request(req) > + assert ret == 0 > + > + # Wait until the requests are finished > + > + reqs = [] > + > + while True: > + # cm.read_event() blocks until there is an event > + cm.read_event() > + > + # Get all the ready requests > + ready_reqs = cm.get_ready_requests() Integrating a simple event loop (maybe based on the Python asyncio module ?) would be good here. > + > + reqs += ready_reqs > + > + if len(reqs) >= num_bufs: > + break > + > + # Process the captured frames > + > + for i, req in enumerate(reqs): > + assert i == req.cookie > + > + buffers = req.buffers > + > + # A ready Request could contain multiple buffers if multiple streams > + # were being used. Here we know we only have a single stream, > + # and we use next(iter()) to get the first and only buffer. > + > + assert len(buffers) == 1 > + > + stream, fb = next(iter(buffers.items())) > + > + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate > + # the crc for each plane. > + > + with libcamera.utils.MappedFrameBuffer(fb) as mfb: > + crcs = [binascii.crc32(p) for p in mfb.planes] Mapping buffers is expensive, this should be done before starting the camera. > + > + meta = fb.metadata > + > + print('seq {}, bytes {}, CRCs {}' > + .format(meta.sequence, meta.bytesused, crcs)) > + > + # Stop the camera > + > + ret = cam.stop() > + assert ret == 0 > + > + # Release the camera > + > + ret = cam.release() > + assert ret == 0 > + > + return 0 > + > + > +if __name__ == '__main__': > + sys.exit(main())
On 26/05/2022 18:55, Laurent Pinchart wrote: > Hi Tomi, > > Thank you for the patch. > > On Tue, May 24, 2022 at 02:46:01PM +0300, Tomi Valkeinen wrote: >> Add an example to showcase the more-or-less minimal capture case. > > Niiiice :-) This is a really good start. I'd like more comments, but > that can be done on top. > >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> >> --- >> src/py/examples/simple-capture.py | 153 ++++++++++++++++++++++++++++++ >> 1 file changed, 153 insertions(+) >> create mode 100755 src/py/examples/simple-capture.py >> >> diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py >> new file mode 100755 >> index 00000000..45df9e7a >> --- /dev/null >> +++ b/src/py/examples/simple-capture.py >> @@ -0,0 +1,153 @@ >> +#!/usr/bin/env python3 >> + >> +# SPDX-License-Identifier: BSD-3-Clause >> +# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> >> + >> +# A simple minimal capture example showing: >> +# - How to setup the camera >> +# - Capture frames in a blocking manner >> +# - Memory map the frames >> +# - How to stop the camera >> + >> +import argparse >> +import binascii >> +import libcamera as libcam >> +import libcamera.utils >> +import sys >> + >> + >> +def main(): >> + parser = argparse.ArgumentParser() >> + parser.add_argument('-c', '--camera', type=str, default='1', >> + help='Camera index number (starting from 1) or part of the name') >> + parser.add_argument('-f', '--format', type=str, help='Pixel format') >> + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') >> + args = parser.parse_args() >> + >> + cm = libcam.CameraManager.singleton() >> + >> + try: >> + if args.camera.isnumeric(): >> + cam_idx = int(args.camera) >> + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) >> + else: >> + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) >> + except Exception: >> + print(f'Failed to find camera "{args.camera}"') >> + return -1 >> + >> + # Acquire the camera for our use >> + >> + ret = cam.acquire() >> + assert ret == 0 >> + >> + # Configure the camera >> + >> + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) > > I'm going to bikeshed a bit as this will be used as a starting point by > many people, sorry. > > As the Python coding style uses snake_case, should this be cam_config ? > The next variable would be stream_config. > > I'd also use the ViewFinder role to match the C++ simple-cam. Sounds good to me. >> + >> + streamconfig = camconfig.at(0) >> + >> + if args.format: >> + fmt = libcam.PixelFormat(args.format) >> + streamconfig.pixel_format = fmt >> + >> + if args.size: >> + w, h = [int(v) for v in args.size.split('x')] >> + streamconfig.size = libcam.Size(w, h) >> + >> + ret = cam.configure(camconfig) >> + assert ret == 0 >> + >> + stream = streamconfig.stream >> + >> + # Allocate the buffers for capture >> + >> + allocator = libcam.FrameBufferAllocator(cam) >> + ret = allocator.allocate(stream) >> + assert ret > 0 >> + >> + num_bufs = len(allocator.buffers(stream)) >> + >> + print(f'Capturing {num_bufs} frames with {streamconfig}') > > This can be done on top, but I'd like to capture more frames (100 for > instance), to show how to requeue requests. > >> + >> + # Create the requests and assign a buffer for each request >> + >> + reqs = [] >> + for i in range(num_bufs): >> + # Use the buffer index as the cookie >> + req = cam.create_request(i) >> + >> + buffer = allocator.buffers(stream)[i] >> + ret = req.add_buffer(stream, buffer) >> + assert ret == 0 >> + >> + reqs.append(req) >> + >> + # Start the camera >> + >> + ret = cam.start() >> + assert ret == 0 >> + >> + # Queue the requests to the camera >> + >> + for req in reqs: >> + ret = cam.queue_request(req) >> + assert ret == 0 >> + >> + # Wait until the requests are finished >> + >> + reqs = [] >> + >> + while True: >> + # cm.read_event() blocks until there is an event >> + cm.read_event() >> + >> + # Get all the ready requests >> + ready_reqs = cm.get_ready_requests() > > Integrating a simple event loop (maybe based on the Python asyncio > module ?) would be good here. Let's discuss these in the thread for the following patch. >> + >> + reqs += ready_reqs >> + >> + if len(reqs) >= num_bufs: >> + break >> + >> + # Process the captured frames >> + >> + for i, req in enumerate(reqs): >> + assert i == req.cookie >> + >> + buffers = req.buffers >> + >> + # A ready Request could contain multiple buffers if multiple streams >> + # were being used. Here we know we only have a single stream, >> + # and we use next(iter()) to get the first and only buffer. >> + >> + assert len(buffers) == 1 >> + >> + stream, fb = next(iter(buffers.items())) >> + >> + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate >> + # the crc for each plane. >> + >> + with libcamera.utils.MappedFrameBuffer(fb) as mfb: >> + crcs = [binascii.crc32(p) for p in mfb.planes] > > Mapping buffers is expensive, this should be done before starting the > camera. Hmm... How do we invalidate the caches if we keep the buffers mapped? Tomi
Hi Tomi, On Fri, May 27, 2022 at 09:08:37AM +0300, Tomi Valkeinen wrote: > On 26/05/2022 18:55, Laurent Pinchart wrote: > > On Tue, May 24, 2022 at 02:46:01PM +0300, Tomi Valkeinen wrote: > >> Add an example to showcase the more-or-less minimal capture case. > > > > Niiiice :-) This is a really good start. I'd like more comments, but > > that can be done on top. > > > >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> > >> --- > >> src/py/examples/simple-capture.py | 153 ++++++++++++++++++++++++++++++ > >> 1 file changed, 153 insertions(+) > >> create mode 100755 src/py/examples/simple-capture.py > >> > >> diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py > >> new file mode 100755 > >> index 00000000..45df9e7a > >> --- /dev/null > >> +++ b/src/py/examples/simple-capture.py > >> @@ -0,0 +1,153 @@ > >> +#!/usr/bin/env python3 > >> + > >> +# SPDX-License-Identifier: BSD-3-Clause > >> +# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> > >> + > >> +# A simple minimal capture example showing: > >> +# - How to setup the camera > >> +# - Capture frames in a blocking manner > >> +# - Memory map the frames > >> +# - How to stop the camera > >> + > >> +import argparse > >> +import binascii > >> +import libcamera as libcam > >> +import libcamera.utils > >> +import sys > >> + > >> + > >> +def main(): > >> + parser = argparse.ArgumentParser() > >> + parser.add_argument('-c', '--camera', type=str, default='1', > >> + help='Camera index number (starting from 1) or part of the name') > >> + parser.add_argument('-f', '--format', type=str, help='Pixel format') > >> + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') > >> + args = parser.parse_args() > >> + > >> + cm = libcam.CameraManager.singleton() > >> + > >> + try: > >> + if args.camera.isnumeric(): > >> + cam_idx = int(args.camera) > >> + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) > >> + else: > >> + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) > >> + except Exception: > >> + print(f'Failed to find camera "{args.camera}"') > >> + return -1 > >> + > >> + # Acquire the camera for our use > >> + > >> + ret = cam.acquire() > >> + assert ret == 0 > >> + > >> + # Configure the camera > >> + > >> + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) > > > > I'm going to bikeshed a bit as this will be used as a starting point by > > many people, sorry. > > > > As the Python coding style uses snake_case, should this be cam_config ? > > The next variable would be stream_config. > > > > I'd also use the ViewFinder role to match the C++ simple-cam. > > Sounds good to me. > > >> + > >> + streamconfig = camconfig.at(0) > >> + > >> + if args.format: > >> + fmt = libcam.PixelFormat(args.format) > >> + streamconfig.pixel_format = fmt > >> + > >> + if args.size: > >> + w, h = [int(v) for v in args.size.split('x')] > >> + streamconfig.size = libcam.Size(w, h) > >> + > >> + ret = cam.configure(camconfig) > >> + assert ret == 0 > >> + > >> + stream = streamconfig.stream > >> + > >> + # Allocate the buffers for capture > >> + > >> + allocator = libcam.FrameBufferAllocator(cam) > >> + ret = allocator.allocate(stream) > >> + assert ret > 0 > >> + > >> + num_bufs = len(allocator.buffers(stream)) > >> + > >> + print(f'Capturing {num_bufs} frames with {streamconfig}') > > > > This can be done on top, but I'd like to capture more frames (100 for > > instance), to show how to requeue requests. > > > >> + > >> + # Create the requests and assign a buffer for each request > >> + > >> + reqs = [] > >> + for i in range(num_bufs): > >> + # Use the buffer index as the cookie > >> + req = cam.create_request(i) > >> + > >> + buffer = allocator.buffers(stream)[i] > >> + ret = req.add_buffer(stream, buffer) > >> + assert ret == 0 > >> + > >> + reqs.append(req) > >> + > >> + # Start the camera > >> + > >> + ret = cam.start() > >> + assert ret == 0 > >> + > >> + # Queue the requests to the camera > >> + > >> + for req in reqs: > >> + ret = cam.queue_request(req) > >> + assert ret == 0 > >> + > >> + # Wait until the requests are finished > >> + > >> + reqs = [] > >> + > >> + while True: > >> + # cm.read_event() blocks until there is an event > >> + cm.read_event() > >> + > >> + # Get all the ready requests > >> + ready_reqs = cm.get_ready_requests() > > > > Integrating a simple event loop (maybe based on the Python asyncio > > module ?) would be good here. > > Let's discuss these in the thread for the following patch. > > >> + > >> + reqs += ready_reqs > >> + > >> + if len(reqs) >= num_bufs: > >> + break > >> + > >> + # Process the captured frames > >> + > >> + for i, req in enumerate(reqs): > >> + assert i == req.cookie > >> + > >> + buffers = req.buffers > >> + > >> + # A ready Request could contain multiple buffers if multiple streams > >> + # were being used. Here we know we only have a single stream, > >> + # and we use next(iter()) to get the first and only buffer. > >> + > >> + assert len(buffers) == 1 > >> + > >> + stream, fb = next(iter(buffers.items())) > >> + > >> + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate > >> + # the crc for each plane. > >> + > >> + with libcamera.utils.MappedFrameBuffer(fb) as mfb: > >> + crcs = [binascii.crc32(p) for p in mfb.planes] > > > > Mapping buffers is expensive, this should be done before starting the > > camera. > > Hmm... How do we invalidate the caches if we keep the buffers mapped? QBUF/DQBUF are supposed to handle that.
On 27/05/2022 09:14, Laurent Pinchart wrote: >>>> + with libcamera.utils.MappedFrameBuffer(fb) as mfb: >>>> + crcs = [binascii.crc32(p) for p in mfb.planes] >>> >>> Mapping buffers is expensive, this should be done before starting the >>> camera. >> >> Hmm... How do we invalidate the caches if we keep the buffers mapped? > > QBUF/DQBUF are supposed to handle that. Ah, ok. Isn't that horribly inefficient if you never actually access the buffer with CPU? Tomi
On Fri, May 27, 2022 at 09:16:01AM +0300, Tomi Valkeinen wrote: > On 27/05/2022 09:14, Laurent Pinchart wrote: > > >>>> + with libcamera.utils.MappedFrameBuffer(fb) as mfb: > >>>> + crcs = [binascii.crc32(p) for p in mfb.planes] > >>> > >>> Mapping buffers is expensive, this should be done before starting the > >>> camera. > >> > >> Hmm... How do we invalidate the caches if we keep the buffers mapped? > > > > QBUF/DQBUF are supposed to handle that. > > Ah, ok. Isn't that horribly inefficient if you never actually access the > buffer with CPU? There are hints that can be passed to the ioctls to have cache operations when not touching the buffers with the CPU.
diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py new file mode 100755 index 00000000..45df9e7a --- /dev/null +++ b/src/py/examples/simple-capture.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> + +# A simple minimal capture example showing: +# - How to setup the camera +# - Capture frames in a blocking manner +# - Memory map the frames +# - How to stop the camera + +import argparse +import binascii +import libcamera as libcam +import libcamera.utils +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--camera', type=str, default='1', + help='Camera index number (starting from 1) or part of the name') + parser.add_argument('-f', '--format', type=str, help='Pixel format') + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') + args = parser.parse_args() + + cm = libcam.CameraManager.singleton() + + try: + if args.camera.isnumeric(): + cam_idx = int(args.camera) + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) + else: + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) + except Exception: + print(f'Failed to find camera "{args.camera}"') + return -1 + + # Acquire the camera for our use + + ret = cam.acquire() + assert ret == 0 + + # Configure the camera + + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) + + streamconfig = camconfig.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + streamconfig.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + streamconfig.size = libcam.Size(w, h) + + ret = cam.configure(camconfig) + assert ret == 0 + + stream = streamconfig.stream + + # Allocate the buffers for capture + + allocator = libcam.FrameBufferAllocator(cam) + ret = allocator.allocate(stream) + assert ret > 0 + + num_bufs = len(allocator.buffers(stream)) + + print(f'Capturing {num_bufs} frames with {streamconfig}') + + # Create the requests and assign a buffer for each request + + reqs = [] + for i in range(num_bufs): + # Use the buffer index as the cookie + req = cam.create_request(i) + + buffer = allocator.buffers(stream)[i] + ret = req.add_buffer(stream, buffer) + assert ret == 0 + + reqs.append(req) + + # Start the camera + + ret = cam.start() + assert ret == 0 + + # Queue the requests to the camera + + for req in reqs: + ret = cam.queue_request(req) + assert ret == 0 + + # Wait until the requests are finished + + reqs = [] + + while True: + # cm.read_event() blocks until there is an event + cm.read_event() + + # Get all the ready requests + ready_reqs = cm.get_ready_requests() + + reqs += ready_reqs + + if len(reqs) >= num_bufs: + break + + # Process the captured frames + + for i, req in enumerate(reqs): + assert i == req.cookie + + buffers = req.buffers + + # A ready Request could contain multiple buffers if multiple streams + # were being used. Here we know we only have a single stream, + # and we use next(iter()) to get the first and only buffer. + + assert len(buffers) == 1 + + stream, fb = next(iter(buffers.items())) + + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate + # the crc for each plane. + + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + crcs = [binascii.crc32(p) for p in mfb.planes] + + meta = fb.metadata + + print('seq {}, bytes {}, CRCs {}' + .format(meta.sequence, meta.bytesused, crcs)) + + # Stop the camera + + ret = cam.stop() + assert ret == 0 + + # Release the camera + + ret = cam.release() + assert ret == 0 + + return 0 + + +if __name__ == '__main__': + sys.exit(main())
Add an example to showcase the more-or-less minimal capture case. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> --- src/py/examples/simple-capture.py | 153 ++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100755 src/py/examples/simple-capture.py