[{"id":31869,"web_url":"https://patchwork.libcamera.org/comment/31869/","msgid":"<172955389784.1470935.4953910999492657243@ping.linuxembedded.co.uk>","date":"2024-10-21T23:38:17","subject":"Re: [PATCH 15 5/7] libcamera: virtual: Add ImageFrameGenerator","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Harvey Yang (2024-10-04 10:59:18)\n> Besides TestPatternGenerator, this patch adds ImageFrameGenerator that\n> loads real images (jpg / jpeg for now) as the source and generates\n> scaled frames.\n> \n> Signed-off-by: Konami Shu <konamiz@google.com>\n> Co-developed-by: Harvey Yang <chenghaoyang@chromium.org>\n> Co-developed-by: Yunke Cao <yunkec@chromium.org>\n> Co-developed-by: Tomasz Figa <tfiga@chromium.org>\n\nMissing SoB's here are failing checkpatch and will prevent merge.\n\n> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>  .../virtual/image_frame_generator.cpp         | 179 ++++++++++++++++++\n>  .../pipeline/virtual/image_frame_generator.h  |  51 +++++\n>  src/libcamera/pipeline/virtual/meson.build    |   4 +\n>  3 files changed, 234 insertions(+)\n>  create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.cpp\n>  create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.h\n> \n> diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp\n> new file mode 100644\n> index 000000000..04382beb7\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp\n> @@ -0,0 +1,179 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Google Inc.\n> + *\n> + * image_frame_generator.cpp - Derived class of FrameGenerator for\n\nPlease drop the filename from the header comments of all added files in\nthe series.\n\n> + * generating frames from images\n> + */\n> +\n> +#include \"image_frame_generator.h\"\n> +\n> +#include <string>\n> +\n> +#include <libcamera/base/file.h>\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/framebuffer.h>\n> +\n> +#include \"libcamera/internal/mapped_framebuffer.h\"\n> +\n> +#include \"libyuv/convert.h\"\n> +#include \"libyuv/scale.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(Virtual)\n> +\n> +/*\n> + * Factory function to create an ImageFrameGenerator object.\n> + * Read the images and convert them to buffers in NV12 format.\n> + * Store the pointers to the buffers to a list (imageFrameDatas)\n> + */\n> +std::unique_ptr<ImageFrameGenerator>\n> +ImageFrameGenerator::create(ImageFrames &imageFrames)\n> +{\n> +       std::unique_ptr<ImageFrameGenerator> imageFrameGenerator =\n> +               std::make_unique<ImageFrameGenerator>();\n> +       imageFrameGenerator->imageFrames_ = &imageFrames;\n> +\n> +       /*\n> +        * For each file in the directory, load the image,\n> +        * convert it to NV12, and store the pointer.\n> +        */\n> +       for (unsigned int i = 0; i < imageFrames.number.value_or(1); i++) {\n> +               std::filesystem::path path;\n> +               if (!imageFrames.number)\n> +                       /* If the path is to an image */\n> +                       path = imageFrames.path;\n> +               else\n> +                       /* If the path is to a directory */\n> +                       path = imageFrames.path / (std::to_string(i) + \".jpg\");\n> +\n> +               File file(path);\n> +               if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> +                       LOG(Virtual, Error) << \"Failed to open image file \" << file.fileName()\n> +                                           << \": \" << strerror(file.error());\n> +                       return nullptr;\n> +               }\n> +\n> +               /* Read the image file to data */\n> +               auto fileSize = file.size();\n> +               auto buffer = std::make_unique<uint8_t[]>(fileSize);\n> +               if (file.read({ buffer.get(), static_cast<size_t>(fileSize) }) != fileSize) {\n> +                       LOG(Virtual, Error) << \"Failed to read file \" << file.fileName()\n> +                                           << \": \" << strerror(file.error());\n> +                       return nullptr;\n> +               }\n> +\n> +               /* Get the width and height of the image */\n> +               int width, height;\n> +               if (libyuv::MJPGSize(buffer.get(), fileSize, &width, &height)) {\n> +                       LOG(Virtual, Error) << \"Failed to get the size of the image file: \"\n> +                                           << file.fileName();\n> +                       return nullptr;\n> +               }\n> +\n> +               std::unique_ptr<uint8_t[]> dstY =\n> +                       std::make_unique<uint8_t[]>(width * height);\n> +               std::unique_ptr<uint8_t[]> dstUV =\n> +                       std::make_unique<uint8_t[]>(width * height / 2);\n> +               int ret = libyuv::MJPGToNV12(buffer.get(), fileSize,\n> +                                            dstY.get(), width, dstUV.get(),\n> +                                            width, width, height, width, height);\n> +               if (ret != 0)\n> +                       LOG(Virtual, Error) << \"MJPGToNV12() failed with \" << ret;\n> +\n> +               imageFrameGenerator->imageFrameDatas_.emplace_back(\n> +                       ImageFrameData{ std::move(dstY), std::move(dstUV),\n> +                                       Size(width, height) });\n> +       }\n> +\n> +       return imageFrameGenerator;\n> +}\n> +\n> +/*\n> + * \\var ImageFrameGenerator::frameRepeat\n> + * \\brief Number of frames to repeat before proceeding to the next frame\n> + */\n> +\n> +/* Scale the buffers for image frames. */\n> +void ImageFrameGenerator::configure(const Size &size)\n> +{\n> +       /* Reset the source images to prevent multiple configuration calls */\n> +       scaledFrameDatas_.clear();\n> +       frameIndex_ = 0;\n> +       parameter_ = 0;\n> +\n> +       for (unsigned int i = 0; i < imageFrames_->number.value_or(1); i++) {\n> +               /* Scale the imageFrameDatas_ to scaledY and scaledUV */\n> +               unsigned int halfSizeWidth = (size.width + 1) / 2;\n> +               unsigned int halfSizeHeight = (size.height + 1) / 2;\n> +               std::unique_ptr<uint8_t[]> scaledY =\n> +                       std::make_unique<uint8_t[]>(size.width * size.height);\n> +               std::unique_ptr<uint8_t[]> scaledUV =\n> +                       std::make_unique<uint8_t[]>(halfSizeWidth * halfSizeHeight * 2);\n> +               auto &src = imageFrameDatas_[i];\n> +\n> +               /*\n> +                * \\todo Some platforms might enforce stride due to GPU.\n> +                 * The width needs to be a multiple of the stride to work\n> +                 * properly for now.\n> +                */\n> +               libyuv::NV12Scale(src.Y.get(), src.size.width,\n> +                                 src.UV.get(), src.size.width,\n> +                                 src.size.width, src.size.height,\n> +                                 scaledY.get(), size.width, scaledUV.get(), size.width,\n> +                                 size.width, size.height, libyuv::FilterMode::kFilterBilinear);\n> +\n> +               scaledFrameDatas_.emplace_back(\n> +                       ImageFrameData{ std::move(scaledY), std::move(scaledUV), size });\n> +       }\n> +}\n> +\n> +int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer)\n> +{\n> +       ASSERT(!scaledFrameDatas_.empty());\n> +\n> +       MappedFrameBuffer mappedFrameBuffer(buffer, MappedFrameBuffer::MapFlag::Write);\n> +\n> +       auto planes = mappedFrameBuffer.planes();\n> +\n> +       /* Loop only around the number of images available */\n> +       frameIndex_ %= imageFrames_->number.value_or(1);\n> +\n> +       /* Write the scaledY and scaledUV to the mapped frame buffer */\n> +       libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width,\n> +                        scaledFrameDatas_[frameIndex_].UV.get(), size.width, planes[0].begin(),\n> +                        size.width, planes[1].begin(), size.width,\n> +                        size.width, size.height);\n> +\n> +       /* Proceed to the next image every 4 frames */\n> +       /* \\todo Consider setting the frameRepeat in the config file  */\n> +       parameter_++;\n> +       if (parameter_ % frameRepeat == 0)\n> +               frameIndex_++;\n> +\n> +       return 0;\n> +}\n> +\n> +/*\n> + * \\var ImageFrameGenerator::imageFrameDatas_\n> + * \\brief List of pointers to the not scaled image buffers\n> + */\n> +\n> +/*\n> + * \\var ImageFrameGenerator::scaledFrameDatas_\n> + * \\brief List of pointers to the scaled image buffers\n> + */\n> +\n> +/*\n> + * \\var ImageFrameGenerator::imageFrames_\n> + * \\brief Pointer to the imageFrames_ in VirtualCameraData\n> + */\n> +\n> +/*\n> + * \\var ImageFrameGenerator::parameter_\n> + * \\brief Speed parameter. Change to the next image every parameter_ frames\n> + */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h\n> new file mode 100644\n> index 000000000..a68094a66\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h\n> @@ -0,0 +1,51 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Google Inc.\n> + *\n> + * image_frame_generator.h - Derived class of FrameGenerator for\n> + * generating frames from images\n> + */\n> +\n> +#pragma once\n> +\n> +#include <filesystem>\n> +#include <memory>\n> +#include <optional>\n> +#include <stdint.h>\n> +#include <sys/types.h>\n> +\n> +#include \"frame_generator.h\"\n> +\n> +namespace libcamera {\n> +\n> +/* Frame configuration provided by the config file */\n> +struct ImageFrames {\n> +       std::filesystem::path path;\n> +       std::optional<unsigned int> number;\n> +};\n> +\n> +class ImageFrameGenerator : public FrameGenerator\n> +{\n> +public:\n> +       static std::unique_ptr<ImageFrameGenerator> create(ImageFrames &imageFrames);\n> +\n> +private:\n> +       static constexpr unsigned int frameRepeat = 4;\n> +\n> +       struct ImageFrameData {\n> +               std::unique_ptr<uint8_t[]> Y;\n> +               std::unique_ptr<uint8_t[]> UV;\n> +               Size size;\n> +       };\n> +\n> +       void configure(const Size &size) override;\n> +       int generateFrame(const Size &size, const FrameBuffer *buffer) override;\n> +\n> +       std::vector<ImageFrameData> imageFrameDatas_;\n> +       std::vector<ImageFrameData> scaledFrameDatas_;\n> +       ImageFrames *imageFrames_;\n> +       unsigned int frameIndex_;\n> +       unsigned int parameter_;\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\n> index 0c537777f..bb38c193c 100644\n> --- a/src/libcamera/pipeline/virtual/meson.build\n> +++ b/src/libcamera/pipeline/virtual/meson.build\n> @@ -1,8 +1,12 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n>  libcamera_internal_sources += files([\n> +    'image_frame_generator.cpp',\n>      'test_pattern_generator.cpp',\n>      'virtual.cpp',\n>  ])\n>  \n> +libjpeg = dependency('libjpeg', required : true)\n> +\n>  libcamera_deps += [libyuv_dep]\n> +libcamera_deps += [libjpeg]\n> -- \n> 2.47.0.rc0.187.ge670bccf7e-goog\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 0CD1AC32A3\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 21 Oct 2024 23:38:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C72D06538B;\n\tTue, 22 Oct 2024 01:38:32 +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 A9E2A65379\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Oct 2024 01:38:21 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 20F8939F;\n\tTue, 22 Oct 2024 01:36:35 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"cbk5CpBH\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1729553795;\n\tbh=VDFDaMDACvZz2G6JLZjR91I7vm660fvTNJhK6PTVYLk=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=cbk5CpBHjRIBL1Tsk2+Xe/XDpuhi1mODf4eTeUaPJRliHspl2Aye/deKHGruRrtto\n\tRpeM4GNU1SEJHslyz/iFUDayLgM/2CizfTdKvMi5Kl1KA6AE5CL079n6WnOLfkNPvU\n\tT8aZWZw2g6N9p62JeWMzPodQinf+WA6qdHdfbwQM=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20241004095946.264537-6-chenghaoyang@google.com>","References":"<20241004095946.264537-1-chenghaoyang@google.com>\n\t<20241004095946.264537-6-chenghaoyang@google.com>","Subject":"Re: [PATCH 15 5/7] libcamera: virtual: Add ImageFrameGenerator","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Harvey Yang <chenghaoyang@google.com>, Konami Shu <konamiz@google.com>, \n\tHarvey Yang <chenghaoyang@chromium.org>, Yunke Cao <yunkec@chromium.org>,\n\tTomasz Figa <tfiga@chromium.org>,\n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Harvey Yang <chenghaoyang@chromium.org>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Tue, 22 Oct 2024 00:38:17 +0100","Message-ID":"<172955389784.1470935.4953910999492657243@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":31870,"web_url":"https://patchwork.libcamera.org/comment/31870/","msgid":"<CAEB1ahuHR6x+E6Rhyn_SpyyRrpOJRDjZCbMZyc-K-5LUbciVZA@mail.gmail.com>","date":"2024-10-22T07:46:01","subject":"Re: [PATCH 15 5/7] libcamera: virtual: Add ImageFrameGenerator","submitter":{"id":117,"url":"https://patchwork.libcamera.org/api/people/117/","name":"Cheng-Hao Yang","email":"chenghaoyang@chromium.org"},"content":"Hi Kieran,\n\nOn Tue, Oct 22, 2024 at 7:38 AM Kieran Bingham\n<kieran.bingham@ideasonboard.com> wrote:\n>\n> Quoting Harvey Yang (2024-10-04 10:59:18)\n> > Besides TestPatternGenerator, this patch adds ImageFrameGenerator that\n> > loads real images (jpg / jpeg for now) as the source and generates\n> > scaled frames.\n> >\n> > Signed-off-by: Konami Shu <konamiz@google.com>\n> > Co-developed-by: Harvey Yang <chenghaoyang@chromium.org>\n> > Co-developed-by: Yunke Cao <yunkec@chromium.org>\n> > Co-developed-by: Tomasz Figa <tfiga@chromium.org>\n>\n> Missing SoB's here are failing checkpatch and will prevent merge.\n\nDone\n\n>\n> > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > ---\n> >  .../virtual/image_frame_generator.cpp         | 179 ++++++++++++++++++\n> >  .../pipeline/virtual/image_frame_generator.h  |  51 +++++\n> >  src/libcamera/pipeline/virtual/meson.build    |   4 +\n> >  3 files changed, 234 insertions(+)\n> >  create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.cpp\n> >  create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.h\n> >\n> > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp\n> > new file mode 100644\n> > index 000000000..04382beb7\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp\n> > @@ -0,0 +1,179 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Google Inc.\n> > + *\n> > + * image_frame_generator.cpp - Derived class of FrameGenerator for\n>\n> Please drop the filename from the header comments of all added files in\n> the series.\n\nDone\n\n\n>\n> > + * generating frames from images\n> > + */\n> > +\n> > +#include \"image_frame_generator.h\"\n> > +\n> > +#include <string>\n> > +\n> > +#include <libcamera/base/file.h>\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include <libcamera/framebuffer.h>\n> > +\n> > +#include \"libcamera/internal/mapped_framebuffer.h\"\n> > +\n> > +#include \"libyuv/convert.h\"\n> > +#include \"libyuv/scale.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DECLARE_CATEGORY(Virtual)\n> > +\n> > +/*\n> > + * Factory function to create an ImageFrameGenerator object.\n> > + * Read the images and convert them to buffers in NV12 format.\n> > + * Store the pointers to the buffers to a list (imageFrameDatas)\n> > + */\n> > +std::unique_ptr<ImageFrameGenerator>\n> > +ImageFrameGenerator::create(ImageFrames &imageFrames)\n> > +{\n> > +       std::unique_ptr<ImageFrameGenerator> imageFrameGenerator =\n> > +               std::make_unique<ImageFrameGenerator>();\n> > +       imageFrameGenerator->imageFrames_ = &imageFrames;\n> > +\n> > +       /*\n> > +        * For each file in the directory, load the image,\n> > +        * convert it to NV12, and store the pointer.\n> > +        */\n> > +       for (unsigned int i = 0; i < imageFrames.number.value_or(1); i++) {\n> > +               std::filesystem::path path;\n> > +               if (!imageFrames.number)\n> > +                       /* If the path is to an image */\n> > +                       path = imageFrames.path;\n> > +               else\n> > +                       /* If the path is to a directory */\n> > +                       path = imageFrames.path / (std::to_string(i) + \".jpg\");\n> > +\n> > +               File file(path);\n> > +               if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> > +                       LOG(Virtual, Error) << \"Failed to open image file \" << file.fileName()\n> > +                                           << \": \" << strerror(file.error());\n> > +                       return nullptr;\n> > +               }\n> > +\n> > +               /* Read the image file to data */\n> > +               auto fileSize = file.size();\n> > +               auto buffer = std::make_unique<uint8_t[]>(fileSize);\n> > +               if (file.read({ buffer.get(), static_cast<size_t>(fileSize) }) != fileSize) {\n> > +                       LOG(Virtual, Error) << \"Failed to read file \" << file.fileName()\n> > +                                           << \": \" << strerror(file.error());\n> > +                       return nullptr;\n> > +               }\n> > +\n> > +               /* Get the width and height of the image */\n> > +               int width, height;\n> > +               if (libyuv::MJPGSize(buffer.get(), fileSize, &width, &height)) {\n> > +                       LOG(Virtual, Error) << \"Failed to get the size of the image file: \"\n> > +                                           << file.fileName();\n> > +                       return nullptr;\n> > +               }\n> > +\n> > +               std::unique_ptr<uint8_t[]> dstY =\n> > +                       std::make_unique<uint8_t[]>(width * height);\n> > +               std::unique_ptr<uint8_t[]> dstUV =\n> > +                       std::make_unique<uint8_t[]>(width * height / 2);\n> > +               int ret = libyuv::MJPGToNV12(buffer.get(), fileSize,\n> > +                                            dstY.get(), width, dstUV.get(),\n> > +                                            width, width, height, width, height);\n> > +               if (ret != 0)\n> > +                       LOG(Virtual, Error) << \"MJPGToNV12() failed with \" << ret;\n> > +\n> > +               imageFrameGenerator->imageFrameDatas_.emplace_back(\n> > +                       ImageFrameData{ std::move(dstY), std::move(dstUV),\n> > +                                       Size(width, height) });\n> > +       }\n> > +\n> > +       return imageFrameGenerator;\n> > +}\n> > +\n> > +/*\n> > + * \\var ImageFrameGenerator::frameRepeat\n> > + * \\brief Number of frames to repeat before proceeding to the next frame\n> > + */\n> > +\n> > +/* Scale the buffers for image frames. */\n> > +void ImageFrameGenerator::configure(const Size &size)\n> > +{\n> > +       /* Reset the source images to prevent multiple configuration calls */\n> > +       scaledFrameDatas_.clear();\n> > +       frameIndex_ = 0;\n> > +       parameter_ = 0;\n> > +\n> > +       for (unsigned int i = 0; i < imageFrames_->number.value_or(1); i++) {\n> > +               /* Scale the imageFrameDatas_ to scaledY and scaledUV */\n> > +               unsigned int halfSizeWidth = (size.width + 1) / 2;\n> > +               unsigned int halfSizeHeight = (size.height + 1) / 2;\n> > +               std::unique_ptr<uint8_t[]> scaledY =\n> > +                       std::make_unique<uint8_t[]>(size.width * size.height);\n> > +               std::unique_ptr<uint8_t[]> scaledUV =\n> > +                       std::make_unique<uint8_t[]>(halfSizeWidth * halfSizeHeight * 2);\n> > +               auto &src = imageFrameDatas_[i];\n> > +\n> > +               /*\n> > +                * \\todo Some platforms might enforce stride due to GPU.\n> > +                 * The width needs to be a multiple of the stride to work\n> > +                 * properly for now.\n> > +                */\n> > +               libyuv::NV12Scale(src.Y.get(), src.size.width,\n> > +                                 src.UV.get(), src.size.width,\n> > +                                 src.size.width, src.size.height,\n> > +                                 scaledY.get(), size.width, scaledUV.get(), size.width,\n> > +                                 size.width, size.height, libyuv::FilterMode::kFilterBilinear);\n> > +\n> > +               scaledFrameDatas_.emplace_back(\n> > +                       ImageFrameData{ std::move(scaledY), std::move(scaledUV), size });\n> > +       }\n> > +}\n> > +\n> > +int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer)\n> > +{\n> > +       ASSERT(!scaledFrameDatas_.empty());\n> > +\n> > +       MappedFrameBuffer mappedFrameBuffer(buffer, MappedFrameBuffer::MapFlag::Write);\n> > +\n> > +       auto planes = mappedFrameBuffer.planes();\n> > +\n> > +       /* Loop only around the number of images available */\n> > +       frameIndex_ %= imageFrames_->number.value_or(1);\n> > +\n> > +       /* Write the scaledY and scaledUV to the mapped frame buffer */\n> > +       libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width,\n> > +                        scaledFrameDatas_[frameIndex_].UV.get(), size.width, planes[0].begin(),\n> > +                        size.width, planes[1].begin(), size.width,\n> > +                        size.width, size.height);\n> > +\n> > +       /* Proceed to the next image every 4 frames */\n> > +       /* \\todo Consider setting the frameRepeat in the config file  */\n> > +       parameter_++;\n> > +       if (parameter_ % frameRepeat == 0)\n> > +               frameIndex_++;\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +/*\n> > + * \\var ImageFrameGenerator::imageFrameDatas_\n> > + * \\brief List of pointers to the not scaled image buffers\n> > + */\n> > +\n> > +/*\n> > + * \\var ImageFrameGenerator::scaledFrameDatas_\n> > + * \\brief List of pointers to the scaled image buffers\n> > + */\n> > +\n> > +/*\n> > + * \\var ImageFrameGenerator::imageFrames_\n> > + * \\brief Pointer to the imageFrames_ in VirtualCameraData\n> > + */\n> > +\n> > +/*\n> > + * \\var ImageFrameGenerator::parameter_\n> > + * \\brief Speed parameter. Change to the next image every parameter_ frames\n> > + */\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h\n> > new file mode 100644\n> > index 000000000..a68094a66\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h\n> > @@ -0,0 +1,51 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Google Inc.\n> > + *\n> > + * image_frame_generator.h - Derived class of FrameGenerator for\n> > + * generating frames from images\n> > + */\n> > +\n> > +#pragma once\n> > +\n> > +#include <filesystem>\n> > +#include <memory>\n> > +#include <optional>\n> > +#include <stdint.h>\n> > +#include <sys/types.h>\n> > +\n> > +#include \"frame_generator.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +/* Frame configuration provided by the config file */\n> > +struct ImageFrames {\n> > +       std::filesystem::path path;\n> > +       std::optional<unsigned int> number;\n> > +};\n> > +\n> > +class ImageFrameGenerator : public FrameGenerator\n> > +{\n> > +public:\n> > +       static std::unique_ptr<ImageFrameGenerator> create(ImageFrames &imageFrames);\n> > +\n> > +private:\n> > +       static constexpr unsigned int frameRepeat = 4;\n> > +\n> > +       struct ImageFrameData {\n> > +               std::unique_ptr<uint8_t[]> Y;\n> > +               std::unique_ptr<uint8_t[]> UV;\n> > +               Size size;\n> > +       };\n> > +\n> > +       void configure(const Size &size) override;\n> > +       int generateFrame(const Size &size, const FrameBuffer *buffer) override;\n> > +\n> > +       std::vector<ImageFrameData> imageFrameDatas_;\n> > +       std::vector<ImageFrameData> scaledFrameDatas_;\n> > +       ImageFrames *imageFrames_;\n> > +       unsigned int frameIndex_;\n> > +       unsigned int parameter_;\n> > +};\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\n> > index 0c537777f..bb38c193c 100644\n> > --- a/src/libcamera/pipeline/virtual/meson.build\n> > +++ b/src/libcamera/pipeline/virtual/meson.build\n> > @@ -1,8 +1,12 @@\n> >  # SPDX-License-Identifier: CC0-1.0\n> >\n> >  libcamera_internal_sources += files([\n> > +    'image_frame_generator.cpp',\n> >      'test_pattern_generator.cpp',\n> >      'virtual.cpp',\n> >  ])\n> >\n> > +libjpeg = dependency('libjpeg', required : true)\n> > +\n> >  libcamera_deps += [libyuv_dep]\n> > +libcamera_deps += [libjpeg]\n> > --\n> > 2.47.0.rc0.187.ge670bccf7e-goog\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 AEEF1C3274\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 22 Oct 2024 07:46:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 26FAC653A9;\n\tTue, 22 Oct 2024 09:46:16 +0200 (CEST)","from mail-lj1-x231.google.com (mail-lj1-x231.google.com\n\t[IPv6:2a00:1450:4864:20::231])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9AFAC6539A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Oct 2024 09:46:13 +0200 (CEST)","by mail-lj1-x231.google.com with SMTP id\n\t38308e7fff4ca-2fb5111747cso59216961fa.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Oct 2024 00:46:13 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=chromium.org header.i=@chromium.org\n\theader.b=\"C/3Ti1cc\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=chromium.org; s=google; t=1729583173; x=1730187973;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=Tihh/X9eMTLiIRklNB9kY+wHWpZa87cY+XcMg4lFD7A=;\n\tb=C/3Ti1cc+3rZ2mlfzCnCjz3P6gost745Ua1O6e54Gzov7rjG+YXaTDJBEgLGh9qnkd\n\tMzhe4knipKVTXs8GrW1qyZDbxOTf0bGvh4HBOhL+pwtX7P2nw7yDBb25mYy4z9sX+wr8\n\tlxGdzzl4zfTZeRnOK+1w44mj/dFUwugvW5CkE=","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1729583173; x=1730187973;\n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=Tihh/X9eMTLiIRklNB9kY+wHWpZa87cY+XcMg4lFD7A=;\n\tb=SQ/m+b8hCuol1+EMNz95EwwhABeaBpLjJsaSEXPi0zYq0hx7I5rBhJdlRoPymuN7PL\n\tqMyFNezUa7DIW83w1XPfgjrOWylX5TSd1hLMDDpC3P0hLJQNWZRJNWc1ThNRfIQsYRf9\n\tNFs6w+d+ZdNgTXGGoIpWF8nSLPL4tS46XmZugiy89ej3FPd55UqTnRJNGniJf6ggTtMV\n\taGhXGtWkqxNpi9jBfuJzJ6LOeLGWM8o8kVCwbdSdGseXcCovQSkYgY7F6P+LXgNCzETq\n\tlsfS7WIuWP9s417GfENksnQj4+NHZL/hj93vudV0q6H/cmQggl/Fay6RghEK+b60DV0B\n\thgHA==","X-Gm-Message-State":"AOJu0YwuLLpsGsV7Vwbv/OvVTjr+4F6cNb/vjIU5C+DCn1rAVczPq4B1\n\tHA1t6uo9ieL6tFlcMn2uP5S2cC8QHRoZN/YlKfB6tDN7QoZcpebiELmrUF+dq+n1Iiyt5P7YRyA\n\tBhuscjsFjuVEjlgfw4lHiZSFu1CvnXJ9HOKF8","X-Google-Smtp-Source":"AGHT+IGpX4Mb4jed2JgMMkaUt13mQ1yjXkdyQQOteT1iK2Fow9GSv2oPkKjMRsI9NX1ImI7iA1Wxan9K9lWJ+5E0MLY=","X-Received":"by 2002:a05:651c:1549:b0:2fb:30d5:669a with SMTP id\n\t38308e7fff4ca-2fc9322fe1fmr7855571fa.0.1729583172640; Tue, 22 Oct 2024\n\t00:46:12 -0700 (PDT)","MIME-Version":"1.0","References":"<20241004095946.264537-1-chenghaoyang@google.com>\n\t<20241004095946.264537-6-chenghaoyang@google.com>\n\t<172955389784.1470935.4953910999492657243@ping.linuxembedded.co.uk>","In-Reply-To":"<172955389784.1470935.4953910999492657243@ping.linuxembedded.co.uk>","From":"Cheng-Hao Yang <chenghaoyang@chromium.org>","Date":"Tue, 22 Oct 2024 15:46:01 +0800","Message-ID":"<CAEB1ahuHR6x+E6Rhyn_SpyyRrpOJRDjZCbMZyc-K-5LUbciVZA@mail.gmail.com>","Subject":"Re: [PATCH 15 5/7] libcamera: virtual: Add ImageFrameGenerator","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tHarvey Yang <chenghaoyang@google.com>, \n\tKonami Shu <konamiz@google.com>, Yunke Cao <yunkec@chromium.org>, \n\tTomasz Figa <tfiga@chromium.org>,\n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]