Message ID | 20220524114610.41848-14-tomi.valkeinen@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Tomi, Thank you for the patch. On Tue, May 24, 2022 at 02:46:04PM +0300, Tomi Valkeinen wrote: > From: Tomi Valkeinen <tomi.valkeinen@iki.fi> > > Convert ctx and state dicts to classes. No functional changes. > > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> > --- > src/py/cam/cam.py | 585 +++++++++++++++++++++-------------------- > src/py/cam/cam_kms.py | 12 +- > src/py/cam/cam_null.py | 8 +- > src/py/cam/cam_qt.py | 16 +- > src/py/cam/cam_qtgl.py | 22 +- > 5 files changed, 327 insertions(+), 316 deletions(-) > > diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py > index 64f67e86..f6e8232c 100755 > --- a/src/py/cam/cam.py > +++ b/src/py/cam/cam.py > @@ -6,6 +6,7 @@ > # \todo Convert ctx and state dicts to proper classes, and move relevant > # functions to those classes. > > +from typing import Any > import argparse > import binascii > import libcamera as libcam > @@ -14,379 +15,400 @@ import sys > import traceback > > > -class CustomAction(argparse.Action): > - def __init__(self, option_strings, dest, **kwargs): > - super().__init__(option_strings, dest, default={}, **kwargs) > +class CameraContext: > + camera: libcam.Camera > + id: str > + idx: int > > - def __call__(self, parser, namespace, values, option_string=None): > - if len(namespace.camera) == 0: > - print(f'Option {option_string} requires a --camera context') > - sys.exit(-1) > + opt_stream: str > + opt_strict_formats: bool > + opt_crc: bool > + opt_metadata: bool > + opt_save_frames: bool > + opt_capture: int > > - if self.type == bool: > - values = True > + stream_names: dict[libcam.Stream, str] > + streams: list[libcam.Stream] > + allocator: libcam.FrameBufferAllocator > + requests: list[libcam.Request] > + reqs_queued: int > + reqs_completed: int > + last: int = 0 > + fps: float > > - current = namespace.camera[-1] > + def __init__(self, camera, idx): > + self.camera = camera > + self.idx = idx > + self.id = 'cam' + str(idx) > + self.reqs_queued = 0 > + self.reqs_completed = 0 > > - data = getattr(namespace, self.dest) > + def do_cmd_list_props(self): > + camera = self.camera > > - if self.nargs == '+': > - if current not in data: > - data[current] = [] > + print('Properties for', self.id) > > - data[current] += values > - else: > - data[current] = values > + for name, prop in camera.properties.items(): You can use self.camera here and drop the local variable. Same in a few other functions below. > + print('\t{}: {}'.format(name, prop)) > > + def do_cmd_list_controls(self): > + camera = self.camera > > -def do_cmd_list(cm): > - print('Available cameras:') > - > - for idx, c in enumerate(cm.cameras): > - print(f'{idx + 1}: {c.id}') > - > - > -def do_cmd_list_props(ctx): > - camera = ctx['camera'] > - > - print('Properties for', ctx['id']) > - > - for name, prop in camera.properties.items(): > - print('\t{}: {}'.format(name, prop)) > - > - > -def do_cmd_list_controls(ctx): > - camera = ctx['camera'] > - > - print('Controls for', ctx['id']) > - > - for name, prop in camera.controls.items(): > - print('\t{}: {}'.format(name, prop)) > + print('Controls for', self.id) > > + for name, prop in camera.controls.items(): > + print('\t{}: {}'.format(name, prop)) > > -def do_cmd_info(ctx): > - camera = ctx['camera'] > + def do_cmd_info(self): > + camera = self.camera > > - print('Stream info for', ctx['id']) > + print('Stream info for', self.id) > > - roles = [libcam.StreamRole.Viewfinder] > + roles = [libcam.StreamRole.Viewfinder] > > - camconfig = camera.generate_configuration(roles) > - if camconfig is None: > - raise Exception('Generating config failed') > + camconfig = camera.generate_configuration(roles) > + if camconfig is None: > + raise Exception('Generating config failed') > > - for i, stream_config in enumerate(camconfig): > - print('\t{}: {}'.format(i, stream_config)) > + for i, stream_config in enumerate(camconfig): > + print('\t{}: {}'.format(i, stream_config)) > > - formats = stream_config.formats > - for fmt in formats.pixel_formats: > - print('\t * Pixelformat:', fmt, formats.range(fmt)) > + formats = stream_config.formats > + for fmt in formats.pixel_formats: > + print('\t * Pixelformat:', fmt, formats.range(fmt)) > > - for size in formats.sizes(fmt): > - print('\t -', size) > + for size in formats.sizes(fmt): > + print('\t -', size) > > + def acquire(self): > + camera = self.camera > > -def acquire(ctx): > - camera = ctx['camera'] > + camera.acquire() > > - camera.acquire() > + def release(self): > + camera = self.camera > > + camera.release() > > -def release(ctx): > - camera = ctx['camera'] > + def __parse_streams(self): > + streams = [] > > - camera.release() > + for stream_desc in self.opt_stream: > + stream_opts: dict[str, Any] > + stream_opts = {'role': libcam.StreamRole.Viewfinder} > > + for stream_opt in stream_desc.split(','): > + if stream_opt == 0: > + continue > > -def parse_streams(ctx): > - streams = [] > - > - for stream_desc in ctx['opt-stream']: > - stream_opts = {'role': libcam.StreamRole.Viewfinder} > - > - for stream_opt in stream_desc.split(','): > - if stream_opt == 0: > - continue > - > - arr = stream_opt.split('=') > - if len(arr) != 2: > - print('Bad stream option', stream_opt) > - sys.exit(-1) > - > - key = arr[0] > - value = arr[1] > - > - if key in ['width', 'height']: > - value = int(value) > - elif key == 'role': > - rolemap = { > - 'still': libcam.StreamRole.StillCapture, > - 'raw': libcam.StreamRole.Raw, > - 'video': libcam.StreamRole.VideoRecording, > - 'viewfinder': libcam.StreamRole.Viewfinder, > - } > - > - role = rolemap.get(value.lower(), None) > + arr = stream_opt.split('=') > + if len(arr) != 2: > + print('Bad stream option', stream_opt) > + sys.exit(-1) Raising an exception would be better, but that's a candidate for another patch as it's a functional change. > > - if role is None: > - print('Bad stream role', value) > + key = arr[0] > + value = arr[1] > + > + if key in ['width', 'height']: > + value = int(value) > + elif key == 'role': > + rolemap = { > + 'still': libcam.StreamRole.StillCapture, > + 'raw': libcam.StreamRole.Raw, > + 'video': libcam.StreamRole.VideoRecording, > + 'viewfinder': libcam.StreamRole.Viewfinder, > + } > + > + role = rolemap.get(value.lower(), None) > + > + if role is None: > + print('Bad stream role', value) > + sys.exit(-1) > + > + value = role > + elif key == 'pixelformat': > + pass > + else: > + print('Bad stream option key', key) > sys.exit(-1) > > - value = role > - elif key == 'pixelformat': > - pass > - else: > - print('Bad stream option key', key) > - sys.exit(-1) > + stream_opts[key] = value > > - stream_opts[key] = value > + streams.append(stream_opts) > > - streams.append(stream_opts) > + return streams > > - return streams > + def configure(self): > + camera = self.camera > > + streams = self.__parse_streams() > > -def configure(ctx): > - camera = ctx['camera'] > + roles = [opts['role'] for opts in streams] > > - streams = parse_streams(ctx) > + camconfig = camera.generate_configuration(roles) > + if camconfig is None: > + raise Exception('Generating config failed') > > - roles = [opts['role'] for opts in streams] > + for idx, stream_opts in enumerate(streams): > + stream_config = camconfig.at(idx) > > - camconfig = camera.generate_configuration(roles) > - if camconfig is None: > - raise Exception('Generating config failed') > + if 'width' in stream_opts: > + stream_config.size.width = stream_opts['width'] > > - for idx, stream_opts in enumerate(streams): > - stream_config = camconfig.at(idx) > + if 'height' in stream_opts: > + stream_config.size.height = stream_opts['height'] > > - if 'width' in stream_opts: > - stream_config.size.width = stream_opts['width'] > + if 'pixelformat' in stream_opts: > + stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) > > - if 'height' in stream_opts: > - stream_config.size.height = stream_opts['height'] > + stat = camconfig.validate() > > - if 'pixelformat' in stream_opts: > - stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) > + if stat == libcam.CameraConfiguration.Status.Invalid: > + print('Camera configuration invalid') > + exit(-1) > + elif stat == libcam.CameraConfiguration.Status.Adjusted: > + if self.opt_strict_formats: > + print('Adjusting camera configuration disallowed by --strict-formats argument') > + exit(-1) > > - stat = camconfig.validate() > + print('Camera configuration adjusted') > > - if stat == libcam.CameraConfiguration.Status.Invalid: > - print('Camera configuration invalid') > - exit(-1) > - elif stat == libcam.CameraConfiguration.Status.Adjusted: > - if ctx['opt-strict-formats']: > - print('Adjusting camera configuration disallowed by --strict-formats argument') > - exit(-1) > + r = camera.configure(camconfig) > + if r != 0: > + raise Exception('Configure failed') > > - print('Camera configuration adjusted') > + self.stream_names = {} > + self.streams = [] > > - r = camera.configure(camconfig) > - if r != 0: > - raise Exception('Configure failed') > + for idx, stream_config in enumerate(camconfig): > + stream = stream_config.stream > + self.streams.append(stream) > + self.stream_names[stream] = 'stream' + str(idx) > + print('{}-{}: stream config {}'.format(self.id, self.stream_names[stream], stream.configuration)) > > - ctx['stream-names'] = {} > - ctx['streams'] = [] > + def alloc_buffers(self): > + camera = self.camera > > - for idx, stream_config in enumerate(camconfig): > - stream = stream_config.stream > - ctx['streams'].append(stream) > - ctx['stream-names'][stream] = 'stream' + str(idx) > - print('{}-{}: stream config {}'.format(ctx['id'], ctx['stream-names'][stream], stream.configuration)) > + allocator = libcam.FrameBufferAllocator(camera) > > + for stream in self.streams: > + ret = allocator.allocate(stream) > + if ret < 0: > + print('Cannot allocate buffers') > + exit(-1) > > -def alloc_buffers(ctx): > - camera = ctx['camera'] > + allocated = len(allocator.buffers(stream)) > > - allocator = libcam.FrameBufferAllocator(camera) > + print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated)) > > - for idx, stream in enumerate(ctx['streams']): > - ret = allocator.allocate(stream) > - if ret < 0: > - print('Cannot allocate buffers') > - exit(-1) > + self.allocator = allocator > > - allocated = len(allocator.buffers(stream)) > + def create_requests(self): > + camera = self.camera > > - print('{}-{}: Allocated {} buffers'.format(ctx['id'], ctx['stream-names'][stream], allocated)) > + self.requests = [] > > - ctx['allocator'] = allocator > + # Identify the stream with the least number of buffers > + num_bufs = min([len(self.allocator.buffers(stream)) for stream in self.streams]) > > + requests = [] > > -def create_requests(ctx): > - camera = ctx['camera'] > + for buf_num in range(num_bufs): > + request = camera.create_request(self.idx) > > - ctx['requests'] = [] > + if request is None: > + print('Can not create request') > + exit(-1) > > - # Identify the stream with the least number of buffers > - num_bufs = min([len(ctx['allocator'].buffers(stream)) for stream in ctx['streams']]) > + for stream in self.streams: > + buffers = self.allocator.buffers(stream) > + buffer = buffers[buf_num] > > - requests = [] > + ret = request.add_buffer(stream, buffer) > + if ret < 0: > + print('Can not set buffer for request') > + exit(-1) > > - for buf_num in range(num_bufs): > - request = camera.create_request(ctx['idx']) > + requests.append(request) > > - if request is None: > - print('Can not create request') > - exit(-1) > + self.requests = requests > > - for stream in ctx['streams']: > - buffers = ctx['allocator'].buffers(stream) > - buffer = buffers[buf_num] > + def start(self): > + camera = self.camera > > - ret = request.add_buffer(stream, buffer) > - if ret < 0: > - print('Can not set buffer for request') > - exit(-1) > + camera.start() > > - requests.append(request) > + def stop(self): > + camera = self.camera > > - ctx['requests'] = requests > + camera.stop() > > + def queue_requests(self): > + camera = self.camera > > -def start(ctx): > - camera = ctx['camera'] > + for request in self.requests: > + camera.queue_request(request) > + self.reqs_queued += 1 > > - camera.start() > + del self.requests > > > -def stop(ctx): > - camera = ctx['camera'] > +class CaptureState: > + cm: libcam.CameraManager > + contexts: list[CameraContext] > + renderer: Any > > - camera.stop() > + def __init__(self, cm, contexts): > + self.cm = cm > + self.contexts = contexts > > + # Called from renderer when there is a libcamera event > + def event_handler(self): > + try: > + cm = self.cm > + contexts = self.contexts Same here, these local variables could possibly be dropped. Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > -def queue_requests(ctx): > - camera = ctx['camera'] > + cm.read_event() > > - for request in ctx['requests']: > - camera.queue_request(request) > - ctx['reqs-queued'] += 1 > + reqs = cm.get_ready_requests() > > - del ctx['requests'] > + for req in reqs: > + ctx = next(ctx for ctx in contexts if ctx.idx == req.cookie) > + self.__request_handler(ctx, req) > > + running = any(ctx.reqs_completed < ctx.opt_capture for ctx in contexts) > + return running > + except Exception: > + traceback.print_exc() > + return False > > -def capture_init(contexts): > - for ctx in contexts: > - acquire(ctx) > + def __request_handler(self, ctx, req): > + if req.status != libcam.Request.Status.Complete: > + raise Exception('{}: Request failed: {}'.format(ctx.id, req.status)) > > - for ctx in contexts: > - configure(ctx) > + buffers = req.buffers > > - for ctx in contexts: > - alloc_buffers(ctx) > + # Compute the frame rate. The timestamp is arbitrarily retrieved from > + # the first buffer, as all buffers should have matching timestamps. > + ts = buffers[next(iter(buffers))].metadata.timestamp > + last = ctx.last > + fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 > + ctx.last = ts > + ctx.fps = fps > > - for ctx in contexts: > - create_requests(ctx) > + for stream, fb in buffers.items(): > + stream_name = ctx.stream_names[stream] > > + crcs = [] > + if ctx.opt_crc: > + with libcamera.utils.MappedFrameBuffer(fb) as mfb: > + plane_crcs = [binascii.crc32(p) for p in mfb.planes] > + crcs.append(plane_crcs) > > -def capture_start(contexts): > - for ctx in contexts: > - start(ctx) > + meta = fb.metadata > > - for ctx in contexts: > - queue_requests(ctx) > + print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' > + .format(ts / 1000000000, fps, > + ctx.id, stream_name, > + meta.sequence, meta.bytesused, > + crcs)) > > + if ctx.opt_metadata: > + reqmeta = req.metadata > + for ctrl, val in reqmeta.items(): > + print(f'\t{ctrl} = {val}') > > -# Called from renderer when there is a libcamera event > -def event_handler(state): > - try: > - cm = state['cm'] > - contexts = state['contexts'] > + if ctx.opt_save_frames: > + with libcamera.utils.MappedFrameBuffer(fb) as mfb: > + filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed) > + with open(filename, 'wb') as f: > + for p in mfb.planes: > + f.write(p) > > - cm.read_event() > + self.renderer.request_handler(ctx, req) > > - reqs = cm.get_ready_requests() > + ctx.reqs_completed += 1 > > - for req in reqs: > - ctx = next(ctx for ctx in contexts if ctx['idx'] == req.cookie) > - request_handler(state, ctx, req) > + # Called from renderer when it has finished with a request > + def request_processed(self, ctx, req): > + camera = ctx.camera > > - running = any(ctx['reqs-completed'] < ctx['opt-capture'] for ctx in contexts) > - return running > - except Exception: > - traceback.print_exc() > - return False > + if ctx.reqs_queued < ctx.opt_capture: > + req.reuse() > + camera.queue_request(req) > + ctx.reqs_queued += 1 > > + def __capture_init(self): > + for ctx in self.contexts: > + ctx.acquire() > > -def request_handler(state, ctx, req): > - if req.status != libcam.Request.Status.Complete: > - raise Exception('{}: Request failed: {}'.format(ctx['id'], req.status)) > + for ctx in self.contexts: > + ctx.configure() > > - buffers = req.buffers > + for ctx in self.contexts: > + ctx.alloc_buffers() > > - # Compute the frame rate. The timestamp is arbitrarily retrieved from > - # the first buffer, as all buffers should have matching timestamps. > - ts = buffers[next(iter(buffers))].metadata.timestamp > - last = ctx.get('last', 0) > - fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 > - ctx['last'] = ts > - ctx['fps'] = fps > + for ctx in self.contexts: > + ctx.create_requests() > > - for stream, fb in buffers.items(): > - stream_name = ctx['stream-names'][stream] > + def __capture_start(self): > + for ctx in self.contexts: > + ctx.start() > > - crcs = [] > - if ctx['opt-crc']: > - with libcamera.utils.MappedFrameBuffer(fb) as mfb: > - plane_crcs = [binascii.crc32(p) for p in mfb.planes] > - crcs.append(plane_crcs) > + for ctx in self.contexts: > + ctx.queue_requests() > > - meta = fb.metadata > + def __capture_deinit(self): > + for ctx in self.contexts: > + ctx.stop() > > - print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' > - .format(ts / 1000000000, fps, > - ctx['id'], stream_name, > - meta.sequence, meta.bytesused, > - crcs)) > + for ctx in self.contexts: > + ctx.release() > > - if ctx['opt-metadata']: > - reqmeta = req.metadata > - for ctrl, val in reqmeta.items(): > - print(f'\t{ctrl} = {val}') > + def do_cmd_capture(self): > + self.__capture_init() > > - if ctx['opt-save-frames']: > - with libcamera.utils.MappedFrameBuffer(fb) as mfb: > - filename = 'frame-{}-{}-{}.data'.format(ctx['id'], stream_name, ctx['reqs-completed']) > - with open(filename, 'wb') as f: > - for p in mfb.planes: > - f.write(p) > + renderer = self.renderer > > - state['renderer'].request_handler(ctx, req) > + renderer.setup() > > - ctx['reqs-completed'] += 1 > + self.__capture_start() > > + renderer.run() > > -# Called from renderer when it has finished with a request > -def request_prcessed(ctx, req): > - camera = ctx['camera'] > + self.__capture_deinit() > > - if ctx['reqs-queued'] < ctx['opt-capture']: > - req.reuse() > - camera.queue_request(req) > - ctx['reqs-queued'] += 1 > > +class CustomAction(argparse.Action): > + def __init__(self, option_strings, dest, **kwargs): > + super().__init__(option_strings, dest, default={}, **kwargs) > > -def capture_deinit(contexts): > - for ctx in contexts: > - stop(ctx) > + def __call__(self, parser, namespace, values, option_string=None): > + if len(namespace.camera) == 0: > + print(f'Option {option_string} requires a --camera context') > + sys.exit(-1) > > - for ctx in contexts: > - release(ctx) > + if self.type == bool: > + values = True > > + current = namespace.camera[-1] > > -def do_cmd_capture(state): > - capture_init(state['contexts']) > + data = getattr(namespace, self.dest) > > - renderer = state['renderer'] > + if self.nargs == '+': > + if current not in data: > + data[current] = [] > > - renderer.setup() > + data[current] += values > + else: > + data[current] = values > > - capture_start(state['contexts']) > > - renderer.run() > +def do_cmd_list(cm): > + print('Available cameras:') > > - capture_deinit(state['contexts']) > + for idx, c in enumerate(cm.cameras): > + print(f'{idx + 1}: {c.id}') > > > def main(): > @@ -422,39 +444,28 @@ def main(): > print('Unable to find camera', cam_idx) > return -1 > > - contexts.append({ > - 'camera': camera, > - 'idx': cam_idx, > - 'id': 'cam' + str(cam_idx), > - 'reqs-queued': 0, > - 'reqs-completed': 0, > - 'opt-capture': args.capture.get(cam_idx, False), > - 'opt-crc': args.crc.get(cam_idx, False), > - 'opt-save-frames': args.save_frames.get(cam_idx, False), > - 'opt-metadata': args.metadata.get(cam_idx, False), > - 'opt-strict-formats': args.strict_formats.get(cam_idx, False), > - 'opt-stream': args.stream.get(cam_idx, ['role=viewfinder']), > - }) > + ctx = CameraContext(camera, cam_idx) > + ctx.opt_capture = args.capture.get(cam_idx, 0) > + ctx.opt_crc = args.crc.get(cam_idx, False) > + ctx.opt_save_frames = args.save_frames.get(cam_idx, False) > + ctx.opt_metadata = args.metadata.get(cam_idx, False) > + ctx.opt_strict_formats = args.strict_formats.get(cam_idx, False) > + ctx.opt_stream = args.stream.get(cam_idx, ['role=viewfinder']) > + contexts.append(ctx) > > for ctx in contexts: > - print('Using camera {} as {}'.format(ctx['camera'].id, ctx['id'])) > + print('Using camera {} as {}'.format(ctx.camera.id, ctx.id)) > > for ctx in contexts: > if args.list_properties: > - do_cmd_list_props(ctx) > + ctx.do_cmd_list_props() > if args.list_controls: > - do_cmd_list_controls(ctx) > + ctx.do_cmd_list_controls() > if args.info: > - do_cmd_info(ctx) > + ctx.do_cmd_info() > > if args.capture: > - > - state = { > - 'cm': cm, > - 'contexts': contexts, > - 'event_handler': event_handler, > - 'request_prcessed': request_prcessed, > - } > + state = CaptureState(cm, contexts) > > if args.renderer == 'null': > import cam_null > @@ -472,9 +483,9 @@ def main(): > print('Bad renderer', args.renderer) > return -1 > > - state['renderer'] = renderer > + state.renderer = renderer > > - do_cmd_capture(state) > + state.do_cmd_capture() > > return 0 > > diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py > index 74cd3b38..213e0b03 100644 > --- a/src/py/cam/cam_kms.py > +++ b/src/py/cam/cam_kms.py > @@ -10,8 +10,8 @@ class KMSRenderer: > def __init__(self, state): > self.state = state > > - self.cm = state['cm'] > - self.contexts = state['contexts'] > + self.cm = state.cm > + self.contexts = state.contexts > self.running = False > > card = pykms.Card() > @@ -92,7 +92,7 @@ class KMSRenderer: > if old: > req = old['camreq'] > ctx = old['camctx'] > - self.state['request_prcessed'](ctx, req) > + self.state.request_processed(ctx, req) > > def queue(self, drmreq): > if not self.next: > @@ -108,7 +108,7 @@ class KMSRenderer: > > idx = 0 > for ctx in self.contexts: > - for stream in ctx['streams']: > + for stream in ctx.streams: > > cfg = stream.configuration > fmt = cfg.pixel_format > @@ -125,7 +125,7 @@ class KMSRenderer: > 'size': cfg.size, > }) > > - for fb in ctx['allocator'].buffers(stream): > + for fb in ctx.allocator.buffers(stream): > w = cfg.size.width > h = cfg.size.height > fds = [] > @@ -148,7 +148,7 @@ class KMSRenderer: > self.handle_page_flip(ev.seq, ev.time) > > def readcam(self, fd): > - self.running = self.state['event_handler'](self.state) > + self.running = self.state.event_handler() > > def readkey(self, fileobj): > sys.stdin.readline() > diff --git a/src/py/cam/cam_null.py b/src/py/cam/cam_null.py > index a6da9671..45c5f467 100644 > --- a/src/py/cam/cam_null.py > +++ b/src/py/cam/cam_null.py > @@ -9,8 +9,8 @@ class NullRenderer: > def __init__(self, state): > self.state = state > > - self.cm = state['cm'] > - self.contexts = state['contexts'] > + self.cm = state.cm > + self.contexts = state.contexts > > self.running = False > > @@ -37,11 +37,11 @@ class NullRenderer: > print('Exiting...') > > def readcam(self, fd): > - self.running = self.state['event_handler'](self.state) > + self.running = self.state.event_handler() > > def readkey(self, fileobj): > sys.stdin.readline() > self.running = False > > def request_handler(self, ctx, req): > - self.state['request_prcessed'](ctx, req) > + self.state.request_processed(ctx, req) > diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py > index 03096920..d638e9cc 100644 > --- a/src/py/cam/cam_qt.py > +++ b/src/py/cam/cam_qt.py > @@ -176,8 +176,8 @@ class QtRenderer: > def __init__(self, state): > self.state = state > > - self.cm = state['cm'] > - self.contexts = state['contexts'] > + self.cm = state.cm > + self.contexts = state.contexts > > def setup(self): > self.app = QtWidgets.QApplication([]) > @@ -185,7 +185,7 @@ class QtRenderer: > windows = [] > > for ctx in self.contexts: > - for stream in ctx['streams']: > + for stream in ctx.streams: > window = MainWindow(ctx, stream) > window.show() > windows.append(window) > @@ -206,7 +206,7 @@ class QtRenderer: > print('Exiting...') > > def readcam(self): > - running = self.state['event_handler'](self.state) > + running = self.state.event_handler() > > if not running: > self.app.quit() > @@ -223,7 +223,7 @@ class QtRenderer: > > wnd.handle_request(stream, fb) > > - self.state['request_prcessed'](ctx, req) > + self.state.request_processed(ctx, req) > > def cleanup(self): > for w in self.windows: > @@ -254,7 +254,7 @@ class MainWindow(QtWidgets.QWidget): > group.setLayout(groupLayout) > controlsLayout.addWidget(group) > > - lab = QtWidgets.QLabel(ctx['id']) > + lab = QtWidgets.QLabel(ctx.id) > groupLayout.addWidget(lab) > > self.frameLabel = QtWidgets.QLabel() > @@ -265,7 +265,7 @@ class MainWindow(QtWidgets.QWidget): > group.setLayout(groupLayout) > controlsLayout.addWidget(group) > > - camera = ctx['camera'] > + camera = ctx.camera > > for k, v in camera.properties.items(): > lab = QtWidgets.QLabel() > @@ -308,4 +308,4 @@ class MainWindow(QtWidgets.QWidget): > self.label.setPixmap(pix) > > self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' > - .format(ctx['reqs-queued'], ctx['reqs-completed'], ctx['fps'])) > + .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps)) > diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py > index c9e367a2..5f7ccf1e 100644 > --- a/src/py/cam/cam_qtgl.py > +++ b/src/py/cam/cam_qtgl.py > @@ -142,7 +142,7 @@ class QtRenderer: > self.window = window > > def run(self): > - camnotif = QtCore.QSocketNotifier(self.state['cm'].efd, QtCore.QSocketNotifier.Read) > + camnotif = QtCore.QSocketNotifier(self.state.cm.efd, QtCore.QSocketNotifier.Read) > camnotif.activated.connect(lambda _: self.readcam()) > > keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) > @@ -155,7 +155,7 @@ class QtRenderer: > print('Exiting...') > > def readcam(self): > - running = self.state['event_handler'](self.state) > + running = self.state.event_handler() > > if not running: > self.app.quit() > @@ -184,12 +184,12 @@ class MainWindow(QtWidgets.QWidget): > self.reqqueue = {} > self.current = {} > > - for ctx in self.state['contexts']: > + for ctx in self.state.contexts: > > - self.reqqueue[ctx['idx']] = [] > - self.current[ctx['idx']] = [] > + self.reqqueue[ctx.idx] = [] > + self.current[ctx.idx] = [] > > - for stream in ctx['streams']: > + for stream in ctx.streams: > self.textures[stream] = None > > num_tiles = len(self.textures) > @@ -312,12 +312,12 @@ class MainWindow(QtWidgets.QWidget): > if len(queue) == 0: > continue > > - ctx = next(ctx for ctx in self.state['contexts'] if ctx['idx'] == ctx_idx) > + ctx = next(ctx for ctx in self.state.contexts if ctx.idx == ctx_idx) > > if self.current[ctx_idx]: > old = self.current[ctx_idx] > self.current[ctx_idx] = None > - self.state['request_prcessed'](ctx, old) > + self.state.request_processed(ctx, old) > > next_req = queue.pop(0) > self.current[ctx_idx] = next_req > @@ -336,8 +336,8 @@ class MainWindow(QtWidgets.QWidget): > > size = self.size() > > - for idx, ctx in enumerate(self.state['contexts']): > - for stream in ctx['streams']: > + for idx, ctx in enumerate(self.state.contexts): > + for stream in ctx.streams: > if self.textures[stream] is None: > continue > > @@ -359,5 +359,5 @@ class MainWindow(QtWidgets.QWidget): > assert(b) > > def handle_request(self, ctx, req): > - self.reqqueue[ctx['idx']].append(req) > + self.reqqueue[ctx.idx].append(req) > self.update()
diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 64f67e86..f6e8232c 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -6,6 +6,7 @@ # \todo Convert ctx and state dicts to proper classes, and move relevant # functions to those classes. +from typing import Any import argparse import binascii import libcamera as libcam @@ -14,379 +15,400 @@ import sys import traceback -class CustomAction(argparse.Action): - def __init__(self, option_strings, dest, **kwargs): - super().__init__(option_strings, dest, default={}, **kwargs) +class CameraContext: + camera: libcam.Camera + id: str + idx: int - def __call__(self, parser, namespace, values, option_string=None): - if len(namespace.camera) == 0: - print(f'Option {option_string} requires a --camera context') - sys.exit(-1) + opt_stream: str + opt_strict_formats: bool + opt_crc: bool + opt_metadata: bool + opt_save_frames: bool + opt_capture: int - if self.type == bool: - values = True + stream_names: dict[libcam.Stream, str] + streams: list[libcam.Stream] + allocator: libcam.FrameBufferAllocator + requests: list[libcam.Request] + reqs_queued: int + reqs_completed: int + last: int = 0 + fps: float - current = namespace.camera[-1] + def __init__(self, camera, idx): + self.camera = camera + self.idx = idx + self.id = 'cam' + str(idx) + self.reqs_queued = 0 + self.reqs_completed = 0 - data = getattr(namespace, self.dest) + def do_cmd_list_props(self): + camera = self.camera - if self.nargs == '+': - if current not in data: - data[current] = [] + print('Properties for', self.id) - data[current] += values - else: - data[current] = values + for name, prop in camera.properties.items(): + print('\t{}: {}'.format(name, prop)) + def do_cmd_list_controls(self): + camera = self.camera -def do_cmd_list(cm): - print('Available cameras:') - - for idx, c in enumerate(cm.cameras): - print(f'{idx + 1}: {c.id}') - - -def do_cmd_list_props(ctx): - camera = ctx['camera'] - - print('Properties for', ctx['id']) - - for name, prop in camera.properties.items(): - print('\t{}: {}'.format(name, prop)) - - -def do_cmd_list_controls(ctx): - camera = ctx['camera'] - - print('Controls for', ctx['id']) - - for name, prop in camera.controls.items(): - print('\t{}: {}'.format(name, prop)) + print('Controls for', self.id) + for name, prop in camera.controls.items(): + print('\t{}: {}'.format(name, prop)) -def do_cmd_info(ctx): - camera = ctx['camera'] + def do_cmd_info(self): + camera = self.camera - print('Stream info for', ctx['id']) + print('Stream info for', self.id) - roles = [libcam.StreamRole.Viewfinder] + roles = [libcam.StreamRole.Viewfinder] - camconfig = camera.generate_configuration(roles) - if camconfig is None: - raise Exception('Generating config failed') + camconfig = camera.generate_configuration(roles) + if camconfig is None: + raise Exception('Generating config failed') - for i, stream_config in enumerate(camconfig): - print('\t{}: {}'.format(i, stream_config)) + for i, stream_config in enumerate(camconfig): + print('\t{}: {}'.format(i, stream_config)) - formats = stream_config.formats - for fmt in formats.pixel_formats: - print('\t * Pixelformat:', fmt, formats.range(fmt)) + formats = stream_config.formats + for fmt in formats.pixel_formats: + print('\t * Pixelformat:', fmt, formats.range(fmt)) - for size in formats.sizes(fmt): - print('\t -', size) + for size in formats.sizes(fmt): + print('\t -', size) + def acquire(self): + camera = self.camera -def acquire(ctx): - camera = ctx['camera'] + camera.acquire() - camera.acquire() + def release(self): + camera = self.camera + camera.release() -def release(ctx): - camera = ctx['camera'] + def __parse_streams(self): + streams = [] - camera.release() + for stream_desc in self.opt_stream: + stream_opts: dict[str, Any] + stream_opts = {'role': libcam.StreamRole.Viewfinder} + for stream_opt in stream_desc.split(','): + if stream_opt == 0: + continue -def parse_streams(ctx): - streams = [] - - for stream_desc in ctx['opt-stream']: - stream_opts = {'role': libcam.StreamRole.Viewfinder} - - for stream_opt in stream_desc.split(','): - if stream_opt == 0: - continue - - arr = stream_opt.split('=') - if len(arr) != 2: - print('Bad stream option', stream_opt) - sys.exit(-1) - - key = arr[0] - value = arr[1] - - if key in ['width', 'height']: - value = int(value) - elif key == 'role': - rolemap = { - 'still': libcam.StreamRole.StillCapture, - 'raw': libcam.StreamRole.Raw, - 'video': libcam.StreamRole.VideoRecording, - 'viewfinder': libcam.StreamRole.Viewfinder, - } - - role = rolemap.get(value.lower(), None) + arr = stream_opt.split('=') + if len(arr) != 2: + print('Bad stream option', stream_opt) + sys.exit(-1) - if role is None: - print('Bad stream role', value) + key = arr[0] + value = arr[1] + + if key in ['width', 'height']: + value = int(value) + elif key == 'role': + rolemap = { + 'still': libcam.StreamRole.StillCapture, + 'raw': libcam.StreamRole.Raw, + 'video': libcam.StreamRole.VideoRecording, + 'viewfinder': libcam.StreamRole.Viewfinder, + } + + role = rolemap.get(value.lower(), None) + + if role is None: + print('Bad stream role', value) + sys.exit(-1) + + value = role + elif key == 'pixelformat': + pass + else: + print('Bad stream option key', key) sys.exit(-1) - value = role - elif key == 'pixelformat': - pass - else: - print('Bad stream option key', key) - sys.exit(-1) + stream_opts[key] = value - stream_opts[key] = value + streams.append(stream_opts) - streams.append(stream_opts) + return streams - return streams + def configure(self): + camera = self.camera + streams = self.__parse_streams() -def configure(ctx): - camera = ctx['camera'] + roles = [opts['role'] for opts in streams] - streams = parse_streams(ctx) + camconfig = camera.generate_configuration(roles) + if camconfig is None: + raise Exception('Generating config failed') - roles = [opts['role'] for opts in streams] + for idx, stream_opts in enumerate(streams): + stream_config = camconfig.at(idx) - camconfig = camera.generate_configuration(roles) - if camconfig is None: - raise Exception('Generating config failed') + if 'width' in stream_opts: + stream_config.size.width = stream_opts['width'] - for idx, stream_opts in enumerate(streams): - stream_config = camconfig.at(idx) + if 'height' in stream_opts: + stream_config.size.height = stream_opts['height'] - if 'width' in stream_opts: - stream_config.size.width = stream_opts['width'] + if 'pixelformat' in stream_opts: + stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) - if 'height' in stream_opts: - stream_config.size.height = stream_opts['height'] + stat = camconfig.validate() - if 'pixelformat' in stream_opts: - stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) + if stat == libcam.CameraConfiguration.Status.Invalid: + print('Camera configuration invalid') + exit(-1) + elif stat == libcam.CameraConfiguration.Status.Adjusted: + if self.opt_strict_formats: + print('Adjusting camera configuration disallowed by --strict-formats argument') + exit(-1) - stat = camconfig.validate() + print('Camera configuration adjusted') - if stat == libcam.CameraConfiguration.Status.Invalid: - print('Camera configuration invalid') - exit(-1) - elif stat == libcam.CameraConfiguration.Status.Adjusted: - if ctx['opt-strict-formats']: - print('Adjusting camera configuration disallowed by --strict-formats argument') - exit(-1) + r = camera.configure(camconfig) + if r != 0: + raise Exception('Configure failed') - print('Camera configuration adjusted') + self.stream_names = {} + self.streams = [] - r = camera.configure(camconfig) - if r != 0: - raise Exception('Configure failed') + for idx, stream_config in enumerate(camconfig): + stream = stream_config.stream + self.streams.append(stream) + self.stream_names[stream] = 'stream' + str(idx) + print('{}-{}: stream config {}'.format(self.id, self.stream_names[stream], stream.configuration)) - ctx['stream-names'] = {} - ctx['streams'] = [] + def alloc_buffers(self): + camera = self.camera - for idx, stream_config in enumerate(camconfig): - stream = stream_config.stream - ctx['streams'].append(stream) - ctx['stream-names'][stream] = 'stream' + str(idx) - print('{}-{}: stream config {}'.format(ctx['id'], ctx['stream-names'][stream], stream.configuration)) + allocator = libcam.FrameBufferAllocator(camera) + for stream in self.streams: + ret = allocator.allocate(stream) + if ret < 0: + print('Cannot allocate buffers') + exit(-1) -def alloc_buffers(ctx): - camera = ctx['camera'] + allocated = len(allocator.buffers(stream)) - allocator = libcam.FrameBufferAllocator(camera) + print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated)) - for idx, stream in enumerate(ctx['streams']): - ret = allocator.allocate(stream) - if ret < 0: - print('Cannot allocate buffers') - exit(-1) + self.allocator = allocator - allocated = len(allocator.buffers(stream)) + def create_requests(self): + camera = self.camera - print('{}-{}: Allocated {} buffers'.format(ctx['id'], ctx['stream-names'][stream], allocated)) + self.requests = [] - ctx['allocator'] = allocator + # Identify the stream with the least number of buffers + num_bufs = min([len(self.allocator.buffers(stream)) for stream in self.streams]) + requests = [] -def create_requests(ctx): - camera = ctx['camera'] + for buf_num in range(num_bufs): + request = camera.create_request(self.idx) - ctx['requests'] = [] + if request is None: + print('Can not create request') + exit(-1) - # Identify the stream with the least number of buffers - num_bufs = min([len(ctx['allocator'].buffers(stream)) for stream in ctx['streams']]) + for stream in self.streams: + buffers = self.allocator.buffers(stream) + buffer = buffers[buf_num] - requests = [] + ret = request.add_buffer(stream, buffer) + if ret < 0: + print('Can not set buffer for request') + exit(-1) - for buf_num in range(num_bufs): - request = camera.create_request(ctx['idx']) + requests.append(request) - if request is None: - print('Can not create request') - exit(-1) + self.requests = requests - for stream in ctx['streams']: - buffers = ctx['allocator'].buffers(stream) - buffer = buffers[buf_num] + def start(self): + camera = self.camera - ret = request.add_buffer(stream, buffer) - if ret < 0: - print('Can not set buffer for request') - exit(-1) + camera.start() - requests.append(request) + def stop(self): + camera = self.camera - ctx['requests'] = requests + camera.stop() + def queue_requests(self): + camera = self.camera -def start(ctx): - camera = ctx['camera'] + for request in self.requests: + camera.queue_request(request) + self.reqs_queued += 1 - camera.start() + del self.requests -def stop(ctx): - camera = ctx['camera'] +class CaptureState: + cm: libcam.CameraManager + contexts: list[CameraContext] + renderer: Any - camera.stop() + def __init__(self, cm, contexts): + self.cm = cm + self.contexts = contexts + # Called from renderer when there is a libcamera event + def event_handler(self): + try: + cm = self.cm + contexts = self.contexts -def queue_requests(ctx): - camera = ctx['camera'] + cm.read_event() - for request in ctx['requests']: - camera.queue_request(request) - ctx['reqs-queued'] += 1 + reqs = cm.get_ready_requests() - del ctx['requests'] + for req in reqs: + ctx = next(ctx for ctx in contexts if ctx.idx == req.cookie) + self.__request_handler(ctx, req) + running = any(ctx.reqs_completed < ctx.opt_capture for ctx in contexts) + return running + except Exception: + traceback.print_exc() + return False -def capture_init(contexts): - for ctx in contexts: - acquire(ctx) + def __request_handler(self, ctx, req): + if req.status != libcam.Request.Status.Complete: + raise Exception('{}: Request failed: {}'.format(ctx.id, req.status)) - for ctx in contexts: - configure(ctx) + buffers = req.buffers - for ctx in contexts: - alloc_buffers(ctx) + # Compute the frame rate. The timestamp is arbitrarily retrieved from + # the first buffer, as all buffers should have matching timestamps. + ts = buffers[next(iter(buffers))].metadata.timestamp + last = ctx.last + fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 + ctx.last = ts + ctx.fps = fps - for ctx in contexts: - create_requests(ctx) + for stream, fb in buffers.items(): + stream_name = ctx.stream_names[stream] + crcs = [] + if ctx.opt_crc: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + plane_crcs = [binascii.crc32(p) for p in mfb.planes] + crcs.append(plane_crcs) -def capture_start(contexts): - for ctx in contexts: - start(ctx) + meta = fb.metadata - for ctx in contexts: - queue_requests(ctx) + print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' + .format(ts / 1000000000, fps, + ctx.id, stream_name, + meta.sequence, meta.bytesused, + crcs)) + if ctx.opt_metadata: + reqmeta = req.metadata + for ctrl, val in reqmeta.items(): + print(f'\t{ctrl} = {val}') -# Called from renderer when there is a libcamera event -def event_handler(state): - try: - cm = state['cm'] - contexts = state['contexts'] + if ctx.opt_save_frames: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed) + with open(filename, 'wb') as f: + for p in mfb.planes: + f.write(p) - cm.read_event() + self.renderer.request_handler(ctx, req) - reqs = cm.get_ready_requests() + ctx.reqs_completed += 1 - for req in reqs: - ctx = next(ctx for ctx in contexts if ctx['idx'] == req.cookie) - request_handler(state, ctx, req) + # Called from renderer when it has finished with a request + def request_processed(self, ctx, req): + camera = ctx.camera - running = any(ctx['reqs-completed'] < ctx['opt-capture'] for ctx in contexts) - return running - except Exception: - traceback.print_exc() - return False + if ctx.reqs_queued < ctx.opt_capture: + req.reuse() + camera.queue_request(req) + ctx.reqs_queued += 1 + def __capture_init(self): + for ctx in self.contexts: + ctx.acquire() -def request_handler(state, ctx, req): - if req.status != libcam.Request.Status.Complete: - raise Exception('{}: Request failed: {}'.format(ctx['id'], req.status)) + for ctx in self.contexts: + ctx.configure() - buffers = req.buffers + for ctx in self.contexts: + ctx.alloc_buffers() - # Compute the frame rate. The timestamp is arbitrarily retrieved from - # the first buffer, as all buffers should have matching timestamps. - ts = buffers[next(iter(buffers))].metadata.timestamp - last = ctx.get('last', 0) - fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 - ctx['last'] = ts - ctx['fps'] = fps + for ctx in self.contexts: + ctx.create_requests() - for stream, fb in buffers.items(): - stream_name = ctx['stream-names'][stream] + def __capture_start(self): + for ctx in self.contexts: + ctx.start() - crcs = [] - if ctx['opt-crc']: - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - plane_crcs = [binascii.crc32(p) for p in mfb.planes] - crcs.append(plane_crcs) + for ctx in self.contexts: + ctx.queue_requests() - meta = fb.metadata + def __capture_deinit(self): + for ctx in self.contexts: + ctx.stop() - print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' - .format(ts / 1000000000, fps, - ctx['id'], stream_name, - meta.sequence, meta.bytesused, - crcs)) + for ctx in self.contexts: + ctx.release() - if ctx['opt-metadata']: - reqmeta = req.metadata - for ctrl, val in reqmeta.items(): - print(f'\t{ctrl} = {val}') + def do_cmd_capture(self): + self.__capture_init() - if ctx['opt-save-frames']: - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - filename = 'frame-{}-{}-{}.data'.format(ctx['id'], stream_name, ctx['reqs-completed']) - with open(filename, 'wb') as f: - for p in mfb.planes: - f.write(p) + renderer = self.renderer - state['renderer'].request_handler(ctx, req) + renderer.setup() - ctx['reqs-completed'] += 1 + self.__capture_start() + renderer.run() -# Called from renderer when it has finished with a request -def request_prcessed(ctx, req): - camera = ctx['camera'] + self.__capture_deinit() - if ctx['reqs-queued'] < ctx['opt-capture']: - req.reuse() - camera.queue_request(req) - ctx['reqs-queued'] += 1 +class CustomAction(argparse.Action): + def __init__(self, option_strings, dest, **kwargs): + super().__init__(option_strings, dest, default={}, **kwargs) -def capture_deinit(contexts): - for ctx in contexts: - stop(ctx) + def __call__(self, parser, namespace, values, option_string=None): + if len(namespace.camera) == 0: + print(f'Option {option_string} requires a --camera context') + sys.exit(-1) - for ctx in contexts: - release(ctx) + if self.type == bool: + values = True + current = namespace.camera[-1] -def do_cmd_capture(state): - capture_init(state['contexts']) + data = getattr(namespace, self.dest) - renderer = state['renderer'] + if self.nargs == '+': + if current not in data: + data[current] = [] - renderer.setup() + data[current] += values + else: + data[current] = values - capture_start(state['contexts']) - renderer.run() +def do_cmd_list(cm): + print('Available cameras:') - capture_deinit(state['contexts']) + for idx, c in enumerate(cm.cameras): + print(f'{idx + 1}: {c.id}') def main(): @@ -422,39 +444,28 @@ def main(): print('Unable to find camera', cam_idx) return -1 - contexts.append({ - 'camera': camera, - 'idx': cam_idx, - 'id': 'cam' + str(cam_idx), - 'reqs-queued': 0, - 'reqs-completed': 0, - 'opt-capture': args.capture.get(cam_idx, False), - 'opt-crc': args.crc.get(cam_idx, False), - 'opt-save-frames': args.save_frames.get(cam_idx, False), - 'opt-metadata': args.metadata.get(cam_idx, False), - 'opt-strict-formats': args.strict_formats.get(cam_idx, False), - 'opt-stream': args.stream.get(cam_idx, ['role=viewfinder']), - }) + ctx = CameraContext(camera, cam_idx) + ctx.opt_capture = args.capture.get(cam_idx, 0) + ctx.opt_crc = args.crc.get(cam_idx, False) + ctx.opt_save_frames = args.save_frames.get(cam_idx, False) + ctx.opt_metadata = args.metadata.get(cam_idx, False) + ctx.opt_strict_formats = args.strict_formats.get(cam_idx, False) + ctx.opt_stream = args.stream.get(cam_idx, ['role=viewfinder']) + contexts.append(ctx) for ctx in contexts: - print('Using camera {} as {}'.format(ctx['camera'].id, ctx['id'])) + print('Using camera {} as {}'.format(ctx.camera.id, ctx.id)) for ctx in contexts: if args.list_properties: - do_cmd_list_props(ctx) + ctx.do_cmd_list_props() if args.list_controls: - do_cmd_list_controls(ctx) + ctx.do_cmd_list_controls() if args.info: - do_cmd_info(ctx) + ctx.do_cmd_info() if args.capture: - - state = { - 'cm': cm, - 'contexts': contexts, - 'event_handler': event_handler, - 'request_prcessed': request_prcessed, - } + state = CaptureState(cm, contexts) if args.renderer == 'null': import cam_null @@ -472,9 +483,9 @@ def main(): print('Bad renderer', args.renderer) return -1 - state['renderer'] = renderer + state.renderer = renderer - do_cmd_capture(state) + state.do_cmd_capture() return 0 diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py index 74cd3b38..213e0b03 100644 --- a/src/py/cam/cam_kms.py +++ b/src/py/cam/cam_kms.py @@ -10,8 +10,8 @@ class KMSRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts self.running = False card = pykms.Card() @@ -92,7 +92,7 @@ class KMSRenderer: if old: req = old['camreq'] ctx = old['camctx'] - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) def queue(self, drmreq): if not self.next: @@ -108,7 +108,7 @@ class KMSRenderer: idx = 0 for ctx in self.contexts: - for stream in ctx['streams']: + for stream in ctx.streams: cfg = stream.configuration fmt = cfg.pixel_format @@ -125,7 +125,7 @@ class KMSRenderer: 'size': cfg.size, }) - for fb in ctx['allocator'].buffers(stream): + for fb in ctx.allocator.buffers(stream): w = cfg.size.width h = cfg.size.height fds = [] @@ -148,7 +148,7 @@ class KMSRenderer: self.handle_page_flip(ev.seq, ev.time) def readcam(self, fd): - self.running = self.state['event_handler'](self.state) + self.running = self.state.event_handler() def readkey(self, fileobj): sys.stdin.readline() diff --git a/src/py/cam/cam_null.py b/src/py/cam/cam_null.py index a6da9671..45c5f467 100644 --- a/src/py/cam/cam_null.py +++ b/src/py/cam/cam_null.py @@ -9,8 +9,8 @@ class NullRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts self.running = False @@ -37,11 +37,11 @@ class NullRenderer: print('Exiting...') def readcam(self, fd): - self.running = self.state['event_handler'](self.state) + self.running = self.state.event_handler() def readkey(self, fileobj): sys.stdin.readline() self.running = False def request_handler(self, ctx, req): - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 03096920..d638e9cc 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -176,8 +176,8 @@ class QtRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts def setup(self): self.app = QtWidgets.QApplication([]) @@ -185,7 +185,7 @@ class QtRenderer: windows = [] for ctx in self.contexts: - for stream in ctx['streams']: + for stream in ctx.streams: window = MainWindow(ctx, stream) window.show() windows.append(window) @@ -206,7 +206,7 @@ class QtRenderer: print('Exiting...') def readcam(self): - running = self.state['event_handler'](self.state) + running = self.state.event_handler() if not running: self.app.quit() @@ -223,7 +223,7 @@ class QtRenderer: wnd.handle_request(stream, fb) - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) def cleanup(self): for w in self.windows: @@ -254,7 +254,7 @@ class MainWindow(QtWidgets.QWidget): group.setLayout(groupLayout) controlsLayout.addWidget(group) - lab = QtWidgets.QLabel(ctx['id']) + lab = QtWidgets.QLabel(ctx.id) groupLayout.addWidget(lab) self.frameLabel = QtWidgets.QLabel() @@ -265,7 +265,7 @@ class MainWindow(QtWidgets.QWidget): group.setLayout(groupLayout) controlsLayout.addWidget(group) - camera = ctx['camera'] + camera = ctx.camera for k, v in camera.properties.items(): lab = QtWidgets.QLabel() @@ -308,4 +308,4 @@ class MainWindow(QtWidgets.QWidget): self.label.setPixmap(pix) self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' - .format(ctx['reqs-queued'], ctx['reqs-completed'], ctx['fps'])) + .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps)) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index c9e367a2..5f7ccf1e 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -142,7 +142,7 @@ class QtRenderer: self.window = window def run(self): - camnotif = QtCore.QSocketNotifier(self.state['cm'].efd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.state.cm.efd, QtCore.QSocketNotifier.Read) camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) @@ -155,7 +155,7 @@ class QtRenderer: print('Exiting...') def readcam(self): - running = self.state['event_handler'](self.state) + running = self.state.event_handler() if not running: self.app.quit() @@ -184,12 +184,12 @@ class MainWindow(QtWidgets.QWidget): self.reqqueue = {} self.current = {} - for ctx in self.state['contexts']: + for ctx in self.state.contexts: - self.reqqueue[ctx['idx']] = [] - self.current[ctx['idx']] = [] + self.reqqueue[ctx.idx] = [] + self.current[ctx.idx] = [] - for stream in ctx['streams']: + for stream in ctx.streams: self.textures[stream] = None num_tiles = len(self.textures) @@ -312,12 +312,12 @@ class MainWindow(QtWidgets.QWidget): if len(queue) == 0: continue - ctx = next(ctx for ctx in self.state['contexts'] if ctx['idx'] == ctx_idx) + ctx = next(ctx for ctx in self.state.contexts if ctx.idx == ctx_idx) if self.current[ctx_idx]: old = self.current[ctx_idx] self.current[ctx_idx] = None - self.state['request_prcessed'](ctx, old) + self.state.request_processed(ctx, old) next_req = queue.pop(0) self.current[ctx_idx] = next_req @@ -336,8 +336,8 @@ class MainWindow(QtWidgets.QWidget): size = self.size() - for idx, ctx in enumerate(self.state['contexts']): - for stream in ctx['streams']: + for idx, ctx in enumerate(self.state.contexts): + for stream in ctx.streams: if self.textures[stream] is None: continue @@ -359,5 +359,5 @@ class MainWindow(QtWidgets.QWidget): assert(b) def handle_request(self, ctx, req): - self.reqqueue[ctx['idx']].append(req) + self.reqqueue[ctx.idx].append(req) self.update()