[{"id":24391,"web_url":"https://patchwork.libcamera.org/comment/24391/","msgid":"<165990009443.1706285.13098319184460660142@Monstersaurus>","date":"2022-08-07T19:21:34","subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Laurent Pinchart via libcamera-devel (2022-08-07 19:01:00)\n> The KMS sink currently displays the frame buffer on the top-left corner\n> of the screen, resulting in either a black area on the bottom and right\n> sides (if the frame buffer is smaller than the display resolution) of in\n> a restricted field of view (if the frame buffer is larger than the\n> display resolution). Improve this by scaling the frame buffer to full\n> screen if supported, and aligning the crop rectangle to the frame buffer\n> center if the field of view needs to be restricted.\n> \n> The implementation test three possible composition options, from best to\n> worst. The tests are performed when the camera is started, as testing\n> atomic commits requires access to frame buffer objects, which are not\n> available at configure time. Changing this would require either a large\n> refactoring of the cam application to provide frame buffers earlier, or\n> extending the KMS API to support testing commits with dummy buffer\n> objects. Both are candidates for later development.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  src/cam/kms_sink.cpp | 92 ++++++++++++++++++++++++++++++++++++++++----\n>  src/cam/kms_sink.h   |  8 ++++\n>  2 files changed, 92 insertions(+), 8 deletions(-)\n> \n> diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp\n> index 16435ede6b6a..2f306955cf51 100644\n> --- a/src/cam/kms_sink.cpp\n> +++ b/src/cam/kms_sink.cpp\n> @@ -284,6 +284,77 @@ int KMSSink::stop()\n>         return FrameSink::stop();\n>  }\n>  \n> +bool KMSSink::testModeSet(DRM::FrameBuffer *drmBuffer,\n> +                         const libcamera::Rectangle &src,\n> +                         const libcamera::Rectangle &dst)\n> +{\n> +       DRM::AtomicRequest drmRequest{ &dev_ };\n> +\n> +       drmRequest.addProperty(connector_, \"CRTC_ID\", crtc_->id());\n> +\n> +       drmRequest.addProperty(crtc_, \"ACTIVE\", 1);\n> +       drmRequest.addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n> +\n> +       drmRequest.addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> +       drmRequest.addProperty(plane_, \"FB_ID\", drmBuffer->id());\n> +       drmRequest.addProperty(plane_, \"SRC_X\", src.x << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_Y\", src.y << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_W\", src.width << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_H\", src.height << 16);\n> +       drmRequest.addProperty(plane_, \"CRTC_X\", dst.x);\n> +       drmRequest.addProperty(plane_, \"CRTC_Y\", dst.y);\n> +       drmRequest.addProperty(plane_, \"CRTC_W\", dst.width);\n> +       drmRequest.addProperty(plane_, \"CRTC_H\", dst.height);\n> +\n> +       return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |\n> +                                 DRM::AtomicRequest::FlagTestOnly);\n> +}\n> +\n> +bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)\n> +{\n> +       /*\n> +        * Test composition options, from most to least desirable, to select the\n> +        * best one.\n> +        */\n> +       const libcamera::Rectangle framebuffer{ size_ };\n> +       const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };\n> +\n> +       /* 1. Scale the frame buffer to full screen. */\n\nCan we preserve aspect ratio?\n\nThat might mean two tests. One that checks if we can 'scale' and center\nto fit the screen (with black/white/whatever) boundaries on the edges of\neither horizontal or vertical.\n\nThen one which scales and crops to fit without borders...\n\n\n> +       libcamera::Rectangle src = libcamera::Rectangle{ size_ };\n> +       libcamera::Rectangle dst = display;\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: full-screen scaled output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       /* 2. Center the frame buffer on the display. */\n> +       src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);\n> +       dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: centered output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       /* 3. Align the frame buffer on the top-left of the display. */\n> +       src = framebuffer.boundedTo(display);\n> +       dst = display.boundedTo(framebuffer);\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: top-left aligned output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       return false;\n> +}\n> +\n>  bool KMSSink::processRequest(libcamera::Request *camRequest)\n>  {\n>         /*\n> @@ -307,20 +378,25 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)\n>  \n>         if (!active_ && !queued_) {\n>                 /* Enable the display pipeline on the first frame. */\n> +               if (!setupComposition(drmBuffer)) {\n> +                       std::cerr << \"Failed to setup composition\" << std::endl;\n> +                       return true;\n> +               }\n> +\n>                 drmRequest->addProperty(connector_, \"CRTC_ID\", crtc_->id());\n>  \n>                 drmRequest->addProperty(crtc_, \"ACTIVE\", 1);\n>                 drmRequest->addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n>  \n>                 drmRequest->addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> -               drmRequest->addProperty(plane_, \"SRC_X\", 0 << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_Y\", 0 << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_W\", size_.width << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_H\", size_.height << 16);\n> -               drmRequest->addProperty(plane_, \"CRTC_X\", 0);\n> -               drmRequest->addProperty(plane_, \"CRTC_Y\", 0);\n> -               drmRequest->addProperty(plane_, \"CRTC_W\", size_.width);\n> -               drmRequest->addProperty(plane_, \"CRTC_H\", size_.height);\n> +               drmRequest->addProperty(plane_, \"SRC_X\", src_.x << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_Y\", src_.y << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_W\", src_.width << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_H\", src_.height << 16);\n> +               drmRequest->addProperty(plane_, \"CRTC_X\", dst_.x);\n> +               drmRequest->addProperty(plane_, \"CRTC_Y\", dst_.y);\n> +               drmRequest->addProperty(plane_, \"CRTC_W\", dst_.width);\n> +               drmRequest->addProperty(plane_, \"CRTC_H\", dst_.height);\n>  \n>                 flags |= DRM::AtomicRequest::FlagAllowModeset;\n>         }\n> diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h\n> index 8f5f08667cea..76c4e611bf85 100644\n> --- a/src/cam/kms_sink.h\n> +++ b/src/cam/kms_sink.h\n> @@ -50,6 +50,11 @@ private:\n>  \n>         int selectPipeline(const libcamera::PixelFormat &format);\n>         int configurePipeline(const libcamera::PixelFormat &format);\n> +       bool testModeSet(DRM::FrameBuffer *drmBuffer,\n> +                        const libcamera::Rectangle &src,\n> +                        const libcamera::Rectangle &dst);\n> +       bool setupComposition(DRM::FrameBuffer *drmBuffer);\n> +\n>         void requestComplete(DRM::AtomicRequest *request);\n>  \n>         DRM::Device dev_;\n> @@ -63,6 +68,9 @@ private:\n>         libcamera::Size size_;\n>         unsigned int stride_;\n>  \n> +       libcamera::Rectangle src_;\n> +       libcamera::Rectangle dst_;\n> +\n>         std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;\n>  \n>         std::mutex lock_;\n> -- \n> Regards,\n> \n> Laurent Pinchart\n>","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 6B9BCBE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun,  7 Aug 2022 19:21:40 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B145B63327;\n\tSun,  7 Aug 2022 21:21:39 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7410663312\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  7 Aug 2022 21:21:37 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F2B3249C;\n\tSun,  7 Aug 2022 21:21:36 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1659900099;\n\tbh=E4iCw7WKiDi5QuYSZJ2/EK5KIM/aoDfdTE1eMcnMQQQ=;\n\th=In-Reply-To:References:To:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=nK6rJpd8PFeiwZfAMA80JRKO9bM2obWf5RV9GPjvC3duoXeMUa/qX7yeeQwLYRuVz\n\t7LNI4SjJRivrwgsgSLZ4iV0PNvhHK/iqeVRbfxzpaGkQuxZagN2XvdjXo2Srkmq+S+\n\trCZmddTMhlh8j2qb2HW6bnga3D0ivdQmYtk3F4fNzsuPGuV9urZQU8f+5DXQsmccAp\n\tsecbPgY6LdWGDWVfQhgEDIliuUVMKxiOGX387Jajx5xS2UKIgCH28yING24CKXlj6X\n\tk+df7Y7MqPMKyLpG7ePLAp4FJ4ir2Z1CsvdaokOeyhTbYsFoa1vJSS4QhSTloqOIBD\n\tNlOKPjjjt+VYw==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1659900097;\n\tbh=E4iCw7WKiDi5QuYSZJ2/EK5KIM/aoDfdTE1eMcnMQQQ=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=S8BBDbmEsuVKVPR0CHnVayXo8EIPrF6hGyz+12seOMaiyweMRFaDupGi7WvwT+lX2\n\tTL8gzElBRIibe3O40v35cQvk5dNSFLrqoXBHpRbtan6xFSsjwoyDuK1x83w5l9ETjy\n\tDFwAm2KjJ9/Zfpog8wcq6vb414NaTwc2/a1CjxZs="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"S8BBDbmE\"; dkim-atps=neutral","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20220807180100.396-4-laurent.pinchart@ideasonboard.com>","References":"<20220807180100.396-1-laurent.pinchart@ideasonboard.com>\n\t<20220807180100.396-4-laurent.pinchart@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Sun, 07 Aug 2022 20:21:34 +0100","Message-ID":"<165990009443.1706285.13098319184460660142@Monstersaurus>","User-Agent":"alot/0.10","Subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","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":"Kieran Bingham via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":24392,"web_url":"https://patchwork.libcamera.org/comment/24392/","msgid":"<YvAT8VUOuKRVT+vj@pendragon.ideasonboard.com>","date":"2022-08-07T19:35:13","subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Kieran,\n\nThank you for the patch.\n\nOn Sun, Aug 07, 2022 at 08:21:34PM +0100, Kieran Bingham wrote:\n> Quoting Laurent Pinchart via libcamera-devel (2022-08-07 19:01:00)\n> > The KMS sink currently displays the frame buffer on the top-left corner\n> > of the screen, resulting in either a black area on the bottom and right\n> > sides (if the frame buffer is smaller than the display resolution) of in\n> > a restricted field of view (if the frame buffer is larger than the\n> > display resolution). Improve this by scaling the frame buffer to full\n> > screen if supported, and aligning the crop rectangle to the frame buffer\n> > center if the field of view needs to be restricted.\n> > \n> > The implementation test three possible composition options, from best to\n> > worst. The tests are performed when the camera is started, as testing\n> > atomic commits requires access to frame buffer objects, which are not\n> > available at configure time. Changing this would require either a large\n> > refactoring of the cam application to provide frame buffers earlier, or\n> > extending the KMS API to support testing commits with dummy buffer\n> > objects. Both are candidates for later development.\n> > \n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  src/cam/kms_sink.cpp | 92 ++++++++++++++++++++++++++++++++++++++++----\n> >  src/cam/kms_sink.h   |  8 ++++\n> >  2 files changed, 92 insertions(+), 8 deletions(-)\n> > \n> > diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp\n> > index 16435ede6b6a..2f306955cf51 100644\n> > --- a/src/cam/kms_sink.cpp\n> > +++ b/src/cam/kms_sink.cpp\n> > @@ -284,6 +284,77 @@ int KMSSink::stop()\n> >         return FrameSink::stop();\n> >  }\n> >  \n> > +bool KMSSink::testModeSet(DRM::FrameBuffer *drmBuffer,\n> > +                         const libcamera::Rectangle &src,\n> > +                         const libcamera::Rectangle &dst)\n> > +{\n> > +       DRM::AtomicRequest drmRequest{ &dev_ };\n> > +\n> > +       drmRequest.addProperty(connector_, \"CRTC_ID\", crtc_->id());\n> > +\n> > +       drmRequest.addProperty(crtc_, \"ACTIVE\", 1);\n> > +       drmRequest.addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n> > +\n> > +       drmRequest.addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> > +       drmRequest.addProperty(plane_, \"FB_ID\", drmBuffer->id());\n> > +       drmRequest.addProperty(plane_, \"SRC_X\", src.x << 16);\n> > +       drmRequest.addProperty(plane_, \"SRC_Y\", src.y << 16);\n> > +       drmRequest.addProperty(plane_, \"SRC_W\", src.width << 16);\n> > +       drmRequest.addProperty(plane_, \"SRC_H\", src.height << 16);\n> > +       drmRequest.addProperty(plane_, \"CRTC_X\", dst.x);\n> > +       drmRequest.addProperty(plane_, \"CRTC_Y\", dst.y);\n> > +       drmRequest.addProperty(plane_, \"CRTC_W\", dst.width);\n> > +       drmRequest.addProperty(plane_, \"CRTC_H\", dst.height);\n> > +\n> > +       return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |\n> > +                                 DRM::AtomicRequest::FlagTestOnly);\n> > +}\n> > +\n> > +bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)\n> > +{\n> > +       /*\n> > +        * Test composition options, from most to least desirable, to select the\n> > +        * best one.\n> > +        */\n> > +       const libcamera::Rectangle framebuffer{ size_ };\n> > +       const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };\n> > +\n> > +       /* 1. Scale the frame buffer to full screen. */\n> \n> Can we preserve aspect ratio?\n> \n> That might mean two tests. One that checks if we can 'scale' and center\n> to fit the screen (with black/white/whatever) boundaries on the edges of\n> either horizontal or vertical.\n> \n> Then one which scales and crops to fit without borders...\n\nI can give it a try.\n\nPreserving the aspect ratio can make the image smaller, which may not\nalways be desirable (I've implemented this series because I want to see\nthe full image on the screen to check the effect of algorithms, so\nmaximizing the size could be better for that use case). It could be nice\nto make this configurable, but to start with, I can preserve the aspect\nratio unconditionally.\n\n> > +       libcamera::Rectangle src = libcamera::Rectangle{ size_ };\n> > +       libcamera::Rectangle dst = display;\n> > +\n> > +       if (testModeSet(drmBuffer, src, dst)) {\n> > +               std::cout << \"KMS: full-screen scaled output\" << std::endl;\n> > +               src_ = src;\n> > +               dst_ = dst;\n> > +               return true;\n> > +       }\n> > +\n> > +       /* 2. Center the frame buffer on the display. */\n> > +       src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);\n> > +       dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);\n> > +\n> > +       if (testModeSet(drmBuffer, src, dst)) {\n> > +               std::cout << \"KMS: centered output\" << std::endl;\n> > +               src_ = src;\n> > +               dst_ = dst;\n> > +               return true;\n> > +       }\n> > +\n> > +       /* 3. Align the frame buffer on the top-left of the display. */\n> > +       src = framebuffer.boundedTo(display);\n> > +       dst = display.boundedTo(framebuffer);\n> > +\n> > +       if (testModeSet(drmBuffer, src, dst)) {\n> > +               std::cout << \"KMS: top-left aligned output\" << std::endl;\n> > +               src_ = src;\n> > +               dst_ = dst;\n> > +               return true;\n> > +       }\n> > +\n> > +       return false;\n> > +}\n> > +\n> >  bool KMSSink::processRequest(libcamera::Request *camRequest)\n> >  {\n> >         /*\n> > @@ -307,20 +378,25 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)\n> >  \n> >         if (!active_ && !queued_) {\n> >                 /* Enable the display pipeline on the first frame. */\n> > +               if (!setupComposition(drmBuffer)) {\n> > +                       std::cerr << \"Failed to setup composition\" << std::endl;\n> > +                       return true;\n> > +               }\n> > +\n> >                 drmRequest->addProperty(connector_, \"CRTC_ID\", crtc_->id());\n> >  \n> >                 drmRequest->addProperty(crtc_, \"ACTIVE\", 1);\n> >                 drmRequest->addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n> >  \n> >                 drmRequest->addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> > -               drmRequest->addProperty(plane_, \"SRC_X\", 0 << 16);\n> > -               drmRequest->addProperty(plane_, \"SRC_Y\", 0 << 16);\n> > -               drmRequest->addProperty(plane_, \"SRC_W\", size_.width << 16);\n> > -               drmRequest->addProperty(plane_, \"SRC_H\", size_.height << 16);\n> > -               drmRequest->addProperty(plane_, \"CRTC_X\", 0);\n> > -               drmRequest->addProperty(plane_, \"CRTC_Y\", 0);\n> > -               drmRequest->addProperty(plane_, \"CRTC_W\", size_.width);\n> > -               drmRequest->addProperty(plane_, \"CRTC_H\", size_.height);\n> > +               drmRequest->addProperty(plane_, \"SRC_X\", src_.x << 16);\n> > +               drmRequest->addProperty(plane_, \"SRC_Y\", src_.y << 16);\n> > +               drmRequest->addProperty(plane_, \"SRC_W\", src_.width << 16);\n> > +               drmRequest->addProperty(plane_, \"SRC_H\", src_.height << 16);\n> > +               drmRequest->addProperty(plane_, \"CRTC_X\", dst_.x);\n> > +               drmRequest->addProperty(plane_, \"CRTC_Y\", dst_.y);\n> > +               drmRequest->addProperty(plane_, \"CRTC_W\", dst_.width);\n> > +               drmRequest->addProperty(plane_, \"CRTC_H\", dst_.height);\n> >  \n> >                 flags |= DRM::AtomicRequest::FlagAllowModeset;\n> >         }\n> > diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h\n> > index 8f5f08667cea..76c4e611bf85 100644\n> > --- a/src/cam/kms_sink.h\n> > +++ b/src/cam/kms_sink.h\n> > @@ -50,6 +50,11 @@ private:\n> >  \n> >         int selectPipeline(const libcamera::PixelFormat &format);\n> >         int configurePipeline(const libcamera::PixelFormat &format);\n> > +       bool testModeSet(DRM::FrameBuffer *drmBuffer,\n> > +                        const libcamera::Rectangle &src,\n> > +                        const libcamera::Rectangle &dst);\n> > +       bool setupComposition(DRM::FrameBuffer *drmBuffer);\n> > +\n> >         void requestComplete(DRM::AtomicRequest *request);\n> >  \n> >         DRM::Device dev_;\n> > @@ -63,6 +68,9 @@ private:\n> >         libcamera::Size size_;\n> >         unsigned int stride_;\n> >  \n> > +       libcamera::Rectangle src_;\n> > +       libcamera::Rectangle dst_;\n> > +\n> >         std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;\n> >  \n> >         std::mutex lock_;","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 682B9C3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun,  7 Aug 2022 19:35:26 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 235626332B;\n\tSun,  7 Aug 2022 21:35:25 +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 41D0363312\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  7 Aug 2022 21:35:24 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 78BE9749;\n\tSun,  7 Aug 2022 21:35:23 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1659900925;\n\tbh=qiyz4u5KMpjUv0WJq2ePegau95y9AVTUlUQAqhf+PLc=;\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=eOL3yrd6p2hASD6qRWhZMyUHGkTuKU2MumsI8r4NxNCEEPhdCP4lBEUcommb+EQ4P\n\t7yRzHEjd/L4KvK0w2ItoH4PgF3tB4HwMuHnG2FlNFlaDiJxfj3bmxvDSvuAAsf62wF\n\turvaF43zEyut64Ox13b/VLjbZQwzV4hcWR7XMEEwmJHVP42Bo8Ikycb018PzdoMddE\n\tBWXV5Bt4iplT30lqo1tr2rEUmeKcH5Lka4rnFgrm84bouxcMovsY1s7OrODh7VzPdO\n\tCj3ujeV1iUtr+Iogr14oKQWxgNuuTwQ/5kGLrpV5NK4+bwbCAxygXxMYZt/WSjT0bZ\n\ttawSTz2D2voXQ==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1659900923;\n\tbh=qiyz4u5KMpjUv0WJq2ePegau95y9AVTUlUQAqhf+PLc=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=j2nW21f68KUhrd7nTacEkyUOCRipyDStIjBW9OufHDbr2ejaPC4R6HyomcQagE2p+\n\tBcS6f84Y3YYpA5lrz0NMr2/OkqUSIJ8Jloy+su9kLdD/IqH7yFqoBxKk0BlykLum5b\n\tgxaiMfOpCeEK046fh5t0wqs3w6XJyEyQSIc3DzJM="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"j2nW21f6\"; dkim-atps=neutral","Date":"Sun, 7 Aug 2022 22:35:13 +0300","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Message-ID":"<YvAT8VUOuKRVT+vj@pendragon.ideasonboard.com>","References":"<20220807180100.396-1-laurent.pinchart@ideasonboard.com>\n\t<20220807180100.396-4-laurent.pinchart@ideasonboard.com>\n\t<165990009443.1706285.13098319184460660142@Monstersaurus>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<165990009443.1706285.13098319184460660142@Monstersaurus>","Subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","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":24428,"web_url":"https://patchwork.libcamera.org/comment/24428/","msgid":"<CAOgh=Fy3+7_7AbM5Re5shiiVKfM+jVRnECWcTTOOmO+aQvoDCQ@mail.gmail.com>","date":"2022-08-08T10:38:42","subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","submitter":{"id":101,"url":"https://patchwork.libcamera.org/api/people/101/","name":"Eric Curtin","email":"ecurtin@redhat.com"},"content":"On Sun, 7 Aug 2022 at 19:01, Laurent Pinchart\n<laurent.pinchart@ideasonboard.com> wrote:\n>\n> The KMS sink currently displays the frame buffer on the top-left corner\n> of the screen, resulting in either a black area on the bottom and right\n> sides (if the frame buffer is smaller than the display resolution) of in\n> a restricted field of view (if the frame buffer is larger than the\n> display resolution). Improve this by scaling the frame buffer to full\n> screen if supported, and aligning the crop rectangle to the frame buffer\n> center if the field of view needs to be restricted.\n>\n> The implementation test three possible composition options, from best to\n> worst. The tests are performed when the camera is started, as testing\n> atomic commits requires access to frame buffer objects, which are not\n> available at configure time. Changing this would require either a large\n> refactoring of the cam application to provide frame buffers earlier, or\n> extending the KMS API to support testing commits with dummy buffer\n> objects. Both are candidates for later development.\n>\n\nLGTM, and also tested successfully.\n\nReviewed-by: Eric Curtin <ecurtin@redhat.com>\n\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  src/cam/kms_sink.cpp | 92 ++++++++++++++++++++++++++++++++++++++++----\n>  src/cam/kms_sink.h   |  8 ++++\n>  2 files changed, 92 insertions(+), 8 deletions(-)\n>\n> diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp\n> index 16435ede6b6a..2f306955cf51 100644\n> --- a/src/cam/kms_sink.cpp\n> +++ b/src/cam/kms_sink.cpp\n> @@ -284,6 +284,77 @@ int KMSSink::stop()\n>         return FrameSink::stop();\n>  }\n>\n> +bool KMSSink::testModeSet(DRM::FrameBuffer *drmBuffer,\n> +                         const libcamera::Rectangle &src,\n> +                         const libcamera::Rectangle &dst)\n> +{\n> +       DRM::AtomicRequest drmRequest{ &dev_ };\n> +\n> +       drmRequest.addProperty(connector_, \"CRTC_ID\", crtc_->id());\n> +\n> +       drmRequest.addProperty(crtc_, \"ACTIVE\", 1);\n> +       drmRequest.addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n> +\n> +       drmRequest.addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> +       drmRequest.addProperty(plane_, \"FB_ID\", drmBuffer->id());\n> +       drmRequest.addProperty(plane_, \"SRC_X\", src.x << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_Y\", src.y << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_W\", src.width << 16);\n> +       drmRequest.addProperty(plane_, \"SRC_H\", src.height << 16);\n> +       drmRequest.addProperty(plane_, \"CRTC_X\", dst.x);\n> +       drmRequest.addProperty(plane_, \"CRTC_Y\", dst.y);\n> +       drmRequest.addProperty(plane_, \"CRTC_W\", dst.width);\n> +       drmRequest.addProperty(plane_, \"CRTC_H\", dst.height);\n> +\n> +       return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |\n> +                                 DRM::AtomicRequest::FlagTestOnly);\n> +}\n> +\n> +bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)\n> +{\n> +       /*\n> +        * Test composition options, from most to least desirable, to select the\n> +        * best one.\n> +        */\n> +       const libcamera::Rectangle framebuffer{ size_ };\n> +       const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };\n> +\n> +       /* 1. Scale the frame buffer to full screen. */\n> +       libcamera::Rectangle src = libcamera::Rectangle{ size_ };\n> +       libcamera::Rectangle dst = display;\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: full-screen scaled output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       /* 2. Center the frame buffer on the display. */\n> +       src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);\n> +       dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: centered output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       /* 3. Align the frame buffer on the top-left of the display. */\n> +       src = framebuffer.boundedTo(display);\n> +       dst = display.boundedTo(framebuffer);\n> +\n> +       if (testModeSet(drmBuffer, src, dst)) {\n> +               std::cout << \"KMS: top-left aligned output\" << std::endl;\n> +               src_ = src;\n> +               dst_ = dst;\n> +               return true;\n> +       }\n> +\n> +       return false;\n> +}\n> +\n>  bool KMSSink::processRequest(libcamera::Request *camRequest)\n>  {\n>         /*\n> @@ -307,20 +378,25 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)\n>\n>         if (!active_ && !queued_) {\n>                 /* Enable the display pipeline on the first frame. */\n> +               if (!setupComposition(drmBuffer)) {\n> +                       std::cerr << \"Failed to setup composition\" << std::endl;\n> +                       return true;\n> +               }\n> +\n>                 drmRequest->addProperty(connector_, \"CRTC_ID\", crtc_->id());\n>\n>                 drmRequest->addProperty(crtc_, \"ACTIVE\", 1);\n>                 drmRequest->addProperty(crtc_, \"MODE_ID\", mode_->toBlob(&dev_));\n>\n>                 drmRequest->addProperty(plane_, \"CRTC_ID\", crtc_->id());\n> -               drmRequest->addProperty(plane_, \"SRC_X\", 0 << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_Y\", 0 << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_W\", size_.width << 16);\n> -               drmRequest->addProperty(plane_, \"SRC_H\", size_.height << 16);\n> -               drmRequest->addProperty(plane_, \"CRTC_X\", 0);\n> -               drmRequest->addProperty(plane_, \"CRTC_Y\", 0);\n> -               drmRequest->addProperty(plane_, \"CRTC_W\", size_.width);\n> -               drmRequest->addProperty(plane_, \"CRTC_H\", size_.height);\n> +               drmRequest->addProperty(plane_, \"SRC_X\", src_.x << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_Y\", src_.y << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_W\", src_.width << 16);\n> +               drmRequest->addProperty(plane_, \"SRC_H\", src_.height << 16);\n> +               drmRequest->addProperty(plane_, \"CRTC_X\", dst_.x);\n> +               drmRequest->addProperty(plane_, \"CRTC_Y\", dst_.y);\n> +               drmRequest->addProperty(plane_, \"CRTC_W\", dst_.width);\n> +               drmRequest->addProperty(plane_, \"CRTC_H\", dst_.height);\n>\n>                 flags |= DRM::AtomicRequest::FlagAllowModeset;\n>         }\n> diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h\n> index 8f5f08667cea..76c4e611bf85 100644\n> --- a/src/cam/kms_sink.h\n> +++ b/src/cam/kms_sink.h\n> @@ -50,6 +50,11 @@ private:\n>\n>         int selectPipeline(const libcamera::PixelFormat &format);\n>         int configurePipeline(const libcamera::PixelFormat &format);\n> +       bool testModeSet(DRM::FrameBuffer *drmBuffer,\n> +                        const libcamera::Rectangle &src,\n> +                        const libcamera::Rectangle &dst);\n> +       bool setupComposition(DRM::FrameBuffer *drmBuffer);\n> +\n>         void requestComplete(DRM::AtomicRequest *request);\n>\n>         DRM::Device dev_;\n> @@ -63,6 +68,9 @@ private:\n>         libcamera::Size size_;\n>         unsigned int stride_;\n>\n> +       libcamera::Rectangle src_;\n> +       libcamera::Rectangle dst_;\n> +\n>         std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;\n>\n>         std::mutex lock_;\n> --\n> Regards,\n>\n> Laurent Pinchart\n>","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 13DF7C3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  8 Aug 2022 10:39:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C56036332C;\n\tMon,  8 Aug 2022 12:39:02 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A1F8663315\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  8 Aug 2022 12:39:01 +0200 (CEST)","from mail-qv1-f69.google.com (mail-qv1-f69.google.com\n\t[209.85.219.69]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n\tus-mta-172-XEvTkgJdPNSDfWKy0IUyPQ-1; Mon, 08 Aug 2022 06:38:59 -0400","by mail-qv1-f69.google.com with SMTP id\n\tf10-20020a05621400ca00b0047752ce4c5cso4207904qvs.5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 08 Aug 2022 03:38:59 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1659955142;\n\tbh=ceXRZ2vAGa4pPX3RuKT02Op+G6EVUS5mMW4K6ukzDSQ=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=bJyUoVMO16nv00twDnN13YG5yVMWd5FdZRmQ7iDD2F3xSQ8/mJmIGHL6M2xq7d/IQ\n\tUTaZ1pyzAUBKx1JaVCesnp60SgnXm99q8+kHx9uHJjqLEGckAkQOf2YHuwMhn/gjLW\n\tJqLU/csSz86I7v+zyRtzP68vwXeiftMFDD7IJ/TBY7qMHXbfM69fWEt5NTV8TnpMow\n\t2hBHdN1+mZ7lUOeHyQNxE+60XbJ3m2ytaX69nvrPmKPh9Kvl3Y8d3yOfZciLWKeUaP\n\tb1i58OsY1un7jfsQHso5ATHdvT4hZs0dd0iMdVNGux6VBmibay2trDvlBew4x3raQw\n\t3Atl4KD0PpcOQ==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1659955140;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=bx0iFYWNPyT7TUpoWNX7J5aQAt+e10sXdwJGQMNSKto=;\n\tb=IPsBxiu4v+bINKA0a8FgVrLp/OZouNSCdFpcv/Nuv8dOvne9p/H/N2BLVpOevVmyjSotXW\n\tl5eS2R8fHIFTeDLQEcj1KP5/o5L8FVeLzJm/o6mefKeEvg83C7jdpl8qgl7EPAZGOcyCgI\n\txauvjjnAg8QAcKw3/rujQzLYA/AAUpc="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=redhat.com\n\theader.i=@redhat.com header.b=\"IPsBxiu4\"; \n\tdkim-atps=neutral","X-MC-Unique":"XEvTkgJdPNSDfWKy0IUyPQ-1","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=bx0iFYWNPyT7TUpoWNX7J5aQAt+e10sXdwJGQMNSKto=;\n\tb=3vBVjcMiwDg1HENgJseWMe0Soy4oVbR5jN/eyc9hnDjOvhrt8a/QmvOF9nHrZz+U6W\n\ts33aEanzL9fdDJ/owDl4k+7qSPoxddkOq9BNVl3cgG73Gos5qmbSHhr4oDM4044Vu+CC\n\tFnigiFTqWCuwoBM7l7CWeNDQb8Wnfjp60urTHs6xA27uYdvcrNfssM8wG1ZQEBDC9ZgS\n\tzjLpbknB3IUIO3ZnWgVMqAw0Fan8eqApJLV3JLbCMFifJLbNWFBySL8w3UI1+yVMDOrv\n\t+vHeGRPY2Gn0fNqRZTqPsl59KA4Plf5+tkxRk+msuhx5+zfK095scsg3k3ZY4mhivdmW\n\t7Pag==","X-Gm-Message-State":"ACgBeo3lBh26lpLBK9Lx/3NQWasV6w01l0DEYEs88eOSbYXKx+ErBu65\n\tWW3NvWVmIxe2H+wsSigBLqp+Cq0Hkf7PaaSjNfLwhBFmfIJxDwiJjNE3rCVfTvAr/YpKd07VST7\n\tHaFAQWMtgZAWZkUaSzK7xbxFYSEZQ0cyLU7UKp7CoQvlBo0CB3w==","X-Received":["by 2002:ac8:5cd0:0:b0:342:eb4f:6e36 with SMTP id\n\ts16-20020ac85cd0000000b00342eb4f6e36mr7447402qta.638.1659955138736; \n\tMon, 08 Aug 2022 03:38:58 -0700 (PDT)","by 2002:ac8:5cd0:0:b0:342:eb4f:6e36 with SMTP id\n\ts16-20020ac85cd0000000b00342eb4f6e36mr7447385qta.638.1659955138490;\n\tMon, 08 Aug 2022 03:38:58 -0700 (PDT)"],"X-Google-Smtp-Source":"AA6agR6ubYCg0UiBaeMUYhkL8o86390SQlBwTleOfdFOsREG7QMYrhvJbEQGVNPImBlQGYvpeO2No5bvUwB0pmNx3LM=","MIME-Version":"1.0","References":"<20220807180100.396-1-laurent.pinchart@ideasonboard.com>\n\t<20220807180100.396-4-laurent.pinchart@ideasonboard.com>","In-Reply-To":"<20220807180100.396-4-laurent.pinchart@ideasonboard.com>","Date":"Mon, 8 Aug 2022 11:38:42 +0100","Message-ID":"<CAOgh=Fy3+7_7AbM5Re5shiiVKfM+jVRnECWcTTOOmO+aQvoDCQ@mail.gmail.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","X-Mimecast-Spam-Score":"0","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH 3/3] cam: kms_sink: Scale the frame\n\tbuffer to full screen if supported","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":"Eric Curtin via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Eric Curtin <ecurtin@redhat.com>","Cc":"libcamera devel <libcamera-devel@lists.libcamera.org>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]