Patch Detail
Show a patch.
GET /api/patches/16020/?format=api
{ "id": 16020, "url": "https://patchwork.libcamera.org/api/patches/16020/?format=api", "web_url": "https://patchwork.libcamera.org/patch/16020/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20220524114610.41848-14-tomi.valkeinen@ideasonboard.com>", "date": "2022-05-24T11:46:04", "name": "[libcamera-devel,v2,13/19] py: cam: Convert ctx and state to classes", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "4e2314e5f7c79066fabfbb1a88c0305aae9fe424", "submitter": { "id": 109, "url": "https://patchwork.libcamera.org/api/people/109/?format=api", "name": "Tomi Valkeinen", "email": "tomi.valkeinen@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/16020/mbox/", "series": [ { "id": 3134, "url": "https://patchwork.libcamera.org/api/series/3134/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3134", "date": "2022-05-24T11:45:51", "name": "More misc Python patches", "version": 2, "mbox": "https://patchwork.libcamera.org/series/3134/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/16020/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/16020/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 75B82C3274\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 24 May 2022 11:46:52 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0FF1F6569F;\n\tTue, 24 May 2022 13:46:52 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DE50E65676\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 24 May 2022 13:46:36 +0200 (CEST)", "from deskari.lan (91-156-85-209.elisa-laajakaista.fi\n\t[91.156.85.209])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CA8E59D;\n\tTue, 24 May 2022 13:46:36 +0200 (CEST)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1653392812;\n\tbh=A0jcK9k2ooiMmYrbeoitDMJX2pKECCk+9tbqDLkqz6Q=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=uszj2TpLUQgBj1tKZpiC8wqsjbzg/0PYTfIM0cHassYt5MW2q3OSbXmtdubaRN2KV\n\t8um90WKSQBFsys2tV6JoLkrjH8R79RBbwo6EVTR6Wy12eV5ZC9tJvVxyjN5UKtXEkG\n\t7Ai6odHLoLxUxfAkor6sKY5BTs+S1+IVARsSypmISbEtteNR2soSRFidqke2avfenL\n\t86M/bX/XrIuTDDV9oosylnzrtpt63vGz441QKgm3hEWKCQp5IktxnUIR6rVt+QjgTu\n\tOfPSe/UieOb2ppDWUkY59gWLJZed1iTcAKuQuphQUieNWGxkSL6hEWCspl6ACNYtCh\n\t6DuB8MJh3WTIw==", "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1653392796;\n\tbh=A0jcK9k2ooiMmYrbeoitDMJX2pKECCk+9tbqDLkqz6Q=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=vRgoqxob49QDInd9um1hTasJXr2zHRp8W6mQQihWbtrbN13XhhLD+IKOh7GH5MKLx\n\tb1klp4cAEczX10rCWXn1Fhl40/3uMazy1Zx3eSQaNvSiKY/M3Hh7RcxZlDO/IEKB2D\n\tCMtolC+z72e+iOgLxOZXUTmAhqfN9GemQgWsz1V0=" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"vRgoqxob\"; dkim-atps=neutral", "To": "libcamera-devel@lists.libcamera.org,\n\tDavid Plowman <david.plowman@raspberrypi.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tJacopo Mondi <jacopo@jmondi.org>", "Date": "Tue, 24 May 2022 14:46:04 +0300", "Message-Id": "<20220524114610.41848-14-tomi.valkeinen@ideasonboard.com>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20220524114610.41848-1-tomi.valkeinen@ideasonboard.com>", "References": "<20220524114610.41848-1-tomi.valkeinen@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v2 13/19] py: cam: Convert ctx and state\n\tto classes", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "From": "Tomi Valkeinen via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>", "Reply-To": "Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>", "Cc": "Tomi Valkeinen <tomi.valkeinen@iki.fi>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "From: Tomi Valkeinen <tomi.valkeinen@iki.fi>\n\nConvert ctx and state dicts to classes. No functional changes.\n\nSigned-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n---\n src/py/cam/cam.py | 585 +++++++++++++++++++++--------------------\n src/py/cam/cam_kms.py | 12 +-\n src/py/cam/cam_null.py | 8 +-\n src/py/cam/cam_qt.py | 16 +-\n src/py/cam/cam_qtgl.py | 22 +-\n 5 files changed, 327 insertions(+), 316 deletions(-)", "diff": "diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\nindex 64f67e86..f6e8232c 100755\n--- a/src/py/cam/cam.py\n+++ b/src/py/cam/cam.py\n@@ -6,6 +6,7 @@\n # \\todo Convert ctx and state dicts to proper classes, and move relevant\n # functions to those classes.\n \n+from typing import Any\n import argparse\n import binascii\n import libcamera as libcam\n@@ -14,379 +15,400 @@ import sys\n import traceback\n \n \n-class CustomAction(argparse.Action):\n- def __init__(self, option_strings, dest, **kwargs):\n- super().__init__(option_strings, dest, default={}, **kwargs)\n+class CameraContext:\n+ camera: libcam.Camera\n+ id: str\n+ idx: int\n \n- def __call__(self, parser, namespace, values, option_string=None):\n- if len(namespace.camera) == 0:\n- print(f'Option {option_string} requires a --camera context')\n- sys.exit(-1)\n+ opt_stream: str\n+ opt_strict_formats: bool\n+ opt_crc: bool\n+ opt_metadata: bool\n+ opt_save_frames: bool\n+ opt_capture: int\n \n- if self.type == bool:\n- values = True\n+ stream_names: dict[libcam.Stream, str]\n+ streams: list[libcam.Stream]\n+ allocator: libcam.FrameBufferAllocator\n+ requests: list[libcam.Request]\n+ reqs_queued: int\n+ reqs_completed: int\n+ last: int = 0\n+ fps: float\n \n- current = namespace.camera[-1]\n+ def __init__(self, camera, idx):\n+ self.camera = camera\n+ self.idx = idx\n+ self.id = 'cam' + str(idx)\n+ self.reqs_queued = 0\n+ self.reqs_completed = 0\n \n- data = getattr(namespace, self.dest)\n+ def do_cmd_list_props(self):\n+ camera = self.camera\n \n- if self.nargs == '+':\n- if current not in data:\n- data[current] = []\n+ print('Properties for', self.id)\n \n- data[current] += values\n- else:\n- data[current] = values\n+ for name, prop in camera.properties.items():\n+ print('\\t{}: {}'.format(name, prop))\n \n+ def do_cmd_list_controls(self):\n+ camera = self.camera\n \n-def do_cmd_list(cm):\n- print('Available cameras:')\n-\n- for idx, c in enumerate(cm.cameras):\n- print(f'{idx + 1}: {c.id}')\n-\n-\n-def do_cmd_list_props(ctx):\n- camera = ctx['camera']\n-\n- print('Properties for', ctx['id'])\n-\n- for name, prop in camera.properties.items():\n- print('\\t{}: {}'.format(name, prop))\n-\n-\n-def do_cmd_list_controls(ctx):\n- camera = ctx['camera']\n-\n- print('Controls for', ctx['id'])\n-\n- for name, prop in camera.controls.items():\n- print('\\t{}: {}'.format(name, prop))\n+ print('Controls for', self.id)\n \n+ for name, prop in camera.controls.items():\n+ print('\\t{}: {}'.format(name, prop))\n \n-def do_cmd_info(ctx):\n- camera = ctx['camera']\n+ def do_cmd_info(self):\n+ camera = self.camera\n \n- print('Stream info for', ctx['id'])\n+ print('Stream info for', self.id)\n \n- roles = [libcam.StreamRole.Viewfinder]\n+ roles = [libcam.StreamRole.Viewfinder]\n \n- camconfig = camera.generate_configuration(roles)\n- if camconfig is None:\n- raise Exception('Generating config failed')\n+ camconfig = camera.generate_configuration(roles)\n+ if camconfig is None:\n+ raise Exception('Generating config failed')\n \n- for i, stream_config in enumerate(camconfig):\n- print('\\t{}: {}'.format(i, stream_config))\n+ for i, stream_config in enumerate(camconfig):\n+ print('\\t{}: {}'.format(i, stream_config))\n \n- formats = stream_config.formats\n- for fmt in formats.pixel_formats:\n- print('\\t * Pixelformat:', fmt, formats.range(fmt))\n+ formats = stream_config.formats\n+ for fmt in formats.pixel_formats:\n+ print('\\t * Pixelformat:', fmt, formats.range(fmt))\n \n- for size in formats.sizes(fmt):\n- print('\\t -', size)\n+ for size in formats.sizes(fmt):\n+ print('\\t -', size)\n \n+ def acquire(self):\n+ camera = self.camera\n \n-def acquire(ctx):\n- camera = ctx['camera']\n+ camera.acquire()\n \n- camera.acquire()\n+ def release(self):\n+ camera = self.camera\n \n+ camera.release()\n \n-def release(ctx):\n- camera = ctx['camera']\n+ def __parse_streams(self):\n+ streams = []\n \n- camera.release()\n+ for stream_desc in self.opt_stream:\n+ stream_opts: dict[str, Any]\n+ stream_opts = {'role': libcam.StreamRole.Viewfinder}\n \n+ for stream_opt in stream_desc.split(','):\n+ if stream_opt == 0:\n+ continue\n \n-def parse_streams(ctx):\n- streams = []\n-\n- for stream_desc in ctx['opt-stream']:\n- stream_opts = {'role': libcam.StreamRole.Viewfinder}\n-\n- for stream_opt in stream_desc.split(','):\n- if stream_opt == 0:\n- continue\n-\n- arr = stream_opt.split('=')\n- if len(arr) != 2:\n- print('Bad stream option', stream_opt)\n- sys.exit(-1)\n-\n- key = arr[0]\n- value = arr[1]\n-\n- if key in ['width', 'height']:\n- value = int(value)\n- elif key == 'role':\n- rolemap = {\n- 'still': libcam.StreamRole.StillCapture,\n- 'raw': libcam.StreamRole.Raw,\n- 'video': libcam.StreamRole.VideoRecording,\n- 'viewfinder': libcam.StreamRole.Viewfinder,\n- }\n-\n- role = rolemap.get(value.lower(), None)\n+ arr = stream_opt.split('=')\n+ if len(arr) != 2:\n+ print('Bad stream option', stream_opt)\n+ sys.exit(-1)\n \n- if role is None:\n- print('Bad stream role', value)\n+ key = arr[0]\n+ value = arr[1]\n+\n+ if key in ['width', 'height']:\n+ value = int(value)\n+ elif key == 'role':\n+ rolemap = {\n+ 'still': libcam.StreamRole.StillCapture,\n+ 'raw': libcam.StreamRole.Raw,\n+ 'video': libcam.StreamRole.VideoRecording,\n+ 'viewfinder': libcam.StreamRole.Viewfinder,\n+ }\n+\n+ role = rolemap.get(value.lower(), None)\n+\n+ if role is None:\n+ print('Bad stream role', value)\n+ sys.exit(-1)\n+\n+ value = role\n+ elif key == 'pixelformat':\n+ pass\n+ else:\n+ print('Bad stream option key', key)\n sys.exit(-1)\n \n- value = role\n- elif key == 'pixelformat':\n- pass\n- else:\n- print('Bad stream option key', key)\n- sys.exit(-1)\n+ stream_opts[key] = value\n \n- stream_opts[key] = value\n+ streams.append(stream_opts)\n \n- streams.append(stream_opts)\n+ return streams\n \n- return streams\n+ def configure(self):\n+ camera = self.camera\n \n+ streams = self.__parse_streams()\n \n-def configure(ctx):\n- camera = ctx['camera']\n+ roles = [opts['role'] for opts in streams]\n \n- streams = parse_streams(ctx)\n+ camconfig = camera.generate_configuration(roles)\n+ if camconfig is None:\n+ raise Exception('Generating config failed')\n \n- roles = [opts['role'] for opts in streams]\n+ for idx, stream_opts in enumerate(streams):\n+ stream_config = camconfig.at(idx)\n \n- camconfig = camera.generate_configuration(roles)\n- if camconfig is None:\n- raise Exception('Generating config failed')\n+ if 'width' in stream_opts:\n+ stream_config.size.width = stream_opts['width']\n \n- for idx, stream_opts in enumerate(streams):\n- stream_config = camconfig.at(idx)\n+ if 'height' in stream_opts:\n+ stream_config.size.height = stream_opts['height']\n \n- if 'width' in stream_opts:\n- stream_config.size.width = stream_opts['width']\n+ if 'pixelformat' in stream_opts:\n+ stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])\n \n- if 'height' in stream_opts:\n- stream_config.size.height = stream_opts['height']\n+ stat = camconfig.validate()\n \n- if 'pixelformat' in stream_opts:\n- stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])\n+ if stat == libcam.CameraConfiguration.Status.Invalid:\n+ print('Camera configuration invalid')\n+ exit(-1)\n+ elif stat == libcam.CameraConfiguration.Status.Adjusted:\n+ if self.opt_strict_formats:\n+ print('Adjusting camera configuration disallowed by --strict-formats argument')\n+ exit(-1)\n \n- stat = camconfig.validate()\n+ print('Camera configuration adjusted')\n \n- if stat == libcam.CameraConfiguration.Status.Invalid:\n- print('Camera configuration invalid')\n- exit(-1)\n- elif stat == libcam.CameraConfiguration.Status.Adjusted:\n- if ctx['opt-strict-formats']:\n- print('Adjusting camera configuration disallowed by --strict-formats argument')\n- exit(-1)\n+ r = camera.configure(camconfig)\n+ if r != 0:\n+ raise Exception('Configure failed')\n \n- print('Camera configuration adjusted')\n+ self.stream_names = {}\n+ self.streams = []\n \n- r = camera.configure(camconfig)\n- if r != 0:\n- raise Exception('Configure failed')\n+ for idx, stream_config in enumerate(camconfig):\n+ stream = stream_config.stream\n+ self.streams.append(stream)\n+ self.stream_names[stream] = 'stream' + str(idx)\n+ print('{}-{}: stream config {}'.format(self.id, self.stream_names[stream], stream.configuration))\n \n- ctx['stream-names'] = {}\n- ctx['streams'] = []\n+ def alloc_buffers(self):\n+ camera = self.camera\n \n- for idx, stream_config in enumerate(camconfig):\n- stream = stream_config.stream\n- ctx['streams'].append(stream)\n- ctx['stream-names'][stream] = 'stream' + str(idx)\n- print('{}-{}: stream config {}'.format(ctx['id'], ctx['stream-names'][stream], stream.configuration))\n+ allocator = libcam.FrameBufferAllocator(camera)\n \n+ for stream in self.streams:\n+ ret = allocator.allocate(stream)\n+ if ret < 0:\n+ print('Cannot allocate buffers')\n+ exit(-1)\n \n-def alloc_buffers(ctx):\n- camera = ctx['camera']\n+ allocated = len(allocator.buffers(stream))\n \n- allocator = libcam.FrameBufferAllocator(camera)\n+ print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated))\n \n- for idx, stream in enumerate(ctx['streams']):\n- ret = allocator.allocate(stream)\n- if ret < 0:\n- print('Cannot allocate buffers')\n- exit(-1)\n+ self.allocator = allocator\n \n- allocated = len(allocator.buffers(stream))\n+ def create_requests(self):\n+ camera = self.camera\n \n- print('{}-{}: Allocated {} buffers'.format(ctx['id'], ctx['stream-names'][stream], allocated))\n+ self.requests = []\n \n- ctx['allocator'] = allocator\n+ # Identify the stream with the least number of buffers\n+ num_bufs = min([len(self.allocator.buffers(stream)) for stream in self.streams])\n \n+ requests = []\n \n-def create_requests(ctx):\n- camera = ctx['camera']\n+ for buf_num in range(num_bufs):\n+ request = camera.create_request(self.idx)\n \n- ctx['requests'] = []\n+ if request is None:\n+ print('Can not create request')\n+ exit(-1)\n \n- # Identify the stream with the least number of buffers\n- num_bufs = min([len(ctx['allocator'].buffers(stream)) for stream in ctx['streams']])\n+ for stream in self.streams:\n+ buffers = self.allocator.buffers(stream)\n+ buffer = buffers[buf_num]\n \n- requests = []\n+ ret = request.add_buffer(stream, buffer)\n+ if ret < 0:\n+ print('Can not set buffer for request')\n+ exit(-1)\n \n- for buf_num in range(num_bufs):\n- request = camera.create_request(ctx['idx'])\n+ requests.append(request)\n \n- if request is None:\n- print('Can not create request')\n- exit(-1)\n+ self.requests = requests\n \n- for stream in ctx['streams']:\n- buffers = ctx['allocator'].buffers(stream)\n- buffer = buffers[buf_num]\n+ def start(self):\n+ camera = self.camera\n \n- ret = request.add_buffer(stream, buffer)\n- if ret < 0:\n- print('Can not set buffer for request')\n- exit(-1)\n+ camera.start()\n \n- requests.append(request)\n+ def stop(self):\n+ camera = self.camera\n \n- ctx['requests'] = requests\n+ camera.stop()\n \n+ def queue_requests(self):\n+ camera = self.camera\n \n-def start(ctx):\n- camera = ctx['camera']\n+ for request in self.requests:\n+ camera.queue_request(request)\n+ self.reqs_queued += 1\n \n- camera.start()\n+ del self.requests\n \n \n-def stop(ctx):\n- camera = ctx['camera']\n+class CaptureState:\n+ cm: libcam.CameraManager\n+ contexts: list[CameraContext]\n+ renderer: Any\n \n- camera.stop()\n+ def __init__(self, cm, contexts):\n+ self.cm = cm\n+ self.contexts = contexts\n \n+ # Called from renderer when there is a libcamera event\n+ def event_handler(self):\n+ try:\n+ cm = self.cm\n+ contexts = self.contexts\n \n-def queue_requests(ctx):\n- camera = ctx['camera']\n+ cm.read_event()\n \n- for request in ctx['requests']:\n- camera.queue_request(request)\n- ctx['reqs-queued'] += 1\n+ reqs = cm.get_ready_requests()\n \n- del ctx['requests']\n+ for req in reqs:\n+ ctx = next(ctx for ctx in contexts if ctx.idx == req.cookie)\n+ self.__request_handler(ctx, req)\n \n+ running = any(ctx.reqs_completed < ctx.opt_capture for ctx in contexts)\n+ return running\n+ except Exception:\n+ traceback.print_exc()\n+ return False\n \n-def capture_init(contexts):\n- for ctx in contexts:\n- acquire(ctx)\n+ def __request_handler(self, ctx, req):\n+ if req.status != libcam.Request.Status.Complete:\n+ raise Exception('{}: Request failed: {}'.format(ctx.id, req.status))\n \n- for ctx in contexts:\n- configure(ctx)\n+ buffers = req.buffers\n \n- for ctx in contexts:\n- alloc_buffers(ctx)\n+ # Compute the frame rate. The timestamp is arbitrarily retrieved from\n+ # the first buffer, as all buffers should have matching timestamps.\n+ ts = buffers[next(iter(buffers))].metadata.timestamp\n+ last = ctx.last\n+ fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0\n+ ctx.last = ts\n+ ctx.fps = fps\n \n- for ctx in contexts:\n- create_requests(ctx)\n+ for stream, fb in buffers.items():\n+ stream_name = ctx.stream_names[stream]\n \n+ crcs = []\n+ if ctx.opt_crc:\n+ with libcamera.utils.MappedFrameBuffer(fb) as mfb:\n+ plane_crcs = [binascii.crc32(p) for p in mfb.planes]\n+ crcs.append(plane_crcs)\n \n-def capture_start(contexts):\n- for ctx in contexts:\n- start(ctx)\n+ meta = fb.metadata\n \n- for ctx in contexts:\n- queue_requests(ctx)\n+ print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}'\n+ .format(ts / 1000000000, fps,\n+ ctx.id, stream_name,\n+ meta.sequence, meta.bytesused,\n+ crcs))\n \n+ if ctx.opt_metadata:\n+ reqmeta = req.metadata\n+ for ctrl, val in reqmeta.items():\n+ print(f'\\t{ctrl} = {val}')\n \n-# Called from renderer when there is a libcamera event\n-def event_handler(state):\n- try:\n- cm = state['cm']\n- contexts = state['contexts']\n+ if ctx.opt_save_frames:\n+ with libcamera.utils.MappedFrameBuffer(fb) as mfb:\n+ filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed)\n+ with open(filename, 'wb') as f:\n+ for p in mfb.planes:\n+ f.write(p)\n \n- cm.read_event()\n+ self.renderer.request_handler(ctx, req)\n \n- reqs = cm.get_ready_requests()\n+ ctx.reqs_completed += 1\n \n- for req in reqs:\n- ctx = next(ctx for ctx in contexts if ctx['idx'] == req.cookie)\n- request_handler(state, ctx, req)\n+ # Called from renderer when it has finished with a request\n+ def request_processed(self, ctx, req):\n+ camera = ctx.camera\n \n- running = any(ctx['reqs-completed'] < ctx['opt-capture'] for ctx in contexts)\n- return running\n- except Exception:\n- traceback.print_exc()\n- return False\n+ if ctx.reqs_queued < ctx.opt_capture:\n+ req.reuse()\n+ camera.queue_request(req)\n+ ctx.reqs_queued += 1\n \n+ def __capture_init(self):\n+ for ctx in self.contexts:\n+ ctx.acquire()\n \n-def request_handler(state, ctx, req):\n- if req.status != libcam.Request.Status.Complete:\n- raise Exception('{}: Request failed: {}'.format(ctx['id'], req.status))\n+ for ctx in self.contexts:\n+ ctx.configure()\n \n- buffers = req.buffers\n+ for ctx in self.contexts:\n+ ctx.alloc_buffers()\n \n- # Compute the frame rate. The timestamp is arbitrarily retrieved from\n- # the first buffer, as all buffers should have matching timestamps.\n- ts = buffers[next(iter(buffers))].metadata.timestamp\n- last = ctx.get('last', 0)\n- fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0\n- ctx['last'] = ts\n- ctx['fps'] = fps\n+ for ctx in self.contexts:\n+ ctx.create_requests()\n \n- for stream, fb in buffers.items():\n- stream_name = ctx['stream-names'][stream]\n+ def __capture_start(self):\n+ for ctx in self.contexts:\n+ ctx.start()\n \n- crcs = []\n- if ctx['opt-crc']:\n- with libcamera.utils.MappedFrameBuffer(fb) as mfb:\n- plane_crcs = [binascii.crc32(p) for p in mfb.planes]\n- crcs.append(plane_crcs)\n+ for ctx in self.contexts:\n+ ctx.queue_requests()\n \n- meta = fb.metadata\n+ def __capture_deinit(self):\n+ for ctx in self.contexts:\n+ ctx.stop()\n \n- print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}'\n- .format(ts / 1000000000, fps,\n- ctx['id'], stream_name,\n- meta.sequence, meta.bytesused,\n- crcs))\n+ for ctx in self.contexts:\n+ ctx.release()\n \n- if ctx['opt-metadata']:\n- reqmeta = req.metadata\n- for ctrl, val in reqmeta.items():\n- print(f'\\t{ctrl} = {val}')\n+ def do_cmd_capture(self):\n+ self.__capture_init()\n \n- if ctx['opt-save-frames']:\n- with libcamera.utils.MappedFrameBuffer(fb) as mfb:\n- filename = 'frame-{}-{}-{}.data'.format(ctx['id'], stream_name, ctx['reqs-completed'])\n- with open(filename, 'wb') as f:\n- for p in mfb.planes:\n- f.write(p)\n+ renderer = self.renderer\n \n- state['renderer'].request_handler(ctx, req)\n+ renderer.setup()\n \n- ctx['reqs-completed'] += 1\n+ self.__capture_start()\n \n+ renderer.run()\n \n-# Called from renderer when it has finished with a request\n-def request_prcessed(ctx, req):\n- camera = ctx['camera']\n+ self.__capture_deinit()\n \n- if ctx['reqs-queued'] < ctx['opt-capture']:\n- req.reuse()\n- camera.queue_request(req)\n- ctx['reqs-queued'] += 1\n \n+class CustomAction(argparse.Action):\n+ def __init__(self, option_strings, dest, **kwargs):\n+ super().__init__(option_strings, dest, default={}, **kwargs)\n \n-def capture_deinit(contexts):\n- for ctx in contexts:\n- stop(ctx)\n+ def __call__(self, parser, namespace, values, option_string=None):\n+ if len(namespace.camera) == 0:\n+ print(f'Option {option_string} requires a --camera context')\n+ sys.exit(-1)\n \n- for ctx in contexts:\n- release(ctx)\n+ if self.type == bool:\n+ values = True\n \n+ current = namespace.camera[-1]\n \n-def do_cmd_capture(state):\n- capture_init(state['contexts'])\n+ data = getattr(namespace, self.dest)\n \n- renderer = state['renderer']\n+ if self.nargs == '+':\n+ if current not in data:\n+ data[current] = []\n \n- renderer.setup()\n+ data[current] += values\n+ else:\n+ data[current] = values\n \n- capture_start(state['contexts'])\n \n- renderer.run()\n+def do_cmd_list(cm):\n+ print('Available cameras:')\n \n- capture_deinit(state['contexts'])\n+ for idx, c in enumerate(cm.cameras):\n+ print(f'{idx + 1}: {c.id}')\n \n \n def main():\n@@ -422,39 +444,28 @@ def main():\n print('Unable to find camera', cam_idx)\n return -1\n \n- contexts.append({\n- 'camera': camera,\n- 'idx': cam_idx,\n- 'id': 'cam' + str(cam_idx),\n- 'reqs-queued': 0,\n- 'reqs-completed': 0,\n- 'opt-capture': args.capture.get(cam_idx, False),\n- 'opt-crc': args.crc.get(cam_idx, False),\n- 'opt-save-frames': args.save_frames.get(cam_idx, False),\n- 'opt-metadata': args.metadata.get(cam_idx, False),\n- 'opt-strict-formats': args.strict_formats.get(cam_idx, False),\n- 'opt-stream': args.stream.get(cam_idx, ['role=viewfinder']),\n- })\n+ ctx = CameraContext(camera, cam_idx)\n+ ctx.opt_capture = args.capture.get(cam_idx, 0)\n+ ctx.opt_crc = args.crc.get(cam_idx, False)\n+ ctx.opt_save_frames = args.save_frames.get(cam_idx, False)\n+ ctx.opt_metadata = args.metadata.get(cam_idx, False)\n+ ctx.opt_strict_formats = args.strict_formats.get(cam_idx, False)\n+ ctx.opt_stream = args.stream.get(cam_idx, ['role=viewfinder'])\n+ contexts.append(ctx)\n \n for ctx in contexts:\n- print('Using camera {} as {}'.format(ctx['camera'].id, ctx['id']))\n+ print('Using camera {} as {}'.format(ctx.camera.id, ctx.id))\n \n for ctx in contexts:\n if args.list_properties:\n- do_cmd_list_props(ctx)\n+ ctx.do_cmd_list_props()\n if args.list_controls:\n- do_cmd_list_controls(ctx)\n+ ctx.do_cmd_list_controls()\n if args.info:\n- do_cmd_info(ctx)\n+ ctx.do_cmd_info()\n \n if args.capture:\n-\n- state = {\n- 'cm': cm,\n- 'contexts': contexts,\n- 'event_handler': event_handler,\n- 'request_prcessed': request_prcessed,\n- }\n+ state = CaptureState(cm, contexts)\n \n if args.renderer == 'null':\n import cam_null\n@@ -472,9 +483,9 @@ def main():\n print('Bad renderer', args.renderer)\n return -1\n \n- state['renderer'] = renderer\n+ state.renderer = renderer\n \n- do_cmd_capture(state)\n+ state.do_cmd_capture()\n \n return 0\n \ndiff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py\nindex 74cd3b38..213e0b03 100644\n--- a/src/py/cam/cam_kms.py\n+++ b/src/py/cam/cam_kms.py\n@@ -10,8 +10,8 @@ class KMSRenderer:\n def __init__(self, state):\n self.state = state\n \n- self.cm = state['cm']\n- self.contexts = state['contexts']\n+ self.cm = state.cm\n+ self.contexts = state.contexts\n self.running = False\n \n card = pykms.Card()\n@@ -92,7 +92,7 @@ class KMSRenderer:\n if old:\n req = old['camreq']\n ctx = old['camctx']\n- self.state['request_prcessed'](ctx, req)\n+ self.state.request_processed(ctx, req)\n \n def queue(self, drmreq):\n if not self.next:\n@@ -108,7 +108,7 @@ class KMSRenderer:\n \n idx = 0\n for ctx in self.contexts:\n- for stream in ctx['streams']:\n+ for stream in ctx.streams:\n \n cfg = stream.configuration\n fmt = cfg.pixel_format\n@@ -125,7 +125,7 @@ class KMSRenderer:\n 'size': cfg.size,\n })\n \n- for fb in ctx['allocator'].buffers(stream):\n+ for fb in ctx.allocator.buffers(stream):\n w = cfg.size.width\n h = cfg.size.height\n fds = []\n@@ -148,7 +148,7 @@ class KMSRenderer:\n self.handle_page_flip(ev.seq, ev.time)\n \n def readcam(self, fd):\n- self.running = self.state['event_handler'](self.state)\n+ self.running = self.state.event_handler()\n \n def readkey(self, fileobj):\n sys.stdin.readline()\ndiff --git a/src/py/cam/cam_null.py b/src/py/cam/cam_null.py\nindex a6da9671..45c5f467 100644\n--- a/src/py/cam/cam_null.py\n+++ b/src/py/cam/cam_null.py\n@@ -9,8 +9,8 @@ class NullRenderer:\n def __init__(self, state):\n self.state = state\n \n- self.cm = state['cm']\n- self.contexts = state['contexts']\n+ self.cm = state.cm\n+ self.contexts = state.contexts\n \n self.running = False\n \n@@ -37,11 +37,11 @@ class NullRenderer:\n print('Exiting...')\n \n def readcam(self, fd):\n- self.running = self.state['event_handler'](self.state)\n+ self.running = self.state.event_handler()\n \n def readkey(self, fileobj):\n sys.stdin.readline()\n self.running = False\n \n def request_handler(self, ctx, req):\n- self.state['request_prcessed'](ctx, req)\n+ self.state.request_processed(ctx, req)\ndiff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py\nindex 03096920..d638e9cc 100644\n--- a/src/py/cam/cam_qt.py\n+++ b/src/py/cam/cam_qt.py\n@@ -176,8 +176,8 @@ class QtRenderer:\n def __init__(self, state):\n self.state = state\n \n- self.cm = state['cm']\n- self.contexts = state['contexts']\n+ self.cm = state.cm\n+ self.contexts = state.contexts\n \n def setup(self):\n self.app = QtWidgets.QApplication([])\n@@ -185,7 +185,7 @@ class QtRenderer:\n windows = []\n \n for ctx in self.contexts:\n- for stream in ctx['streams']:\n+ for stream in ctx.streams:\n window = MainWindow(ctx, stream)\n window.show()\n windows.append(window)\n@@ -206,7 +206,7 @@ class QtRenderer:\n print('Exiting...')\n \n def readcam(self):\n- running = self.state['event_handler'](self.state)\n+ running = self.state.event_handler()\n \n if not running:\n self.app.quit()\n@@ -223,7 +223,7 @@ class QtRenderer:\n \n wnd.handle_request(stream, fb)\n \n- self.state['request_prcessed'](ctx, req)\n+ self.state.request_processed(ctx, req)\n \n def cleanup(self):\n for w in self.windows:\n@@ -254,7 +254,7 @@ class MainWindow(QtWidgets.QWidget):\n group.setLayout(groupLayout)\n controlsLayout.addWidget(group)\n \n- lab = QtWidgets.QLabel(ctx['id'])\n+ lab = QtWidgets.QLabel(ctx.id)\n groupLayout.addWidget(lab)\n \n self.frameLabel = QtWidgets.QLabel()\n@@ -265,7 +265,7 @@ class MainWindow(QtWidgets.QWidget):\n group.setLayout(groupLayout)\n controlsLayout.addWidget(group)\n \n- camera = ctx['camera']\n+ camera = ctx.camera\n \n for k, v in camera.properties.items():\n lab = QtWidgets.QLabel()\n@@ -308,4 +308,4 @@ class MainWindow(QtWidgets.QWidget):\n self.label.setPixmap(pix)\n \n self.frameLabel.setText('Queued: {}\\nDone: {}\\nFps: {:.2f}'\n- .format(ctx['reqs-queued'], ctx['reqs-completed'], ctx['fps']))\n+ .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps))\ndiff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py\nindex c9e367a2..5f7ccf1e 100644\n--- a/src/py/cam/cam_qtgl.py\n+++ b/src/py/cam/cam_qtgl.py\n@@ -142,7 +142,7 @@ class QtRenderer:\n self.window = window\n \n def run(self):\n- camnotif = QtCore.QSocketNotifier(self.state['cm'].efd, QtCore.QSocketNotifier.Read)\n+ camnotif = QtCore.QSocketNotifier(self.state.cm.efd, QtCore.QSocketNotifier.Read)\n camnotif.activated.connect(lambda _: self.readcam())\n \n keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)\n@@ -155,7 +155,7 @@ class QtRenderer:\n print('Exiting...')\n \n def readcam(self):\n- running = self.state['event_handler'](self.state)\n+ running = self.state.event_handler()\n \n if not running:\n self.app.quit()\n@@ -184,12 +184,12 @@ class MainWindow(QtWidgets.QWidget):\n self.reqqueue = {}\n self.current = {}\n \n- for ctx in self.state['contexts']:\n+ for ctx in self.state.contexts:\n \n- self.reqqueue[ctx['idx']] = []\n- self.current[ctx['idx']] = []\n+ self.reqqueue[ctx.idx] = []\n+ self.current[ctx.idx] = []\n \n- for stream in ctx['streams']:\n+ for stream in ctx.streams:\n self.textures[stream] = None\n \n num_tiles = len(self.textures)\n@@ -312,12 +312,12 @@ class MainWindow(QtWidgets.QWidget):\n if len(queue) == 0:\n continue\n \n- ctx = next(ctx for ctx in self.state['contexts'] if ctx['idx'] == ctx_idx)\n+ ctx = next(ctx for ctx in self.state.contexts if ctx.idx == ctx_idx)\n \n if self.current[ctx_idx]:\n old = self.current[ctx_idx]\n self.current[ctx_idx] = None\n- self.state['request_prcessed'](ctx, old)\n+ self.state.request_processed(ctx, old)\n \n next_req = queue.pop(0)\n self.current[ctx_idx] = next_req\n@@ -336,8 +336,8 @@ class MainWindow(QtWidgets.QWidget):\n \n size = self.size()\n \n- for idx, ctx in enumerate(self.state['contexts']):\n- for stream in ctx['streams']:\n+ for idx, ctx in enumerate(self.state.contexts):\n+ for stream in ctx.streams:\n if self.textures[stream] is None:\n continue\n \n@@ -359,5 +359,5 @@ class MainWindow(QtWidgets.QWidget):\n assert(b)\n \n def handle_request(self, ctx, req):\n- self.reqqueue[ctx['idx']].append(req)\n+ self.reqqueue[ctx.idx].append(req)\n self.update()\n", "prefixes": [ "libcamera-devel", "v2", "13/19" ] }