[{"id":26663,"web_url":"https://patchwork.libcamera.org/comment/26663/","msgid":"<20230319130522.GE10144@pendragon.ideasonboard.com>","date":"2023-03-19T13:05:22","subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Tomi,\n\nThank you for the patch.\n\nOn Sun, Mar 19, 2023 at 01:30:13PM +0200, Tomi Valkeinen via libcamera-devel wrote:\n> Here's something I have found useful a few times.\n> \n> This adds a \"tx\" renderer to cam.py, which sends the frames over the\n> network to a receiver.\n> \n> It also adds a \"cam-rx\" tool (non-libcamera based) which receives the\n> frames and uses PyQt to show them on the screen, usually ran on a PC.\n> \n> This is obviously not super efficient, but on the PC side it doesn't\n> matter. On the TX side, at least RPi4 seemed to work without noticeable\n> lag, but my old 32-bit TI DRA76, when sending three camera streams, the\n> performance dropped to ~5fps. Still, I find that more than enough for\n> most development work.\n> \n> This could be extended to also transmit the metadata.\n\nWhat's the advantage of this approach compared to using GStreamer for\nnetwork streaming ? It feels to me that we're reinventing the wheel a\nbit here.\n\n> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> ---\n>  src/py/cam/cam.py                 |   4 +\n>  src/py/cam/cam_tx.py              |  94 +++++++++++++\n>  src/py/examples/cam-rx.py         | 155 +++++++++++++++++++++\n>  src/py/examples/cam_rx_helpers.py | 223 ++++++++++++++++++++++++++++++\n>  4 files changed, 476 insertions(+)\n>  create mode 100644 src/py/cam/cam_tx.py\n>  create mode 100755 src/py/examples/cam-rx.py\n>  create mode 100644 src/py/examples/cam_rx_helpers.py\n> \n> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\n> index 967a72f5..50f0f8d6 100755\n> --- a/src/py/cam/cam.py\n> +++ b/src/py/cam/cam.py\n> @@ -387,6 +387,7 @@ def main():\n>      parser.add_argument('--list-controls', action='store_true', help='List cameras controls')\n>      parser.add_argument('-I', '--info', action='store_true', help='Display information about stream(s)')\n>      parser.add_argument('-R', '--renderer', default='null', help='Renderer (null, kms, qt, qtgl)')\n> +    parser.add_argument('--rargs', default='', help='Arguments passed to the renderer (pass --help to see help)')\n>  \n>      # per camera options\n>      parser.add_argument('-C', '--capture', nargs='?', type=int, const=1000000, action=CustomAction, help='Capture until interrupted by user or until CAPTURE frames captured')\n> @@ -449,6 +450,9 @@ def main():\n>          elif args.renderer == 'qtgl':\n>              import cam_qtgl\n>              renderer = cam_qtgl.QtRenderer(state)\n> +        elif args.renderer == 'tx':\n> +            import cam_tx\n> +            renderer = cam_tx.TxRenderer(state, args.rargs)\n>          else:\n>              print('Bad renderer', args.renderer)\n>              return -1\n> diff --git a/src/py/cam/cam_tx.py b/src/py/cam/cam_tx.py\n> new file mode 100644\n> index 00000000..3d31c6ef\n> --- /dev/null\n> +++ b/src/py/cam/cam_tx.py\n> @@ -0,0 +1,94 @@\n> +# SPDX-License-Identifier: GPL-2.0-or-later\n> +# Copyright (C) 2023, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> +\n> +import argparse\n> +import libcamera\n> +import libcamera.utils\n> +import selectors\n> +import socket\n> +import struct\n> +import sys\n> +\n> +PORT = 43242\n> +\n> +# ctx-idx, width, height, format, num-planes, plane1, plane2, plane3, plane4\n> +struct_fmt = struct.Struct('<III12pI4I')\n> +\n> +\n> +class TxRenderer:\n> +    def __init__(self, state, ropts):\n> +        parser = argparse.ArgumentParser(prog='TxRenderer')\n> +        parser.add_argument('host', default='localhost', help='Address')\n> +        args = parser.parse_args(ropts.split(' '))\n> +\n> +        self.host = args.host\n> +\n> +        self.state = state\n> +\n> +        self.cm = state.cm\n> +        self.contexts = state.contexts\n> +\n> +        self.running = False\n> +\n> +    def setup(self):\n> +        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n> +        sock.connect((self.host, PORT))\n> +        self.sock = sock\n> +\n> +        buf_mmap_map = {}\n> +\n> +        for ctx in self.contexts:\n> +            for stream in ctx.streams:\n> +                for buf in ctx.allocator.buffers(stream):\n> +                    mfb = libcamera.utils.MappedFrameBuffer(buf).mmap()\n> +                    buf_mmap_map[buf] = mfb\n> +\n> +        self.buf_mmap_map = buf_mmap_map\n> +\n> +    def run(self):\n> +        print('Capturing...')\n> +\n> +        self.running = True\n> +\n> +        sel = selectors.DefaultSelector()\n> +        sel.register(self.cm.event_fd, selectors.EVENT_READ, self.readcam)\n> +        sel.register(sys.stdin, selectors.EVENT_READ, self.readkey)\n> +\n> +        print('Press enter to exit')\n> +\n> +        while self.running:\n> +            events = sel.select()\n> +            for key, _ in events:\n> +                callback = key.data\n> +                callback(key.fileobj)\n> +\n> +        print('Exiting...')\n> +\n> +    def readcam(self, fd):\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> +        buffers = req.buffers\n> +\n> +        for stream, fb in buffers.items():\n> +            mfb = self.buf_mmap_map[fb]\n> +\n> +            plane_sizes = [len(p) for p in mfb.planes] + [0] * (4 - len(mfb.planes))\n> +\n> +            stream_config = stream.configuration\n> +\n> +            hdr = struct_fmt.pack(ctx.idx,\n> +                                  stream_config.size.width, stream_config.size.height,\n> +                                  bytes(str(stream_config.pixel_format), 'ascii'),\n> +                                  len(mfb.planes), *plane_sizes)\n> +\n> +            self.sock.sendall(hdr)\n> +\n> +            for p in mfb.planes:\n> +                self.sock.sendall(p)\n> +\n> +        self.state.request_processed(ctx, req)\n> diff --git a/src/py/examples/cam-rx.py b/src/py/examples/cam-rx.py\n> new file mode 100755\n> index 00000000..a53d59c8\n> --- /dev/null\n> +++ b/src/py/examples/cam-rx.py\n> @@ -0,0 +1,155 @@\n> +#!/usr/bin/env python3\n> +\n> +# SPDX-License-Identifier: BSD-3-Clause\n> +# Copyright (C) 2023, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> +\n> +from cam_rx_helpers import data_to_pix\n> +from PyQt5 import QtCore, QtWidgets\n> +from PyQt5.QtCore import Qt\n> +import PyQt5.QtNetwork\n> +import struct\n> +import sys\n> +import traceback\n> +\n> +PORT = 43242\n> +receivers = []\n> +\n> +struct_fmt = struct.Struct('<III12pI4I')\n> +\n> +\n> +# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these.\n> +def qt_message_handler(msg_type, msg_log_context, msg_string):\n> +    if msg_string.startswith(\"Corrupt JPEG data\"):\n> +        return\n> +\n> +    # For some reason qInstallMessageHandler returns None, so we won't\n> +    # call the old handler\n> +    if old_msg_handler is not None:\n> +        old_msg_handler(msg_type, msg_log_context, msg_string)\n> +    else:\n> +        print(msg_string)\n> +\n> +\n> +old_msg_handler = QtCore.qInstallMessageHandler(qt_message_handler)\n> +\n> +\n> +class Receiver(QtWidgets.QWidget):\n> +    def __init__(self, socket: PyQt5.QtNetwork.QTcpSocket):\n> +        super().__init__()\n> +\n> +        self.name = '{}:{}'.format(socket.peerAddress().toString(), socket.peerPort())\n> +\n> +        print('[{}] Accepted new connection'.format(self.name))\n> +\n> +        self.socket = socket\n> +\n> +        self.socket.readyRead.connect(self.on_ready_read)\n> +        self.socket.disconnected.connect(self.on_disconnected)\n> +        self.socket.error.connect(self.on_error)\n> +\n> +        self.header_buffer = bytearray()\n> +        self.data_buffer = bytearray()\n> +        self.data_size = 0\n> +\n> +        self.state = 0\n> +\n> +        self.resize(1000, 600)\n> +        self.setAttribute(Qt.WA_ShowWithoutActivating)\n> +        self.setWindowFlag(Qt.WindowStaysOnTopHint, True)\n> +\n> +        self.gridLayout = QtWidgets.QGridLayout()\n> +        self.setLayout(self.gridLayout)\n> +\n> +        self.labels = {}\n> +\n> +        self.show()\n> +        print(\"done\")\n> +\n> +    def on_ready_read(self):\n> +        while self.socket.bytesAvailable():\n> +            if self.state == 0:\n> +                data = self.socket.read(struct_fmt.size - len(self.header_buffer))\n> +                self.header_buffer.extend(data)\n> +\n> +                if len(self.header_buffer) == struct_fmt.size:\n> +                    self.on_header()\n> +            else:\n> +                data = self.socket.read(self.data_size - len(self.data_buffer))\n> +                self.data_buffer.extend(data)\n> +\n> +                if len(self.data_buffer) == self.data_size:\n> +                    try:\n> +                        self.on_buffers()\n> +                    except Exception:\n> +                        print(traceback.format_exc())\n> +                        qApp.exit(-1)\n> +                        return\n> +\n> +    def on_header(self):\n> +        self.header_tuple = struct_fmt.unpack_from(self.header_buffer)\n> +        idx, w, h, fmtstr, num_planes, p0, p1, p2, p3 = self.header_tuple\n> +        self.data_size = p0 + p1 + p2 + p3\n> +        self.header_buffer = bytearray()\n> +\n> +        self.state = 1\n> +\n> +    def on_buffers(self):\n> +        idx, w, h, fmtstr, num_planes, p0, p1, p2, p3 = self.header_tuple\n> +        fmt = fmtstr.decode('ascii')\n> +\n> +        print('[{}] cam{} {}x{}-{}'.format(self.name, idx, w, h, fmt))\n> +\n> +        if idx not in self.labels:\n> +            label = QtWidgets.QLabel()\n> +            label.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)\n> +            self.labels[idx] = label\n> +            self.gridLayout.addWidget(label, self.gridLayout.count() // 2, self.gridLayout.count() % 2)\n> +\n> +        label = self.labels[idx]\n> +\n> +        pix = data_to_pix(fmt, w, h, self.data_buffer)\n> +\n> +        pix = pix.scaled(label.width(), label.height(), Qt.AspectRatioMode.KeepAspectRatio,\n> +                         Qt.TransformationMode.FastTransformation)\n> +\n> +        label.setPixmap(pix)\n> +\n> +        self.data_buffer = bytearray()\n> +\n> +        self.state = 0\n> +\n> +    def on_disconnected(self):\n> +        print('[{}] Disconnected'.format(self.name))\n> +        self.close()\n> +        receivers.remove(self)\n> +\n> +    def on_error(self):\n> +        print('[{}] Error: {}'.format(self.name, self.socket.errorString()))\n> +\n> +\n> +def new_connection(tcpServer):\n> +    clientConnection: PyQt5.QtNetwork.QTcpSocket = tcpServer.nextPendingConnection()\n> +    w = Receiver(clientConnection)\n> +    receivers.append(w)\n> +\n> +\n> +def readkey():\n> +    global qApp\n> +    sys.stdin.readline()\n> +    qApp.quit()\n> +\n> +\n> +if __name__ == '__main__':\n> +    global qApp\n> +\n> +    qApp = QtWidgets.QApplication(sys.argv)\n> +    qApp.setQuitOnLastWindowClosed(False)\n> +\n> +    keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)\n> +    keynotif.activated.connect(readkey)\n> +\n> +    tcpServer = PyQt5.QtNetwork.QTcpServer(qApp)\n> +    tcpServer.listen(PyQt5.QtNetwork.QHostAddress('0.0.0.0'), PORT)\n> +    tcpServer.newConnection.connect(lambda: new_connection(tcpServer))\n> +\n> +    sys.exit(qApp.exec_())\n> diff --git a/src/py/examples/cam_rx_helpers.py b/src/py/examples/cam_rx_helpers.py\n> new file mode 100644\n> index 00000000..293eb63d\n> --- /dev/null\n> +++ b/src/py/examples/cam_rx_helpers.py\n> @@ -0,0 +1,223 @@\n> +# SPDX-License-Identifier: BSD-3-Clause\n> +# Copyright (C) 2023, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> +#\n> +# Debayering code based on PiCamera documentation\n> +\n> +from numpy.lib.stride_tricks import as_strided\n> +from PyQt5 import QtGui\n> +import numpy as np\n> +\n> +\n> +def demosaic(data, r0, g0, g1, b0):\n> +    # Separate the components from the Bayer data to RGB planes\n> +\n> +    rgb = np.zeros(data.shape + (3,), dtype=data.dtype)\n> +    rgb[1::2, 0::2, 0] = data[r0[1]::2, r0[0]::2]  # Red\n> +    rgb[0::2, 0::2, 1] = data[g0[1]::2, g0[0]::2]  # Green\n> +    rgb[1::2, 1::2, 1] = data[g1[1]::2, g1[0]::2]  # Green\n> +    rgb[0::2, 1::2, 2] = data[b0[1]::2, b0[0]::2]  # Blue\n> +\n> +    # Below we present a fairly naive de-mosaic method that simply\n> +    # calculates the weighted average of a pixel based on the pixels\n> +    # surrounding it. The weighting is provided by a byte representation of\n> +    # the Bayer filter which we construct first:\n> +\n> +    bayer = np.zeros(rgb.shape, dtype=np.uint8)\n> +    bayer[1::2, 0::2, 0] = 1  # Red\n> +    bayer[0::2, 0::2, 1] = 1  # Green\n> +    bayer[1::2, 1::2, 1] = 1  # Green\n> +    bayer[0::2, 1::2, 2] = 1  # Blue\n> +\n> +    # Allocate an array to hold our output with the same shape as the input\n> +    # data. After this we define the size of window that will be used to\n> +    # calculate each weighted average (3x3). Then we pad out the rgb and\n> +    # bayer arrays, adding blank pixels at their edges to compensate for the\n> +    # size of the window when calculating averages for edge pixels.\n> +\n> +    output = np.empty(rgb.shape, dtype=rgb.dtype)\n> +    window = (3, 3)\n> +    borders = (window[0] - 1, window[1] - 1)\n> +    border = (borders[0] // 2, borders[1] // 2)\n> +\n> +    rgb = np.pad(rgb, [\n> +        (border[0], border[0]),\n> +        (border[1], border[1]),\n> +        (0, 0),\n> +    ], 'constant')\n> +    bayer = np.pad(bayer, [\n> +        (border[0], border[0]),\n> +        (border[1], border[1]),\n> +        (0, 0),\n> +    ], 'constant')\n> +\n> +    # For each plane in the RGB data, we use a nifty numpy trick\n> +    # (as_strided) to construct a view over the plane of 3x3 matrices. We do\n> +    # the same for the bayer array, then use Einstein summation on each\n> +    # (np.sum is simpler, but copies the data so it's slower), and divide\n> +    # the results to get our weighted average:\n> +\n> +    for plane in range(3):\n> +        p = rgb[..., plane]\n> +        b = bayer[..., plane]\n> +\n> +        pview = as_strided(p, shape=(\n> +            p.shape[0] - borders[0],\n> +            p.shape[1] - borders[1]) + window, strides=p.strides * 2)\n> +        bview = as_strided(b, shape=(\n> +            b.shape[0] - borders[0],\n> +            b.shape[1] - borders[1]) + window, strides=b.strides * 2)\n> +        psum = np.einsum('ijkl->ij', pview)\n> +        bsum = np.einsum('ijkl->ij', bview)\n> +        output[..., plane] = psum // bsum\n> +\n> +    return output\n> +\n> +\n> +def convert_raw(data, w, h, fmt):\n> +    bayer_pattern = fmt[1:5]\n> +    bitspp = int(fmt[5:])\n> +\n> +    if bitspp == 8:\n> +        data = data.reshape((h, w))\n> +        data = data.astype(np.uint16)\n> +    elif bitspp in [10, 12]:\n> +        data = data.view(np.uint16)\n> +        data = data.reshape((h, w))\n> +    else:\n> +        raise Exception('Bad bitspp:' + str(bitspp))\n> +\n> +    idx = bayer_pattern.find('R')\n> +    assert(idx != -1)\n> +    r0 = (idx % 2, idx // 2)\n> +\n> +    idx = bayer_pattern.find('G')\n> +    assert(idx != -1)\n> +    g0 = (idx % 2, idx // 2)\n> +\n> +    idx = bayer_pattern.find('G', idx + 1)\n> +    assert(idx != -1)\n> +    g1 = (idx % 2, idx // 2)\n> +\n> +    idx = bayer_pattern.find('B')\n> +    assert(idx != -1)\n> +    b0 = (idx % 2, idx // 2)\n> +\n> +    rgb = demosaic(data, r0, g0, g1, b0)\n> +    rgb = (rgb >> (bitspp - 8)).astype(np.uint8)\n> +\n> +    return rgb\n> +\n> +\n> +def convert_yuv444_to_rgb(yuv):\n> +    m = np.array([\n> +        [1.0, 1.0, 1.0],\n> +        [-0.000007154783816076815, -0.3441331386566162, 1.7720025777816772],\n> +        [1.4019975662231445, -0.7141380310058594, 0.00001542569043522235]\n> +    ])\n> +\n> +    rgb = np.dot(yuv, m)\n> +    rgb[:, :, 0] -= 179.45477266423404\n> +    rgb[:, :, 1] += 135.45870971679688\n> +    rgb[:, :, 2] -= 226.8183044444304\n> +    rgb = rgb.astype(np.uint8)\n> +\n> +    return rgb\n> +\n> +\n> +def convert_yuyv(data, w, h):\n> +    # YUV422\n> +    yuyv = data.reshape((h, w // 2 * 4))\n> +\n> +    # YUV444\n> +    yuv = np.empty((h, w, 3), dtype=np.uint8)\n> +    yuv[:, :, 0] = yuyv[:, 0::2]                    # Y\n> +    yuv[:, :, 1] = yuyv[:, 1::4].repeat(2, axis=1)  # U\n> +    yuv[:, :, 2] = yuyv[:, 3::4].repeat(2, axis=1)  # V\n> +\n> +    return convert_yuv444_to_rgb(yuv)\n> +\n> +\n> +def convert_uyvy(data, w, h):\n> +    # YUV422\n> +    yuyv = data.reshape((h, w // 2 * 4))\n> +\n> +    # YUV444\n> +    yuv = np.empty((h, w, 3), dtype=np.uint8)\n> +    yuv[:, :, 0] = yuyv[:, 1::2]                    # Y\n> +    yuv[:, :, 1] = yuyv[:, 0::4].repeat(2, axis=1)  # U\n> +    yuv[:, :, 2] = yuyv[:, 2::4].repeat(2, axis=1)  # V\n> +\n> +    return convert_yuv444_to_rgb(yuv)\n> +\n> +\n> +def convert_nv12(data, w, h):\n> +    plane1 = data[:w * h]\n> +    plane2 = data[w * h:]\n> +\n> +    y = plane1.reshape((h, w))\n> +    uv = plane2.reshape((h // 2, w // 2, 2))\n> +\n> +    # YUV444\n> +    yuv = np.empty((h, w, 3), dtype=np.uint8)\n> +    yuv[:, :, 0] = y[:, :]                    # Y\n> +    yuv[:, :, 1] = uv[:, :, 0].repeat(2, axis=0).repeat(2, axis=1)  # U\n> +    yuv[:, :, 2] = uv[:, :, 1].repeat(2, axis=0).repeat(2, axis=1)  # V\n> +\n> +    return convert_yuv444_to_rgb(yuv)\n> +\n> +\n> +def to_rgb(fmt, w, h, data):\n> +    if fmt == 'YUYV':\n> +        return convert_yuyv(data, w, h)\n> +\n> +    if fmt == 'UYVY':\n> +        return convert_uyvy(data, w, h)\n> +\n> +    elif fmt == 'NV12':\n> +        return convert_nv12(data, w, h)\n> +\n> +    elif fmt == 'RGB888':\n> +        rgb = data.reshape((h, w, 3))\n> +        rgb[:, :, [0, 1, 2]] = rgb[:, :, [2, 1, 0]]\n> +\n> +    elif fmt == 'BGR888':\n> +        rgb = data.reshape((h, w, 3))\n> +\n> +    elif fmt in ['ARGB8888', 'XRGB8888']:\n> +        rgb = data.reshape((h, w, 4))\n> +        rgb = np.flip(rgb, axis=2)\n> +        # drop alpha component\n> +        rgb = np.delete(rgb, np.s_[0::4], axis=2)\n> +\n> +    elif fmt.startswith('S'):\n> +        return convert_raw(data, w, h, fmt)\n> +\n> +    else:\n> +        raise Exception('Unsupported format ' + fmt)\n> +\n> +    return rgb\n> +\n> +\n> +def data_to_rgb(fmt, w, h, data):\n> +    data = np.frombuffer(data, dtype=np.uint8)\n> +    rgb = to_rgb(fmt, w, h, data)\n> +    return rgb\n> +\n> +\n> +def rgb_to_pix(rgb):\n> +    w = rgb.shape[1]\n> +    h = rgb.shape[0]\n> +    qim = QtGui.QImage(rgb, w, h, QtGui.QImage.Format.Format_RGB888)\n> +    pix = QtGui.QPixmap.fromImage(qim)\n> +    return pix\n> +\n> +\n> +def data_to_pix(fmt, w, h, data):\n> +    if fmt == 'MJPEG':\n> +        pix = QtGui.QPixmap(w, h)\n> +        pix.loadFromData(data)\n> +    else:\n> +        rgb = data_to_rgb(fmt, w, h, data)\n> +        pix = rgb_to_pix(rgb)\n> +\n> +    return pix","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 3B2C7BD80A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 19 Mar 2023 13:05:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 70C9C626E7;\n\tSun, 19 Mar 2023 14:05:23 +0100 (CET)","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 076C1603AC\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 19 Mar 2023 14:05:22 +0100 (CET)","from pendragon.ideasonboard.com (85-76-162-78-nat.elisa-mobile.fi\n\t[85.76.162.78])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0C1A21858;\n\tSun, 19 Mar 2023 14:05:20 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679231123;\n\tbh=ktqiyn+MCFus5/e+oXKT3fFsaqOycfKVPYITvHIcL0Q=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=Dn20/NHs3ir1mL2ETIJklFYg7P5WGaiIwZiVFTRHTo6LwbJ7xfxZ1b5Mo5U7c4Ye/\n\t/UJpWmEBSYX5mrbYDhi64eKjw54BoUqmvYaraM6HnhwlU1MdMEJs83xhbScP8YGruB\n\tcVCBLkxXmo6aSA0p2INJaYQO2mr9pGLsMH8xsXe51y9jSIGQOeMYTu4mNUevdzs6eK\n\tEaSymhVqUXr9L5Z8/yOV6r/9N4KRWO0ch7FmSwyF6dZa2k9/6yLd+WTcP60kNfkJil\n\tRyNhd7YZ9Hjccw2/Y1rO8e6/oGpyoji3VeJxIbabwzNAuye67FLaIvCQIx1s7E396w\n\tcefafM8YDpJeg==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1679231121;\n\tbh=ktqiyn+MCFus5/e+oXKT3fFsaqOycfKVPYITvHIcL0Q=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=qKA/ZgYthR1wzzBggBoDRcUsN3zVC1u2cczNWuyqnUhVrGfYVc/oTy7xe54VyWYGH\n\tBQVUsazYiUR2sH91z+JMp7aW/xZ1Rholq18wORuURTJhXkUzoaogysPQtl2J9AOEO2\n\t1yfkwGNkWoC7TVEtRAfNneyfLYtbaWf3YwDSbzSw="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"qKA/ZgYt\"; dkim-atps=neutral","Date":"Sun, 19 Mar 2023 15:05:22 +0200","To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Message-ID":"<20230319130522.GE10144@pendragon.ideasonboard.com>","References":"<20230319113013.25046-1-tomi.valkeinen@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20230319113013.25046-1-tomi.valkeinen@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","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":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26667,"web_url":"https://patchwork.libcamera.org/comment/26667/","msgid":"<2eba7f11-4437-a0f1-5adb-7f98cbb62a99@ideasonboard.com>","date":"2023-03-19T15:49:46","subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"Hi Laurent,\n\nOn 19/03/2023 15:05, Laurent Pinchart wrote:\n> Hi Tomi,\n> \n> Thank you for the patch.\n> \n> On Sun, Mar 19, 2023 at 01:30:13PM +0200, Tomi Valkeinen via libcamera-devel wrote:\n>> Here's something I have found useful a few times.\n>>\n>> This adds a \"tx\" renderer to cam.py, which sends the frames over the\n>> network to a receiver.\n>>\n>> It also adds a \"cam-rx\" tool (non-libcamera based) which receives the\n>> frames and uses PyQt to show them on the screen, usually ran on a PC.\n>>\n>> This is obviously not super efficient, but on the PC side it doesn't\n>> matter. On the TX side, at least RPi4 seemed to work without noticeable\n>> lag, but my old 32-bit TI DRA76, when sending three camera streams, the\n>> performance dropped to ~5fps. Still, I find that more than enough for\n>> most development work.\n>>\n>> This could be extended to also transmit the metadata.\n> \n> What's the advantage of this approach compared to using GStreamer for\n> network streaming ? It feels to me that we're reinventing the wheel a\n> bit here.\n\nWell, these may not matter to other people, but for me:\n\n- This doesn't need gstreamer\n- This works, whereas I have a lot of trouble getting gstreamer working. \nI did manage the get a few formats rendering locally, but I couldn't get \nanything over a tcp sink.\n- The code is short and trivial, and I have the same TX code working on \nmy v4l2 python test app.\n- With this, I have a trivial way to get the raw frames and metadata (to \nbe implemented =) on my PC and process and study them with python & numpy.\n- This could be used for the \"py: cam.py: Provide live graph of request \nmetadata\"\n\nI should have emphasized that this is a development/testing helper, not \n\"streaming support\".\n\nAlso, doesn't your point apply to any rendering done by cam?\n\n  Tomi","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 13858C0F1B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 19 Mar 2023 15:49:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 50391626DA;\n\tSun, 19 Mar 2023 16:49:51 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DCD34626CA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 19 Mar 2023 16:49:49 +0100 (CET)","from [192.168.1.15] (91-154-32-225.elisa-laajakaista.fi\n\t[91.154.32.225])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 441C6189A;\n\tSun, 19 Mar 2023 16:49:49 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679240991;\n\tbh=b9MCeIRzYePtteR5MgHg5GqA2ZEwiFWXOQNl8pmPOeo=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=grjCuHxpC4a6oh83l2xKkbqHyKI6sOaGFywiHfIHwH50th9wKUObz2QsN+ORJGl8f\n\tDZx8FqTQwQFjwIHplyC9gvapAS5RTZAyE9OOhTKXz8J7VI5AiDDTRKPMFB5snj276C\n\t8tRkg4B6XTHgIB0a85+3BbN55MXk9RROs6Kdylprip2UBPaiZ40JJd9fqTMDZpAR0R\n\t4yYteAAK48Glmw8jkSWc+F/k5MuiT1lTO1C9398JGX1OU+31eOA0Zd4yPDj0mVq5L7\n\tDnHD0wC6XjWq3uW7coq26B+2FtwmDHEqdXUG6lByG5GdcliGs61jbB/FhsMoij4uyg\n\tF4uGxEZ5RkzMA==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1679240989;\n\tbh=b9MCeIRzYePtteR5MgHg5GqA2ZEwiFWXOQNl8pmPOeo=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=UkCe8RaTV5dMWn5WBcAfvtZzhV4OyP9pkqcuaQtZzSlPTKXVpBdqOzokaVtscBApa\n\tlHoOx8utzpVWAx7qMeKSkn/nhf8gdaxDgN+wWNxSHPbNYOYqCqvSRa1R1U4SsX0W/9\n\tuffuuuIGXzDDkx3DjtNx3Frb74rDfkgli76zQZpw="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"UkCe8RaT\"; dkim-atps=neutral","Message-ID":"<2eba7f11-4437-a0f1-5adb-7f98cbb62a99@ideasonboard.com>","Date":"Sun, 19 Mar 2023 17:49:46 +0200","MIME-Version":"1.0","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101\n\tThunderbird/102.8.0","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20230319113013.25046-1-tomi.valkeinen@ideasonboard.com>\n\t<20230319130522.GE10144@pendragon.ideasonboard.com>","Content-Language":"en-US","In-Reply-To":"<20230319130522.GE10144@pendragon.ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","Subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","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":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26672,"web_url":"https://patchwork.libcamera.org/comment/26672/","msgid":"<20230319233315.GA20234@pendragon.ideasonboard.com>","date":"2023-03-19T23:33:15","subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Tomi,\n\nOn Sun, Mar 19, 2023 at 05:49:46PM +0200, Tomi Valkeinen wrote:\n> On 19/03/2023 15:05, Laurent Pinchart wrote:\n> > On Sun, Mar 19, 2023 at 01:30:13PM +0200, Tomi Valkeinen via libcamera-devel wrote:\n> >> Here's something I have found useful a few times.\n> >>\n> >> This adds a \"tx\" renderer to cam.py, which sends the frames over the\n> >> network to a receiver.\n> >>\n> >> It also adds a \"cam-rx\" tool (non-libcamera based) which receives the\n> >> frames and uses PyQt to show them on the screen, usually ran on a PC.\n> >>\n> >> This is obviously not super efficient, but on the PC side it doesn't\n> >> matter. On the TX side, at least RPi4 seemed to work without noticeable\n> >> lag, but my old 32-bit TI DRA76, when sending three camera streams, the\n> >> performance dropped to ~5fps. Still, I find that more than enough for\n> >> most development work.\n> >>\n> >> This could be extended to also transmit the metadata.\n> > \n> > What's the advantage of this approach compared to using GStreamer for\n> > network streaming ? It feels to me that we're reinventing the wheel a\n> > bit here.\n> \n> Well, these may not matter to other people, but for me:\n> \n> - This doesn't need gstreamer\n\nThat's an argument I can't disagree with :-)\n\n> - This works, whereas I have a lot of trouble getting gstreamer working. \n> I did manage the get a few formats rendering locally, but I couldn't get \n> anything over a tcp sink.\n\nThen we need to improve GStreamer support, fixing bugs if any, and\nproviding documentation with sample pipelines for both the TX and RX\nsides.\n\n> - The code is short and trivial, and I have the same TX code working on \n> my v4l2 python test app.\n> - With this, I have a trivial way to get the raw frames and metadata (to \n> be implemented =) on my PC and process and study them with python & numpy.\n> - This could be used for the \"py: cam.py: Provide live graph of request \n> metadata\"\n> \n> I should have emphasized that this is a development/testing helper, not \n> \"streaming support\".\n> \n> Also, doesn't your point apply to any rendering done by cam?\n\nThis leads to the real question: where do we draw the line ? A trivial\nnetwork streaming implementation is, well, trivial, but it will fail in\nvarious ways in various cases. I don't want to end up with a custom\nimplementation of RTSP in cam.py, so where will we stop ?\n\nI would also argue that it would be good to keep the feature set of cam\nand cam.py as close as possible to each other.","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 00054C0F1B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 19 Mar 2023 23:33:13 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 520F2626DA;\n\tMon, 20 Mar 2023 00:33:12 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 641AB603AD\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Mar 2023 00:33:10 +0100 (CET)","from pendragon.ideasonboard.com (213-243-189-158.bb.dnainternet.fi\n\t[213.243.189.158])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 910941373;\n\tMon, 20 Mar 2023 00:33:09 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679268792;\n\tbh=fhCE84HBJJRNJqgJo74jpKjJTNpJ63NKEwCf2NMbSO0=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=GXeW3fz0gGfG11iz9TyFkS0yNH4bGkt1nPRt0OsxoutpBFhBlQeitvHGjQ3v+c1zk\n\tdtKM62+bP961Rk6tc2bxAT5+StYVoxXPkr9R3PJMSptk36g/S2W3d2HQqOGFelc6OR\n\tKthu1iTDospe/1OK6523jm5OVEd1j3b9JFAzX8ehAxOp91kafAA9/PhvzWyt/9uF7z\n\tZi7JdFcP5RJNGa8OsLrJo35GmQVDeTJJAg8M5t3P03Gz/tk0KpPjYtdP6B0mjeH7I7\n\t3dyMwVD8PV0DNuwoADeHQXhOaPOfM/SM5Zkm6LBhyJ+4B/fg75IEbMB6mJj1H0RUU+\n\tgYpXMVDBVod8g==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1679268789;\n\tbh=fhCE84HBJJRNJqgJo74jpKjJTNpJ63NKEwCf2NMbSO0=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=sHZRwnLTuT2Xd0KOhbEgI7JAvS6dgmFxJmA8F7ZGj+A8xizBUY4QPrjvJOrK0z9KK\n\tAjSqVu2GUWiR+EJVBBTSHw6j7PTQzMeks5F+yDZrkBdofXwHQKfiZIShtj3hZ3HM9v\n\tW+xQWWg8X4mFqj07igE2w7l5Iui7eQQUVkkab02c="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"sHZRwnLT\"; dkim-atps=neutral","Date":"Mon, 20 Mar 2023 01:33:15 +0200","To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Message-ID":"<20230319233315.GA20234@pendragon.ideasonboard.com>","References":"<20230319113013.25046-1-tomi.valkeinen@ideasonboard.com>\n\t<20230319130522.GE10144@pendragon.ideasonboard.com>\n\t<2eba7f11-4437-a0f1-5adb-7f98cbb62a99@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<2eba7f11-4437-a0f1-5adb-7f98cbb62a99@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","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":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":26678,"web_url":"https://patchwork.libcamera.org/comment/26678/","msgid":"<102a8b49-760e-ea0b-0e52-131a4f895ab1@ideasonboard.com>","date":"2023-03-20T09:29:55","subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"On 20/03/2023 01:33, Laurent Pinchart wrote:\n> Hi Tomi,\n> \n> On Sun, Mar 19, 2023 at 05:49:46PM +0200, Tomi Valkeinen wrote:\n>> On 19/03/2023 15:05, Laurent Pinchart wrote:\n>>> On Sun, Mar 19, 2023 at 01:30:13PM +0200, Tomi Valkeinen via libcamera-devel wrote:\n>>>> Here's something I have found useful a few times.\n>>>>\n>>>> This adds a \"tx\" renderer to cam.py, which sends the frames over the\n>>>> network to a receiver.\n>>>>\n>>>> It also adds a \"cam-rx\" tool (non-libcamera based) which receives the\n>>>> frames and uses PyQt to show them on the screen, usually ran on a PC.\n>>>>\n>>>> This is obviously not super efficient, but on the PC side it doesn't\n>>>> matter. On the TX side, at least RPi4 seemed to work without noticeable\n>>>> lag, but my old 32-bit TI DRA76, when sending three camera streams, the\n>>>> performance dropped to ~5fps. Still, I find that more than enough for\n>>>> most development work.\n>>>>\n>>>> This could be extended to also transmit the metadata.\n>>>\n>>> What's the advantage of this approach compared to using GStreamer for\n>>> network streaming ? It feels to me that we're reinventing the wheel a\n>>> bit here.\n>>\n>> Well, these may not matter to other people, but for me:\n>>\n>> - This doesn't need gstreamer\n> \n> That's an argument I can't disagree with :-)\n> \n>> - This works, whereas I have a lot of trouble getting gstreamer working.\n>> I did manage the get a few formats rendering locally, but I couldn't get\n>> anything over a tcp sink.\n> \n> Then we need to improve GStreamer support, fixing bugs if any, and\n> providing documentation with sample pipelines for both the TX and RX\n> sides.\n\nI can't disagree with that, but I still feel the gstreamer case and what \nI'm doing here are not really comparable.\n\n>> - The code is short and trivial, and I have the same TX code working on\n>> my v4l2 python test app.\n>> - With this, I have a trivial way to get the raw frames and metadata (to\n>> be implemented =) on my PC and process and study them with python & numpy.\n>> - This could be used for the \"py: cam.py: Provide live graph of request\n>> metadata\"\n>>\n>> I should have emphasized that this is a development/testing helper, not\n>> \"streaming support\".\n>>\n>> Also, doesn't your point apply to any rendering done by cam?\n> \n> This leads to the real question: where do we draw the line ? A trivial\n\nI was referring (also) to cam, not just cam.py. cam has at least kms and \nsdl renderers, seems to support mjpeg, and can save frames to disk. \nCan't gstreamer do all those?\n\n> network streaming implementation is, well, trivial, but it will fail in\n> various ways in various cases. I don't want to end up with a custom\n> implementation of RTSP in cam.py, so where will we stop ?\n\nMe neither, and also, what would be the point? If you're thinking RTSP, \nyou're already on quite a high level. I'm sure gstreamer does that just \nfine (at least supposedly).\n\nThe features I've implemented to cam.py have been quite low level, or as \nlow as is needed to exercise some particular \"core\" feature. KMS \nrendering is quite obvious. Rendering with Qt and GL are also, I think, \n\"core\", even if you need a bunch of dependencies to get there.\n\nWhat I wanted here is a way to get (more or less) real-time data from \nthe device to my PC for analysis/processing, without any extra \nprocessing done on the device. Maybe gstreamer can accomplish the same, \nalthough I'm guessing it can't handle the metadata. But even if it did, \nand even if I did get gstreamer working, considering the amount of code \nin this patch for the network tx and rx, and the added complexity of \ngstreamer, at least for me and my uses the choice is clear.\n\n> I would also argue that it would be good to keep the feature set of cam\n> and cam.py as close as possible to each other.\n\nWell, I thought so too, until I didn't =). Why would it be good? The \nonly reason I come up with is that cam.py serves as a py example, as one \ncan compare to cam. But I don't think that's a very good argument, as \nproper examples are better examples (!). But even more importantly, if \nwe stick to cam features, we will miss all the features that Python \ncould offer us easily, but would be more laborious on C++.\n\nThen again, if cam is supposed to exercise the libcamera API as fully as \npossible (but is it?), doing the same on cam.py makes sense, just to see \nthat we have the important things implemented and that they are usable \nin practice.\n\nBut perhaps this discussion should be more about what do we consider \npart of libcamera, and what should be in separate repositories. I should \nprobably set up my own repo for all kinds of \"stuff\" I use, which help \nmy life, and might be helpful for others, but should never be considered \nas a supported part of libcamera. This patch is probably more on the \n\"stuff\" side. But then we come back to the question of \"what should \ncam.py support\", as I think some features could as well be removed and \nmoved to a \"stuff\" repo.\n\nI do think that small non-production-quality pieces of Py code are \nvaluable. They show how certain things can be done, and serve as \nexamples. And I think the best examples are pieces of code which are not \npure examples, but actually serve some kind of real life use case.\n\nWe probably should also consider moving the python bindings to be \noutside the libcamera repo, so that it would be easy to package them in \na form that can be installed from PyPI. But that probably has to wait \nuntil we have a stable API, which is kind of unfortunate as people seem \nto expect finding the Py support from PyPI.\n\nAlso, I didn't mean this patch to be a complex issue. I just though to \nshare it as I found it helpful =).\n\n  Tomi","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 7F72FC0F1B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 20 Mar 2023 09:30:01 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7F4BD626DC;\n\tMon, 20 Mar 2023 10:30:00 +0100 (CET)","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 4F30361ED1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Mar 2023 10:29:59 +0100 (CET)","from [192.168.1.15] (91-154-32-225.elisa-laajakaista.fi\n\t[91.154.32.225])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A12E11373;\n\tMon, 20 Mar 2023 10:29:58 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1679304600;\n\tbh=t9poe5STDqp8GgPIBLkiOMdbCiZ6R66KJASKtsjx/n0=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=QOqeUr3zRFLsqfExz6Wg+M9MWLiCJJhkotGdTCpt0LC5oQkrmdzJYEqGXwlSVmOC7\n\t1o3NiNTsS1oJPTrF3Gvln3Dk6VJSN+xqd8McafUVK52saJYndjWry1ooQaafcjigdI\n\t8zl6V5Vxk5OkiKybmo7ECMYQwEe2aGGjbuY4CFqF7e+GrO7b/IPpNHH+MXgh6IpSdi\n\t2cErNwLL61rtK5hcMokOPLpDwPunwrQ40v5Qk9JN7DCvTjrng7JJtN7Wlfb8q52nt/\n\tcgnQlX2pSImEikPoF5OQq9WLkMU1R48GkpSgALPHAGBUfEpgsx260kHryXYvSVN7nO\n\tvShakKpYrmsAw==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1679304598;\n\tbh=t9poe5STDqp8GgPIBLkiOMdbCiZ6R66KJASKtsjx/n0=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=QNUBiGX9nY1s2MlM0mn5LTfi6y2/slrbFnIrmEw0sTnXogxPrOQTGr+wbwByfup4Z\n\tmCEf2kaB/FenQeeO2FGLUYiPx0SunqTGKo9rSQOjnvSiRiwrebHLcuW+0fE0uUWHHX\n\tlTYls42ureTe/dfjXL3s8RJjWSgJHD0yJXDJryTQ="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"QNUBiGX9\"; dkim-atps=neutral","Message-ID":"<102a8b49-760e-ea0b-0e52-131a4f895ab1@ideasonboard.com>","Date":"Mon, 20 Mar 2023 11:29:55 +0200","MIME-Version":"1.0","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101\n\tThunderbird/102.8.0","Content-Language":"en-US","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20230319113013.25046-1-tomi.valkeinen@ideasonboard.com>\n\t<20230319130522.GE10144@pendragon.ideasonboard.com>\n\t<2eba7f11-4437-a0f1-5adb-7f98cbb62a99@ideasonboard.com>\n\t<20230319233315.GA20234@pendragon.ideasonboard.com>","In-Reply-To":"<20230319233315.GA20234@pendragon.ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","Subject":"Re: [libcamera-devel] [PATCH] py: cam: Network renderer","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":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]