[{"id":31400,"web_url":"https://patchwork.libcamera.org/comment/31400/","msgid":"<qc5ljdapvdoh4oxbql4efrga6qlsrbe5scxu367yhk4taqhmde@po2c2usnenjg>","date":"2024-09-26T11:18:47","subject":"Re: [PATCH v12 4/7] libcamera: pipeline: Add test pattern for\n\tVirtualPipelineHandler","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Harvey\n\nOn Tue, Sep 10, 2024 at 04:40:17AM GMT, Harvey Yang wrote:\n> From: Konami Shu <konamiz@google.com>\n>\n> Add a test pattern generator class hierarchy for the Virtual\n> pipeline handler.\n>\n> Implement two types of test patterns: color bars and diagonal lines\n> generator and use them in the Virtual pipeline handler.\n>\n> A shifting mechanism is enabled. For each frame, the image is shifted to\n> the left by 1 pixel. It drops FPS though.\n>\n> Add a dependency for libyuv to the build system to generate images\n> in NV12 format from the test pattern.\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> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>  src/android/meson.build                       |  19 ---\n>  .../pipeline/virtual/frame_generator.h        |  29 ++++\n>  src/libcamera/pipeline/virtual/meson.build    |   3 +\n>  .../virtual/test_pattern_generator.cpp        | 135 ++++++++++++++++++\n>  .../pipeline/virtual/test_pattern_generator.h |  53 +++++++\n>  src/libcamera/pipeline/virtual/virtual.cpp    |  38 ++++-\n>  src/libcamera/pipeline/virtual/virtual.h      |   5 +\n>  src/meson.build                               |  19 +++\n>  8 files changed, 279 insertions(+), 22 deletions(-)\n>  create mode 100644 src/libcamera/pipeline/virtual/frame_generator.h\n>  create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n>  create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.h\n>\n> diff --git a/src/android/meson.build b/src/android/meson.build\n> index 68646120..6341ee8b 100644\n> --- a/src/android/meson.build\n> +++ b/src/android/meson.build\n> @@ -15,25 +15,6 @@ foreach dep : android_deps\n>      endif\n>  endforeach\n>\n> -libyuv_dep = dependency('libyuv', required : false)\n> -\n> -# Fallback to a subproject if libyuv isn't found, as it's typically not\n> -# provided by distributions.\n> -if not libyuv_dep.found()\n> -    cmake = import('cmake')\n> -\n> -    libyuv_vars = cmake.subproject_options()\n> -    libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})\n> -    libyuv_vars.set_override_option('cpp_std', 'c++17')\n> -    libyuv_vars.append_compile_args('cpp',\n> -         '-Wno-sign-compare',\n> -         '-Wno-unused-variable',\n> -         '-Wno-unused-parameter')\n> -    libyuv_vars.append_link_args('-ljpeg')\n> -    libyuv = cmake.subproject('libyuv', options : libyuv_vars)\n> -    libyuv_dep = libyuv.dependency('yuv')\n> -endif\n> -\n>  android_deps += [libyuv_dep]\n>\n>  android_hal_sources = files([\n> diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h\n> new file mode 100644\n> index 00000000..d8727b8f\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/virtual/frame_generator.h\n> @@ -0,0 +1,29 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Google Inc.\n> + *\n> + * frame_generator.h - Virtual cameras helper to generate frames\n> + */\n> +\n> +#pragma once\n> +\n> +#include <libcamera/framebuffer.h>\n> +#include <libcamera/geometry.h>\n> +\n> +namespace libcamera {\n> +\n> +class FrameGenerator\n> +{\n> +public:\n> +\tvirtual ~FrameGenerator() = default;\n> +\n> +\tvirtual void configure(const Size &size) = 0;\n> +\n> +\tvirtual void generateFrame(const Size &size,\n> +\t\t\t\t   const FrameBuffer *buffer) = 0;\n> +\n> +protected:\n> +\tFrameGenerator() {}\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\n> index ada1b335..0c537777 100644\n> --- a/src/libcamera/pipeline/virtual/meson.build\n> +++ b/src/libcamera/pipeline/virtual/meson.build\n> @@ -1,5 +1,8 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libcamera_internal_sources += files([\n> +    'test_pattern_generator.cpp',\n>      'virtual.cpp',\n>  ])\n> +\n> +libcamera_deps += [libyuv_dep]\n> diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n> new file mode 100644\n> index 00000000..f84ab2f3\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n> @@ -0,0 +1,135 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Google Inc.\n> + *\n> + * test_pattern_generator.cpp - Derived class of FrameGenerator for\n> + * generating test patterns\n> + */\n> +\n> +#include \"test_pattern_generator.h\"\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/internal/mapped_framebuffer.h\"\n> +\n> +#include <libyuv/convert_from_argb.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(Virtual)\n> +\n> +static const unsigned int kARGBSize = 4;\n> +\n> +void TestPatternGenerator::generateFrame(const Size &size,\n\nI wonder if it's worth returning an error to the caller and complete\nthe buffer in error state in the pipeline handler in that case.\n\n> +\t\t\t\t\t const FrameBuffer *buffer)\n> +{\n> +\tMappedFrameBuffer mappedFrameBuffer(buffer,\n> +\t\t\t\t\t    MappedFrameBuffer::MapFlag::Write);\n> +\n> +\tauto planes = mappedFrameBuffer.planes();\n> +\n> +\tshiftLeft(size);\n> +\n> +\t/* Convert the template_ to the frame buffer */\n> +\tint ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize,\n> +\t\t\t\t     planes[0].begin(), size.width,\n> +\t\t\t\t     planes[1].begin(), size.width,\n> +\t\t\t\t     size.width, size.height);\n> +\tif (ret != 0)\n> +\t\tLOG(Virtual, Error) << \"ARGBToNV12() failed with \" << ret;\n> +}\n> +\n> +void TestPatternGenerator::shiftLeft(const Size &size)\n> +{\n> +\t/* Store the first column temporarily */\n> +\tauto firstColumn = std::make_unique<uint8_t[]>(size.height * kARGBSize);\n> +\tfor (size_t h = 0; h < size.height; h++) {\n> +\t\tunsigned int index = h * size.width * kARGBSize;\n> +\t\tunsigned int index1 = h * kARGBSize;\n> +\t\tfirstColumn[index1] = template_[index];\n> +\t\tfirstColumn[index1 + 1] = template_[index + 1];\n> +\t\tfirstColumn[index1 + 2] = template_[index + 2];\n> +\t\tfirstColumn[index1 + 3] = 0x00;\n> +\t}\n> +\n> +\t/* Overwrite template_ */\n> +\tuint8_t *buf = template_.get();\n> +\tfor (size_t h = 0; h < size.height; h++) {\n> +\t\tfor (size_t w = 0; w < size.width - 1; w++) {\n> +\t\t\t/* Overwrite with the pixel on the right */\n> +\t\t\tunsigned int index = (h * size.width + w + 1) * kARGBSize;\n> +\t\t\t*buf++ = template_[index]; /* B */\n> +\t\t\t*buf++ = template_[index + 1]; /* G */\n> +\t\t\t*buf++ = template_[index + 2]; /* R */\n> +\t\t\t*buf++ = 0x00; /* A */\n> +\t\t}\n> +\t\t/* Overwrite the new last column with the original first column */\n> +\t\tunsigned int index1 = h * kARGBSize;\n> +\t\t*buf++ = firstColumn[index1]; /* B */\n> +\t\t*buf++ = firstColumn[index1 + 1]; /* G */\n> +\t\t*buf++ = firstColumn[index1 + 2]; /* R */\n> +\t\t*buf++ = 0x00; /* A */\n> +\t}\n> +}\n> +\n> +void ColorBarsGenerator::configure(const Size &size)\n> +{\n> +\tconstexpr uint8_t kColorBar[8][3] = {\n> +\t\t/*  R,    G,    B */\n> +\t\t{ 0xff, 0xff, 0xff }, /* White */\n> +\t\t{ 0xff, 0xff, 0x00 }, /* Yellow */\n> +\t\t{ 0x00, 0xff, 0xff }, /* Cyan */\n> +\t\t{ 0x00, 0xff, 0x00 }, /* Green */\n> +\t\t{ 0xff, 0x00, 0xff }, /* Magenta */\n> +\t\t{ 0xff, 0x00, 0x00 }, /* Red */\n> +\t\t{ 0x00, 0x00, 0xff }, /* Blue */\n> +\t\t{ 0x00, 0x00, 0x00 }, /* Black */\n> +\t};\n> +\n> +\ttemplate_ = std::make_unique<uint8_t[]>(\n> +\t\tsize.width * size.height * kARGBSize);\n> +\n> +\tunsigned int colorBarWidth = size.width / std::size(kColorBar);\n> +\n> +\tuint8_t *buf = template_.get();\n> +\tfor (size_t h = 0; h < size.height; h++) {\n> +\t\tfor (size_t w = 0; w < size.width; w++) {\n> +\t\t\t/* Repeat when the width is exceed */\n> +\t\t\tunsigned int index = (w / colorBarWidth) % std::size(kColorBar);\n> +\n> +\t\t\t*buf++ = kColorBar[index][2]; /* B */\n> +\t\t\t*buf++ = kColorBar[index][1]; /* G */\n> +\t\t\t*buf++ = kColorBar[index][0]; /* R */\n> +\t\t\t*buf++ = 0x00; /* A */\n> +\t\t}\n> +\t}\n> +}\n> +\n> +void DiagonalLinesGenerator::configure(const Size &size)\n> +{\n> +\tconstexpr uint8_t kColorBar[8][3] = {\n> +\t\t/*  R,    G,    B */\n> +\t\t{ 0xff, 0xff, 0xff }, /* White */\n> +\t\t{ 0x00, 0x00, 0x00 }, /* Black */\n> +\t};\n> +\n> +\ttemplate_ = std::make_unique<uint8_t[]>(\n> +\t\tsize.width * size.height * kARGBSize);\n> +\n> +\tunsigned int lineWidth = size.width / 10;\n> +\n> +\tuint8_t *buf = template_.get();\n> +\tfor (size_t h = 0; h < size.height; h++) {\n> +\t\tfor (size_t w = 0; w < size.width; w++) {\n> +\t\t\t/* Repeat when the width is exceed */\n> +\t\t\tint index = ((w + h) / lineWidth) % 2;\n> +\n> +\t\t\t*buf++ = kColorBar[index][2]; /* B */\n> +\t\t\t*buf++ = kColorBar[index][1]; /* G */\n> +\t\t\t*buf++ = kColorBar[index][0]; /* R */\n> +\t\t\t*buf++ = 0x00; /* A */\n> +\t\t}\n> +\t}\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h\n> new file mode 100644\n> index 00000000..b8bbe0b8\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h\n> @@ -0,0 +1,53 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Google Inc.\n> + *\n> + * test_pattern_generator.h - Derived class of FrameGenerator for\n> + * generating test patterns\n> + */\n> +\n> +#pragma once\n> +\n> +#include <memory>\n> +\n> +#include <libcamera/framebuffer.h>\n> +#include <libcamera/geometry.h>\n> +\n> +#include \"frame_generator.h\"\n> +\n> +namespace libcamera {\n> +\n> +enum class TestPattern : char {\n> +\tColorBars = 0,\n> +\tDiagonalLines = 1,\n> +};\n> +\n> +class TestPatternGenerator : public FrameGenerator\n> +{\n> +public:\n> +\tvoid generateFrame(const Size &size, const FrameBuffer *buffer) override;\n> +\n> +protected:\n> +\t/* Buffer of test pattern template */\n> +\tstd::unique_ptr<uint8_t[]> template_;\n> +\n> +private:\n> +\t/* Shift the buffer by 1 pixel left each frame */\n> +\tvoid shiftLeft(const Size &size);\n> +};\n> +\n> +class ColorBarsGenerator : public TestPatternGenerator\n> +{\n> +public:\n> +\t/* Generate a template buffer of the color bar test pattern. */\n> +\tvoid configure(const Size &size) override;\n> +};\n> +\n> +class DiagonalLinesGenerator : public TestPatternGenerator\n> +{\n> +public:\n> +\t/* Generate a template buffer of the diagonal lines test pattern. */\n> +\tvoid configure(const Size &size) override;\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\n> index 56e05528..4e9a6973 100644\n> --- a/src/libcamera/pipeline/virtual/virtual.cpp\n> +++ b/src/libcamera/pipeline/virtual/virtual.cpp\n> @@ -84,6 +84,8 @@ private:\n>  \t\treturn static_cast<VirtualCameraData *>(camera->_d());\n>  \t}\n>\n> +\tvoid initFrameGenerator(Camera *camera);\n> +\n>  \tDmaBufAllocator dmaBufAllocator_;\n>  };\n>\n> @@ -218,8 +220,10 @@ int PipelineHandlerVirtual::configure(Camera *camera,\n>  \t\t\t\t      CameraConfiguration *config)\n>  {\n>  \tVirtualCameraData *data = cameraData(camera);\n> -\tfor (auto [i, c] : utils::enumerate(*config))\n> +\tfor (auto [i, c] : utils::enumerate(*config)) {\n>  \t\tc.setStream(&data->streamConfigs_[i].stream);\n> +\t\tdata->streamConfigs_[i].frameGenerator->configure(c.size);\n> +\t}\n>\n>  \treturn 0;\n>  }\n> @@ -255,9 +259,23 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)\n>  int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n>  \t\t\t\t\t       Request *request)\n>  {\n> +\tVirtualCameraData *data = cameraData(camera);\n> +\n>  \t/* \\todo Read from the virtual video if any. */\n> -\tfor (auto it : request->buffers())\n> -\t\tcompleteBuffer(request, it.second);\n> +\tfor (auto const &[stream, buffer] : request->buffers()) {\n> +\t\tbool found = false;\n> +\t\t/* map buffer and fill test patterns */\n> +\t\tfor (auto &streamConfig : data->streamConfigs_) {\n> +\t\t\tif (stream == &streamConfig.stream) {\n> +\t\t\t\tfound = true;\n> +\t\t\t\tstreamConfig.frameGenerator->generateFrame(\n> +\t\t\t\t\tstream->configuration().size, buffer);\n> +\t\t\t\tcompleteBuffer(request, buffer);\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t}\n> +\t\tASSERT(found);\n> +\t}\n\nNot a requirement, but you can make this a little more C++ with\n\n\tfor (auto const &[stream, buffer] : request->buffers()) {\n\t\tauto it = std::find_if(data->streamConfigs_.begin(),\n\t\t\t\t       data->streamConfigs_.end(),\n\t\t\t\t       [stream](const auto &c) {\n\t\t\t\t\t\treturn &c.stream == stream;\n\t\t\t\t\t});\n\t\tASSERT(it == data->streamConfigs_.end());\n\n\t\tit->frameGenerator->generateFrame(stream->configuration().size,\n\t\t\t\t\t\t  buffer);\n\t\tcompleteBuffer(request, buffer);\n\t}\n\nwarning: compile-tested only\n\nThe rest looks good\n\n>\n>  \trequest->metadata().set(controls::SensorTimestamp, currentTimestamp());\n>  \tcompleteRequest(request);\n> @@ -299,11 +317,25 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator\n>\n>  \tconst std::string id = \"Virtual0\";\n>  \tstd::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);\n> +\n> +\tinitFrameGenerator(camera.get());\n> +\n>  \tregisterCamera(std::move(camera));\n>\n>  \treturn true;\n>  }\n>\n> +void PipelineHandlerVirtual::initFrameGenerator(Camera *camera)\n> +{\n> +\tauto data = cameraData(camera);\n> +\tfor (auto &streamConfig : data->streamConfigs_) {\n> +\t\tif (data->testPattern_ == TestPattern::DiagonalLines)\n> +\t\t\tstreamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>();\n> +\t\telse\n> +\t\t\tstreamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>();\n> +\t}\n> +}\n> +\n>  REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, \"virtual\")\n>\n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\n> index 77823c41..acdd437e 100644\n> --- a/src/libcamera/pipeline/virtual/virtual.h\n> +++ b/src/libcamera/pipeline/virtual/virtual.h\n> @@ -14,6 +14,8 @@\n>  #include \"libcamera/internal/camera.h\"\n>  #include \"libcamera/internal/pipeline_handler.h\"\n>\n> +#include \"test_pattern_generator.h\"\n> +\n>  namespace libcamera {\n>\n>  class VirtualCameraData : public Camera::Private\n> @@ -27,6 +29,7 @@ public:\n>  \t};\n>  \tstruct StreamConfig {\n>  \t\tStream stream;\n> +\t\tstd::unique_ptr<FrameGenerator> frameGenerator;\n>  \t};\n>\n>  \tVirtualCameraData(PipelineHandler *pipe,\n> @@ -34,6 +37,8 @@ public:\n>\n>  \t~VirtualCameraData() = default;\n>\n> +\tTestPattern testPattern_ = TestPattern::ColorBars;\n> +\n>  \tconst std::vector<Resolution> supportedResolutions_;\n>  \tSize maxResolutionSize_;\n>  \tSize minResolutionSize_;\n> diff --git a/src/meson.build b/src/meson.build\n> index 165a77bb..91bea775 100644\n> --- a/src/meson.build\n> +++ b/src/meson.build\n> @@ -27,6 +27,25 @@ else\n>      ipa_sign_module = false\n>  endif\n>\n> +libyuv_dep = dependency('libyuv', required : false)\n> +\n> +# Fallback to a subproject if libyuv isn't found, as it's typically not\n> +# provided by distributions.\n> +if not libyuv_dep.found()\n> +    cmake = import('cmake')\n> +\n> +    libyuv_vars = cmake.subproject_options()\n> +    libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})\n> +    libyuv_vars.set_override_option('cpp_std', 'c++17')\n> +    libyuv_vars.append_compile_args('cpp',\n> +         '-Wno-sign-compare',\n> +         '-Wno-unused-variable',\n> +         '-Wno-unused-parameter')\n> +    libyuv_vars.append_link_args('-ljpeg')\n> +    libyuv = cmake.subproject('libyuv', options : libyuv_vars)\n> +    libyuv_dep = libyuv.dependency('yuv')\n> +endif\n> +\n>  # libcamera must be built first as a dependency to the other components.\n>  subdir('libcamera')\n>\n> --\n> 2.46.0.598.g6f2099f65c-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 2D251C0F1B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 26 Sep 2024 11:18:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 53C456350F;\n\tThu, 26 Sep 2024 13:18:52 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 572B8634F9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Sep 2024 13:18:51 +0200 (CEST)","from ideasonboard.com (mob-5-90-51-229.net.vodafone.it\n\t[5.90.51.229])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A8F328D4;\n\tThu, 26 Sep 2024 13:17:22 +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=\"g2Hnsox7\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1727349442;\n\tbh=TtdB5IPvzTygUUjtH9mh92q2+Jv1Cnx9d362p2i/rBg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=g2Hnsox7pwWxb65Dy81TRGO5e3/LUmAX8vUyB+w9/vZq2jX8iUNIHzB7RFicchT+R\n\tNbK1MpZRE3sd1ZBk6O1GFh0Rudhxc0X8YjiqWSCNrfIZ5k8ep9jJq7otWdqQF3IP2A\n\t7alyZ6iIF9x6TWNGQK4RTgck/laA1b/AsNtrTzXk=","Date":"Thu, 26 Sep 2024 13:18:47 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Harvey Yang <chenghaoyang@chromium.org>","Cc":"libcamera-devel@lists.libcamera.org, Konami Shu <konamiz@google.com>, \n\tYunke Cao <yunkec@chromium.org>, Tomasz Figa <tfiga@chromium.org>, \n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>","Subject":"Re: [PATCH v12 4/7] libcamera: pipeline: Add test pattern for\n\tVirtualPipelineHandler","Message-ID":"<qc5ljdapvdoh4oxbql4efrga6qlsrbe5scxu367yhk4taqhmde@po2c2usnenjg>","References":"<20240910044834.2477701-1-chenghaoyang@google.com>\n\t<20240910044834.2477701-5-chenghaoyang@google.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240910044834.2477701-5-chenghaoyang@google.com>","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":31415,"web_url":"https://patchwork.libcamera.org/comment/31415/","msgid":"<CAC=wSGX5E6-tBbQHV=g76kb1do_-G3MEZyn08ibFiw9dxDLuAg@mail.gmail.com>","date":"2024-09-26T16:32:18","subject":"Re: [PATCH v12 4/7] libcamera: pipeline: Add test pattern for\n\tVirtualPipelineHandler","submitter":{"id":148,"url":"https://patchwork.libcamera.org/api/people/148/","name":"Cheng-Hao Yang","email":"chenghaoyang@google.com"},"content":"Hi Jacopo,\n\nOn Thu, Sep 26, 2024 at 7:18 PM Jacopo Mondi\n<jacopo.mondi@ideasonboard.com> wrote:\n>\n> Hi Harvey\n>\n> On Tue, Sep 10, 2024 at 04:40:17AM GMT, Harvey Yang wrote:\n> > From: Konami Shu <konamiz@google.com>\n> >\n> > Add a test pattern generator class hierarchy for the Virtual\n> > pipeline handler.\n> >\n> > Implement two types of test patterns: color bars and diagonal lines\n> > generator and use them in the Virtual pipeline handler.\n> >\n> > A shifting mechanism is enabled. For each frame, the image is shifted to\n> > the left by 1 pixel. It drops FPS though.\n> >\n> > Add a dependency for libyuv to the build system to generate images\n> > in NV12 format from the test pattern.\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> > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > ---\n> >  src/android/meson.build                       |  19 ---\n> >  .../pipeline/virtual/frame_generator.h        |  29 ++++\n> >  src/libcamera/pipeline/virtual/meson.build    |   3 +\n> >  .../virtual/test_pattern_generator.cpp        | 135 ++++++++++++++++++\n> >  .../pipeline/virtual/test_pattern_generator.h |  53 +++++++\n> >  src/libcamera/pipeline/virtual/virtual.cpp    |  38 ++++-\n> >  src/libcamera/pipeline/virtual/virtual.h      |   5 +\n> >  src/meson.build                               |  19 +++\n> >  8 files changed, 279 insertions(+), 22 deletions(-)\n> >  create mode 100644 src/libcamera/pipeline/virtual/frame_generator.h\n> >  create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n> >  create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.h\n> >\n> > diff --git a/src/android/meson.build b/src/android/meson.build\n> > index 68646120..6341ee8b 100644\n> > --- a/src/android/meson.build\n> > +++ b/src/android/meson.build\n> > @@ -15,25 +15,6 @@ foreach dep : android_deps\n> >      endif\n> >  endforeach\n> >\n> > -libyuv_dep = dependency('libyuv', required : false)\n> > -\n> > -# Fallback to a subproject if libyuv isn't found, as it's typically not\n> > -# provided by distributions.\n> > -if not libyuv_dep.found()\n> > -    cmake = import('cmake')\n> > -\n> > -    libyuv_vars = cmake.subproject_options()\n> > -    libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})\n> > -    libyuv_vars.set_override_option('cpp_std', 'c++17')\n> > -    libyuv_vars.append_compile_args('cpp',\n> > -         '-Wno-sign-compare',\n> > -         '-Wno-unused-variable',\n> > -         '-Wno-unused-parameter')\n> > -    libyuv_vars.append_link_args('-ljpeg')\n> > -    libyuv = cmake.subproject('libyuv', options : libyuv_vars)\n> > -    libyuv_dep = libyuv.dependency('yuv')\n> > -endif\n> > -\n> >  android_deps += [libyuv_dep]\n> >\n> >  android_hal_sources = files([\n> > diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h\n> > new file mode 100644\n> > index 00000000..d8727b8f\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/virtual/frame_generator.h\n> > @@ -0,0 +1,29 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Google Inc.\n> > + *\n> > + * frame_generator.h - Virtual cameras helper to generate frames\n> > + */\n> > +\n> > +#pragma once\n> > +\n> > +#include <libcamera/framebuffer.h>\n> > +#include <libcamera/geometry.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +class FrameGenerator\n> > +{\n> > +public:\n> > +     virtual ~FrameGenerator() = default;\n> > +\n> > +     virtual void configure(const Size &size) = 0;\n> > +\n> > +     virtual void generateFrame(const Size &size,\n> > +                                const FrameBuffer *buffer) = 0;\n> > +\n> > +protected:\n> > +     FrameGenerator() {}\n> > +};\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\n> > index ada1b335..0c537777 100644\n> > --- a/src/libcamera/pipeline/virtual/meson.build\n> > +++ b/src/libcamera/pipeline/virtual/meson.build\n> > @@ -1,5 +1,8 @@\n> >  # SPDX-License-Identifier: CC0-1.0\n> >\n> >  libcamera_internal_sources += files([\n> > +    'test_pattern_generator.cpp',\n> >      'virtual.cpp',\n> >  ])\n> > +\n> > +libcamera_deps += [libyuv_dep]\n> > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n> > new file mode 100644\n> > index 00000000..f84ab2f3\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp\n> > @@ -0,0 +1,135 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Google Inc.\n> > + *\n> > + * test_pattern_generator.cpp - Derived class of FrameGenerator for\n> > + * generating test patterns\n> > + */\n> > +\n> > +#include \"test_pattern_generator.h\"\n> > +\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include \"libcamera/internal/mapped_framebuffer.h\"\n> > +\n> > +#include <libyuv/convert_from_argb.h>\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DECLARE_CATEGORY(Virtual)\n> > +\n> > +static const unsigned int kARGBSize = 4;\n> > +\n> > +void TestPatternGenerator::generateFrame(const Size &size,\n>\n> I wonder if it's worth returning an error to the caller and complete\n> the buffer in error state in the pipeline handler in that case.\n\nSure, updated.\n\n>\n> > +                                      const FrameBuffer *buffer)\n> > +{\n> > +     MappedFrameBuffer mappedFrameBuffer(buffer,\n> > +                                         MappedFrameBuffer::MapFlag::Write);\n> > +\n> > +     auto planes = mappedFrameBuffer.planes();\n> > +\n> > +     shiftLeft(size);\n> > +\n> > +     /* Convert the template_ to the frame buffer */\n> > +     int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize,\n> > +                                  planes[0].begin(), size.width,\n> > +                                  planes[1].begin(), size.width,\n> > +                                  size.width, size.height);\n> > +     if (ret != 0)\n> > +             LOG(Virtual, Error) << \"ARGBToNV12() failed with \" << ret;\n> > +}\n> > +\n> > +void TestPatternGenerator::shiftLeft(const Size &size)\n> > +{\n> > +     /* Store the first column temporarily */\n> > +     auto firstColumn = std::make_unique<uint8_t[]>(size.height * kARGBSize);\n> > +     for (size_t h = 0; h < size.height; h++) {\n> > +             unsigned int index = h * size.width * kARGBSize;\n> > +             unsigned int index1 = h * kARGBSize;\n> > +             firstColumn[index1] = template_[index];\n> > +             firstColumn[index1 + 1] = template_[index + 1];\n> > +             firstColumn[index1 + 2] = template_[index + 2];\n> > +             firstColumn[index1 + 3] = 0x00;\n> > +     }\n> > +\n> > +     /* Overwrite template_ */\n> > +     uint8_t *buf = template_.get();\n> > +     for (size_t h = 0; h < size.height; h++) {\n> > +             for (size_t w = 0; w < size.width - 1; w++) {\n> > +                     /* Overwrite with the pixel on the right */\n> > +                     unsigned int index = (h * size.width + w + 1) * kARGBSize;\n> > +                     *buf++ = template_[index]; /* B */\n> > +                     *buf++ = template_[index + 1]; /* G */\n> > +                     *buf++ = template_[index + 2]; /* R */\n> > +                     *buf++ = 0x00; /* A */\n> > +             }\n> > +             /* Overwrite the new last column with the original first column */\n> > +             unsigned int index1 = h * kARGBSize;\n> > +             *buf++ = firstColumn[index1]; /* B */\n> > +             *buf++ = firstColumn[index1 + 1]; /* G */\n> > +             *buf++ = firstColumn[index1 + 2]; /* R */\n> > +             *buf++ = 0x00; /* A */\n> > +     }\n> > +}\n> > +\n> > +void ColorBarsGenerator::configure(const Size &size)\n> > +{\n> > +     constexpr uint8_t kColorBar[8][3] = {\n> > +             /*  R,    G,    B */\n> > +             { 0xff, 0xff, 0xff }, /* White */\n> > +             { 0xff, 0xff, 0x00 }, /* Yellow */\n> > +             { 0x00, 0xff, 0xff }, /* Cyan */\n> > +             { 0x00, 0xff, 0x00 }, /* Green */\n> > +             { 0xff, 0x00, 0xff }, /* Magenta */\n> > +             { 0xff, 0x00, 0x00 }, /* Red */\n> > +             { 0x00, 0x00, 0xff }, /* Blue */\n> > +             { 0x00, 0x00, 0x00 }, /* Black */\n> > +     };\n> > +\n> > +     template_ = std::make_unique<uint8_t[]>(\n> > +             size.width * size.height * kARGBSize);\n> > +\n> > +     unsigned int colorBarWidth = size.width / std::size(kColorBar);\n> > +\n> > +     uint8_t *buf = template_.get();\n> > +     for (size_t h = 0; h < size.height; h++) {\n> > +             for (size_t w = 0; w < size.width; w++) {\n> > +                     /* Repeat when the width is exceed */\n> > +                     unsigned int index = (w / colorBarWidth) % std::size(kColorBar);\n> > +\n> > +                     *buf++ = kColorBar[index][2]; /* B */\n> > +                     *buf++ = kColorBar[index][1]; /* G */\n> > +                     *buf++ = kColorBar[index][0]; /* R */\n> > +                     *buf++ = 0x00; /* A */\n> > +             }\n> > +     }\n> > +}\n> > +\n> > +void DiagonalLinesGenerator::configure(const Size &size)\n> > +{\n> > +     constexpr uint8_t kColorBar[8][3] = {\n> > +             /*  R,    G,    B */\n> > +             { 0xff, 0xff, 0xff }, /* White */\n> > +             { 0x00, 0x00, 0x00 }, /* Black */\n> > +     };\n> > +\n> > +     template_ = std::make_unique<uint8_t[]>(\n> > +             size.width * size.height * kARGBSize);\n> > +\n> > +     unsigned int lineWidth = size.width / 10;\n> > +\n> > +     uint8_t *buf = template_.get();\n> > +     for (size_t h = 0; h < size.height; h++) {\n> > +             for (size_t w = 0; w < size.width; w++) {\n> > +                     /* Repeat when the width is exceed */\n> > +                     int index = ((w + h) / lineWidth) % 2;\n> > +\n> > +                     *buf++ = kColorBar[index][2]; /* B */\n> > +                     *buf++ = kColorBar[index][1]; /* G */\n> > +                     *buf++ = kColorBar[index][0]; /* R */\n> > +                     *buf++ = 0x00; /* A */\n> > +             }\n> > +     }\n> > +}\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h\n> > new file mode 100644\n> > index 00000000..b8bbe0b8\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h\n> > @@ -0,0 +1,53 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Google Inc.\n> > + *\n> > + * test_pattern_generator.h - Derived class of FrameGenerator for\n> > + * generating test patterns\n> > + */\n> > +\n> > +#pragma once\n> > +\n> > +#include <memory>\n> > +\n> > +#include <libcamera/framebuffer.h>\n> > +#include <libcamera/geometry.h>\n> > +\n> > +#include \"frame_generator.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +enum class TestPattern : char {\n> > +     ColorBars = 0,\n> > +     DiagonalLines = 1,\n> > +};\n> > +\n> > +class TestPatternGenerator : public FrameGenerator\n> > +{\n> > +public:\n> > +     void generateFrame(const Size &size, const FrameBuffer *buffer) override;\n> > +\n> > +protected:\n> > +     /* Buffer of test pattern template */\n> > +     std::unique_ptr<uint8_t[]> template_;\n> > +\n> > +private:\n> > +     /* Shift the buffer by 1 pixel left each frame */\n> > +     void shiftLeft(const Size &size);\n> > +};\n> > +\n> > +class ColorBarsGenerator : public TestPatternGenerator\n> > +{\n> > +public:\n> > +     /* Generate a template buffer of the color bar test pattern. */\n> > +     void configure(const Size &size) override;\n> > +};\n> > +\n> > +class DiagonalLinesGenerator : public TestPatternGenerator\n> > +{\n> > +public:\n> > +     /* Generate a template buffer of the diagonal lines test pattern. */\n> > +     void configure(const Size &size) override;\n> > +};\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\n> > index 56e05528..4e9a6973 100644\n> > --- a/src/libcamera/pipeline/virtual/virtual.cpp\n> > +++ b/src/libcamera/pipeline/virtual/virtual.cpp\n> > @@ -84,6 +84,8 @@ private:\n> >               return static_cast<VirtualCameraData *>(camera->_d());\n> >       }\n> >\n> > +     void initFrameGenerator(Camera *camera);\n> > +\n> >       DmaBufAllocator dmaBufAllocator_;\n> >  };\n> >\n> > @@ -218,8 +220,10 @@ int PipelineHandlerVirtual::configure(Camera *camera,\n> >                                     CameraConfiguration *config)\n> >  {\n> >       VirtualCameraData *data = cameraData(camera);\n> > -     for (auto [i, c] : utils::enumerate(*config))\n> > +     for (auto [i, c] : utils::enumerate(*config)) {\n> >               c.setStream(&data->streamConfigs_[i].stream);\n> > +             data->streamConfigs_[i].frameGenerator->configure(c.size);\n> > +     }\n> >\n> >       return 0;\n> >  }\n> > @@ -255,9 +259,23 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)\n> >  int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n> >                                              Request *request)\n> >  {\n> > +     VirtualCameraData *data = cameraData(camera);\n> > +\n> >       /* \\todo Read from the virtual video if any. */\n> > -     for (auto it : request->buffers())\n> > -             completeBuffer(request, it.second);\n> > +     for (auto const &[stream, buffer] : request->buffers()) {\n> > +             bool found = false;\n> > +             /* map buffer and fill test patterns */\n> > +             for (auto &streamConfig : data->streamConfigs_) {\n> > +                     if (stream == &streamConfig.stream) {\n> > +                             found = true;\n> > +                             streamConfig.frameGenerator->generateFrame(\n> > +                                     stream->configuration().size, buffer);\n> > +                             completeBuffer(request, buffer);\n> > +                             break;\n> > +                     }\n> > +             }\n> > +             ASSERT(found);\n> > +     }\n>\n> Not a requirement, but you can make this a little more C++ with\n>\n>         for (auto const &[stream, buffer] : request->buffers()) {\n>                 auto it = std::find_if(data->streamConfigs_.begin(),\n>                                        data->streamConfigs_.end(),\n>                                        [stream](const auto &c) {\n>                                                 return &c.stream == stream;\n>                                         });\n>                 ASSERT(it == data->streamConfigs_.end());\n>\n>                 it->frameGenerator->generateFrame(stream->configuration().size,\n>                                                   buffer);\n>                 completeBuffer(request, buffer);\n>         }\n>\n> warning: compile-tested only\n>\n> The rest looks good\n\nLet's skip this for now, as my linter shows an error\n`Captured structured bindings are a C++20 extension`,\nand it also fails the gitlab pipeline:\nhttps://gitlab.freedesktop.org/chenghaoyang/libcamera/-/jobs/64229041\n\n\n\n>\n> >\n> >       request->metadata().set(controls::SensorTimestamp, currentTimestamp());\n> >       completeRequest(request);\n> > @@ -299,11 +317,25 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator\n> >\n> >       const std::string id = \"Virtual0\";\n> >       std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);\n> > +\n> > +     initFrameGenerator(camera.get());\n> > +\n> >       registerCamera(std::move(camera));\n> >\n> >       return true;\n> >  }\n> >\n> > +void PipelineHandlerVirtual::initFrameGenerator(Camera *camera)\n> > +{\n> > +     auto data = cameraData(camera);\n> > +     for (auto &streamConfig : data->streamConfigs_) {\n> > +             if (data->testPattern_ == TestPattern::DiagonalLines)\n> > +                     streamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>();\n> > +             else\n> > +                     streamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>();\n> > +     }\n> > +}\n> > +\n> >  REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, \"virtual\")\n> >\n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\n> > index 77823c41..acdd437e 100644\n> > --- a/src/libcamera/pipeline/virtual/virtual.h\n> > +++ b/src/libcamera/pipeline/virtual/virtual.h\n> > @@ -14,6 +14,8 @@\n> >  #include \"libcamera/internal/camera.h\"\n> >  #include \"libcamera/internal/pipeline_handler.h\"\n> >\n> > +#include \"test_pattern_generator.h\"\n> > +\n> >  namespace libcamera {\n> >\n> >  class VirtualCameraData : public Camera::Private\n> > @@ -27,6 +29,7 @@ public:\n> >       };\n> >       struct StreamConfig {\n> >               Stream stream;\n> > +             std::unique_ptr<FrameGenerator> frameGenerator;\n> >       };\n> >\n> >       VirtualCameraData(PipelineHandler *pipe,\n> > @@ -34,6 +37,8 @@ public:\n> >\n> >       ~VirtualCameraData() = default;\n> >\n> > +     TestPattern testPattern_ = TestPattern::ColorBars;\n> > +\n> >       const std::vector<Resolution> supportedResolutions_;\n> >       Size maxResolutionSize_;\n> >       Size minResolutionSize_;\n> > diff --git a/src/meson.build b/src/meson.build\n> > index 165a77bb..91bea775 100644\n> > --- a/src/meson.build\n> > +++ b/src/meson.build\n> > @@ -27,6 +27,25 @@ else\n> >      ipa_sign_module = false\n> >  endif\n> >\n> > +libyuv_dep = dependency('libyuv', required : false)\n> > +\n> > +# Fallback to a subproject if libyuv isn't found, as it's typically not\n> > +# provided by distributions.\n> > +if not libyuv_dep.found()\n> > +    cmake = import('cmake')\n> > +\n> > +    libyuv_vars = cmake.subproject_options()\n> > +    libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})\n> > +    libyuv_vars.set_override_option('cpp_std', 'c++17')\n> > +    libyuv_vars.append_compile_args('cpp',\n> > +         '-Wno-sign-compare',\n> > +         '-Wno-unused-variable',\n> > +         '-Wno-unused-parameter')\n> > +    libyuv_vars.append_link_args('-ljpeg')\n> > +    libyuv = cmake.subproject('libyuv', options : libyuv_vars)\n> > +    libyuv_dep = libyuv.dependency('yuv')\n> > +endif\n> > +\n> >  # libcamera must be built first as a dependency to the other components.\n> >  subdir('libcamera')\n> >\n> > --\n> > 2.46.0.598.g6f2099f65c-goog\n> >\n\n\n\n--\nBR,\nHarvey Yang","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 0A4F2C0F1B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 26 Sep 2024 16:32:59 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A3F1A6351D;\n\tThu, 26 Sep 2024 18:32:58 +0200 (CEST)","from mail-lf1-x132.google.com (mail-lf1-x132.google.com\n\t[IPv6:2a00:1450:4864:20::132])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4CDFA634F9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Sep 2024 18:32:56 +0200 (CEST)","by mail-lf1-x132.google.com with SMTP id\n\t2adb3069b0e04-536552fcc07so18781e87.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Sep 2024 09:32:56 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=google.com header.i=@google.com\n\theader.b=\"GzZ59heh\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=google.com; s=20230601; t=1727368375; x=1727973175;\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=XGOWl8MjFhps6+AES3DZobs55nAz6xh0XJa5SnScynU=;\n\tb=GzZ59hehix5nFT7S/tSj5eJicS/k682IaWixygc6W3Na/+IA/iE+3Dox0fQiB4BU6E\n\t9f8ivBztGs0ryFVLK1gtM+cJnrjrJ/I1lYjHeSOuwTZhbqmvxql7VQMd0NekZH3RZ0Oj\n\td7rIceMuQvzfAX5wZgtt7S5dXN2tWsQZmPqRubTQcCdWT9zwpmFvArqdieAhaPVWXThL\n\tHofBnS7vvjYRVPjfYkGE7ThMp21fFQLcYb4wBbmuEqGSROy6xCXMrY4jOgIElWhhdX7p\n\tWjuEPCEiki6eKfeGIghjntNZOs6Jv6qPOlhwiAiZqxdHgSyD7XLWPRpZiIS/v6vtR4Z5\n\toURQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1727368375; x=1727973175;\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=XGOWl8MjFhps6+AES3DZobs55nAz6xh0XJa5SnScynU=;\n\tb=j0h3eVS32z8Ce0ZmyjFQHKi/aMytd6UlzvYeU8AuTwMUShm5AfMkpvaCI+P0/acQ4z\n\tLGgJsh0g0bvYl3bxtiYMcfpE3vzTOfw0BzlpyVnaCznTVD2qar7yoB1nzRMACjHQ1lRO\n\tHdVz+uiBvMjhaPfBPwXfC2edvgk6T5VCuPCfZ35hbt/vBzsW9dlpiAA3adJm9W23IimD\n\tpDusfDXYifCyx5wEM/s/L0upDgdnTIGbpj09oli7avnoRR89CAumL8nrCYwt7bS9p2wI\n\t5HOGvueRZiNl0697eokG4JZxkzY6XeOYlFItbI2MnRpPh+Z1m2jwfYtFKxuim97OnAjj\n\t+8HA==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCUu/tHVd6sWUGJl5j8p2K5tyjvC4eSE5/LDJK/ubpLGUZj+Oe0qJ2M+xBseSpTzWipwP3k+srd2d9FTvb0o+j0=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YxVlotMDhutxsLNs6XYAYsvHvCi1bk351pgxwUDKK02ePbVM2IJ\n\tMmqaIQCzCIDS7v9UvWVtplP72TaaAsM7AfkvhZRJH14IbnJBH2GL+o8gRW7Zr7y1ef3QDIhzHx2\n\tSzjqBh+oEKsjnB5FeTSig8tMVlwl3Z78Voh68UL+zUQbtX0Ygw7Qw","X-Google-Smtp-Source":"AGHT+IFIJ+RTWx8EHAPNtWyOv5EX6hW4XuBN+aWQdo3L1I54xF2IjjVuemv+SRX5sc62Pc8lODNJCbVOM+QXui8VWo8=","X-Received":"by 2002:a05:6512:ea6:b0:533:49ab:780e with SMTP id\n\t2adb3069b0e04-53897279d62mr307965e87.2.1727368375179; Thu, 26 Sep 2024\n\t09:32:55 -0700 (PDT)","MIME-Version":"1.0","References":"<20240910044834.2477701-1-chenghaoyang@google.com>\n\t<20240910044834.2477701-5-chenghaoyang@google.com>\n\t<qc5ljdapvdoh4oxbql4efrga6qlsrbe5scxu367yhk4taqhmde@po2c2usnenjg>","In-Reply-To":"<qc5ljdapvdoh4oxbql4efrga6qlsrbe5scxu367yhk4taqhmde@po2c2usnenjg>","From":"Cheng-Hao Yang <chenghaoyang@google.com>","Date":"Fri, 27 Sep 2024 00:32:18 +0800","Message-ID":"<CAC=wSGX5E6-tBbQHV=g76kb1do_-G3MEZyn08ibFiw9dxDLuAg@mail.gmail.com>","Subject":"Re: [PATCH v12 4/7] libcamera: pipeline: Add test pattern for\n\tVirtualPipelineHandler","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"Harvey Yang <chenghaoyang@chromium.org>,\n\tlibcamera-devel@lists.libcamera.org, \n\tKonami Shu <konamiz@google.com>, Yunke Cao <yunkec@chromium.org>, \n\tTomasz Figa <tfiga@chromium.org>","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>"}}]