[{"id":3093,"web_url":"https://patchwork.libcamera.org/comment/3093/","msgid":"<20191118232836.GQ8072@bigcity.dyn.berto.se>","date":"2019-11-18T23:28:36","subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your patch.\n\nOn 2019-11-08 22:54:08 +0200, Laurent Pinchart wrote:\n> Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,\n> and verify that the translation between C and C++ APIs work correctly.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n>  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++\n>  test/ipa/meson.build           |   5 +-\n>  2 files changed, 393 insertions(+), 2 deletions(-)\n>  create mode 100644 test/ipa/ipa_wrappers_test.cpp\n> \n> diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp\n> new file mode 100644\n> index 000000000000..c0717f0e301b\n> --- /dev/null\n> +++ b/test/ipa/ipa_wrappers_test.cpp\n> @@ -0,0 +1,390 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers\n> + */\n> +\n> +#include <fcntl.h>\n> +#include <iostream>\n> +#include <memory>\n> +#include <linux/videodev2.h>\n> +#include <sys/stat.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/controls.h>\n> +#include <libipa/ipa_interface_wrapper.h>\n> +\n> +#include \"device_enumerator.h\"\n> +#include \"ipa_context_wrapper.h\"\n> +#include \"media_device.h\"\n> +#include \"utils.h\"\n> +#include \"v4l2_subdevice.h\"\n> +\n> +#include \"test.h\"\n> +\n> +using namespace libcamera;\n> +using namespace std;\n> +\n> +enum Operation {\n> +\tOp_init,\n> +\tOp_configure,\n> +\tOp_mapBuffers,\n> +\tOp_unmapBuffers,\n> +\tOp_processEvent,\n> +};\n> +\n> +class TestIPAInterface : public IPAInterface\n> +{\n> +public:\n> +\tTestIPAInterface()\n> +\t\t: sequence_(0)\n> +\t{\n> +\t}\n> +\n> +\tint init() override\n> +\t{\n> +\t\treport(Op_init, TestPass);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tvoid configure(const std::map<unsigned int, IPAStream> &streamConfig,\n> +\t\t       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override\n> +\t{\n> +\t\t/* Verify streamConfig. */\n> +\t\tif (streamConfig.size() != 2) {\n> +\t\t\tcerr << \"configure(): Invalid number of streams \"\n> +\t\t\t     << streamConfig.size() << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\tauto iter = streamConfig.find(1);\n> +\t\tif (iter == streamConfig.end()) {\n> +\t\t\tcerr << \"configure(): No configuration for stream 1\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\t\tconst IPAStream *stream = &iter->second;\n> +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||\n> +\t\t    stream->size != Size{ 1024, 768 }) {\n> +\t\t\tcerr << \"configure(): Invalid configuration for stream 1\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\titer = streamConfig.find(2);\n> +\t\tif (iter == streamConfig.end()) {\n> +\t\t\tcerr << \"configure(): No configuration for stream 2\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\t\tstream = &iter->second;\n> +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||\n> +\t\t    stream->size != Size{ 800, 600 }) {\n> +\t\t\tcerr << \"configure(): Invalid configuration for stream 2\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\t/* Verify entityControls. */\n> +\t\tauto ctrlIter = entityControls.find(42);\n> +\t\tif (ctrlIter == entityControls.end()) {\n> +\t\t\tcerr << \"configure(): Controls not found\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\tconst ControlInfoMap &infoMap = ctrlIter->second;\n> +\n> +\t\tif (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||\n> +\t\t    infoMap.count(V4L2_CID_CONTRAST) != 1 ||\n> +\t\t    infoMap.count(V4L2_CID_SATURATION) != 1) {\n> +\t\t\tcerr << \"configure(): Invalid control IDs\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_configure, TestPass);\n> +\t}\n> +\n> +\tvoid mapBuffers(const std::vector<IPABuffer> &buffers) override\n> +\t{\n> +\t\tif (buffers.size() != 2) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid number of buffers \"\n> +\t\t\t     << buffers.size() << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].id != 10 ||\n> +\t\t    buffers[1].id != 11) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid buffer IDs\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes().size() != 3 ||\n> +\t\t    buffers[1].memory.planes().size() != 3) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid number of planes\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes()[0].length() != 4096 ||\n> +\t\t    buffers[0].memory.planes()[1].length() != 0 ||\n> +\t\t    buffers[0].memory.planes()[2].length() != 0 ||\n> +\t\t    buffers[0].memory.planes()[0].length() != 4096 ||\n> +\t\t    buffers[1].memory.planes()[1].length() != 4096 ||\n> +\t\t    buffers[1].memory.planes()[2].length() != 0) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid length\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> +\t\t    buffers[0].memory.planes()[1].dmabuf() != -1 ||\n> +\t\t    buffers[0].memory.planes()[2].dmabuf() != -1 ||\n> +\t\t    buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> +\t\t    buffers[1].memory.planes()[1].dmabuf() == -1 ||\n> +\t\t    buffers[1].memory.planes()[2].dmabuf() != -1) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid dmabuf\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_mapBuffers, TestPass);\n> +\t}\n> +\n> +\tvoid unmapBuffers(const std::vector<unsigned int> &ids) override\n> +\t{\n> +\t\tif (ids.size() != 2) {\n> +\t\t\tcerr << \"unmapBuffers(): Invalid number of ids \"\n> +\t\t\t     << ids.size() << endl;\n> +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (ids[0] != 10 || ids[1] != 11) {\n> +\t\t\tcerr << \"unmapBuffers(): Invalid buffer IDs\" << endl;\n> +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_unmapBuffers, TestPass);\n> +\t}\n> +\n> +\tvoid processEvent(const IPAOperationData &data) override\n> +\t{\n> +\t\t/* Verify operation and data. */\n> +\t\tif (data.operation != Op_processEvent) {\n> +\t\t\tcerr << \"processEvent(): Invalid operation \"\n> +\t\t\t     << data.operation << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\tif (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {\n> +\t\t\tcerr << \"processEvent(): Invalid data\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\t/* Verify controls. */\n> +\t\tif (data.controls.size() != 1) {\n> +\t\t\tcerr << \"processEvent(): Controls not found\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\tconst ControlList &controls = data.controls[0];\n> +\t\tif (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||\n> +\t\t    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||\n> +\t\t    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {\n> +\t\t\tcerr << \"processEvent(): Invalid controls\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_processEvent, TestPass);\n> +\t}\n> +\n> +private:\n> +\tvoid report(Operation op, int status)\n> +\t{\n> +\t\tIPAOperationData data;\n> +\t\tdata.operation = op;\n> +\t\tdata.data.resize(1);\n> +\t\tdata.data[0] = status;\n> +\t\tqueueFrameAction.emit(sequence_++, data);\n> +\t}\n> +\n> +\tunsigned int sequence_;\n> +};\n> +\n> +#define INVOKE(method, ...) \\\n> +\tinvoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)\n> +\n> +class IPAWrappersTest : public Test\n> +{\n> +public:\n> +\tIPAWrappersTest()\n> +\t\t: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)\n> +\t{\n> +\t}\n> +\n> +protected:\n> +\tint init() override\n> +\t{\n> +\t\t/* Locate the VIMC Sensor B subdevice. */\n> +\t\tenumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());\n> +\t\tif (!enumerator_) {\n> +\t\t\tcerr << \"Failed to create device enumerator\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tif (enumerator_->enumerate()) {\n> +\t\t\tcerr << \"Failed to enumerate media devices\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tDeviceMatch dm(\"vimc\");\n> +\t\tmedia_ = enumerator_->search(dm);\n> +\t\tif (!media_) {\n> +\t\t\tcerr << \"No VIMC media device found: skip test\" << endl;\n> +\t\t\treturn TestSkip;\n> +\t\t}\n> +\n> +\t\tMediaEntity *entity = media_->getEntityByName(\"Sensor A\");\n> +\t\tif (!entity) {\n> +\t\t\tcerr << \"Unable to find media entity 'Sensor A'\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tsubdev_ = new V4L2Subdevice(entity);\n> +\t\tif (subdev_->open() < 0) {\n> +\t\t\tcerr << \"Unable to open 'Sensor A' subdevice\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\t/* Force usage of the C API as that's what we want to test. */\n> +\t\tint ret = setenv(\"LIBCAMERA_IPA_FORCE_C_API\", \"\", 1);\n> +\t\tif (ret)\n> +\t\t\treturn TestFail;\n> +\n> +\t\tstd::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();\n> +\t\twrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));\n> +\t\twrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);\n> +\n> +\t\t/* Create a file descriptor for the buffer-related operations. */\n> +\t\tfd_ = open(\"/tmp\", O_TMPFILE | O_RDWR, 0600);\n> +\t\tif (fd_ == -1)\n> +\t\t\treturn TestFail;\n> +\n> +\t\tftruncate(fd_, 4096);\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tint run() override\n> +\t{\n> +\t\tint ret;\n> +\n> +\t\t/* Test configure(). */\n> +\t\tstd::map<unsigned int, IPAStream> config{\n> +\t\t\t{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },\n> +\t\t\t{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },\n> +\t\t};\n> +\t\tstd::map<unsigned int, const ControlInfoMap &> controlInfo;\n> +\t\tcontrolInfo.emplace(42, subdev_->controls());\n> +\t\tret = INVOKE(configure, config, controlInfo);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test mapBuffers(). */\n> +\t\tstd::vector<IPABuffer> buffers(2);\n> +\t\tbuffers[0].memory.planes().resize(3);\n> +\t\tbuffers[0].id = 10;\n> +\t\tbuffers[0].memory.planes()[0].setDmabuf(fd_, 4096);\n> +\t\tbuffers[1].id = 11;\n> +\t\tbuffers[1].memory.planes().resize(3);\n> +\t\tbuffers[1].memory.planes()[0].setDmabuf(fd_, 4096);\n> +\t\tbuffers[1].memory.planes()[1].setDmabuf(fd_, 4096);\n> +\n> +\t\tret = INVOKE(mapBuffers, buffers);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test unmapBuffers(). */\n> +\t\tstd::vector<unsigned int> bufferIds = { 10, 11 };\n> +\t\tret = INVOKE(unmapBuffers, bufferIds);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test processEvent(). */\n> +\t\tIPAOperationData data;\n> +\t\tdata.operation = Op_processEvent;\n> +\t\tdata.data = { 1, 2, 3, 4 };\n> +\t\tdata.controls.emplace_back(subdev_->controls());\n> +\n> +\t\tControlList &controls = data.controls.back();\n> +\t\tcontrols.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));\n> +\t\tcontrols.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));\n> +\t\tcontrols.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));\n> +\n> +\t\tret = INVOKE(processEvent, data);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/*\n> +\t\t * Test init() last to ensure nothing in the wrappers or\n> +\t\t * serializer depends on init() being called first.\n> +\t\t */\n> +\t\tret = INVOKE(init);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tvoid cleanup() override\n> +\t{\n> +\t\tdelete wrapper_;\n> +\t\tdelete subdev_;\n> +\n> +\t\tif (fd_ != -1)\n> +\t\t\tclose(fd_);\n> +\t}\n> +\n> +private:\n> +\ttemplate<typename T, typename... Args1, typename... Args2>\n> +\tint invoke(T (IPAInterface::*func)(Args1...), Operation op,\n> +\t\t   const char *name, Args2... args)\n> +\t{\n> +\t\tdata_ = IPAOperationData();\n> +\t\t(wrapper_->*func)(args...);\n> +\n> +\t\tif (frame_ != sequence_) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): invalid frame number \" << frame_\n> +\t\t\t     << \", expected \" << sequence_;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tsequence_++;\n> +\n> +\t\tif (data_.operation != op) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): failed to propagate\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tif (data_.data[0] != TestPass) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): reported an error\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tvoid queueFrameAction(unsigned int frame, const IPAOperationData &data)\n> +\t{\n> +\t\tframe_ = frame;\n> +\t\tdata_ = data;\n> +\t}\n> +\n> +\tstd::shared_ptr<MediaDevice> media_;\n> +\tstd::unique_ptr<DeviceEnumerator> enumerator_;\n> +\tV4L2Subdevice *subdev_;\n> +\n> +\tIPAContextWrapper *wrapper_;\n> +\tIPAOperationData data_;\n> +\tunsigned int sequence_;\n> +\tunsigned int frame_;\n> +\tint fd_;\n> +};\n> +\n> +TEST_REGISTER(IPAWrappersTest)\n> diff --git a/test/ipa/meson.build b/test/ipa/meson.build\n> index c501fcf99aed..f925c50a085e 100644\n> --- a/test/ipa/meson.build\n> +++ b/test/ipa/meson.build\n> @@ -1,13 +1,14 @@\n>  ipa_test = [\n>      ['ipa_module_test',     'ipa_module_test.cpp'],\n>      ['ipa_interface_test',  'ipa_interface_test.cpp'],\n> +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],\n>  ]\n>  \n>  foreach t : ipa_test\n>      exe = executable(t[0], t[1],\n>                       dependencies : libcamera_dep,\n> -                     link_with : test_libraries,\n> -                     include_directories : test_includes_internal)\n> +                     link_with : [libipa, test_libraries],\n> +                     include_directories : [libipa_includes, test_includes_internal])\n>  \n>      test(t[0], exe, suite : 'ipa')\n>  endforeach\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x241.google.com (mail-lj1-x241.google.com\n\t[IPv6:2a00:1450:4864:20::241])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5A9F460C12\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 Nov 2019 00:28:39 +0100 (CET)","by mail-lj1-x241.google.com with SMTP id k15so21038724lja.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 18 Nov 2019 15:28:39 -0800 (PST)","from localhost (h-93-159.A463.priv.bahnhof.se. [46.59.93.159])\n\tby smtp.gmail.com with ESMTPSA id\n\ts20sm2652376lfb.6.2019.11.18.15.28.37\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 18 Nov 2019 15:28:37 -0800 (PST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=Oz2An037UrMWc8zfVj0UrFifmHGbnD5OX2MVRa8915c=;\n\tb=rCQrejPjBK8RcQV7A0V5KFFff2SDjQSlMdbVcHcTf2xYgSJy3KcfYLXUFx6CUqLyiN\n\tjnVH+rO3zzWUKXJKwZRvKa5QQhcMMPZIT8pAMvNBAuli4XjDK1MDKdJDSpRu27APZdh+\n\t5jiP3eaYxDsoYvtrn07WRBsdP47wB/RPHZHDx3/aH5j/2Hl+dszTOwvBv93uo2OzrvvM\n\tGnyqdriApn5BE8UNbahNaMjqoJWyY+rqw5dO2rpip4aebHpovh59gNunRAXAqRSRoMGZ\n\ty4EVNNj/iRGqAzq+cXz3KF0VTt7hZgNS0Lenj2Rct+ZPMj3TrTdcD64Od/Ifvwmjifwx\n\tyYFg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=Oz2An037UrMWc8zfVj0UrFifmHGbnD5OX2MVRa8915c=;\n\tb=OKIAR3yLNZMdthAwh3BfeN8R3kAVCGIIWVuMCAR+JCor31ofRiwnOAlevtjEMnd8z9\n\t48vGKX8xQlvk+6aeITFsSOQKB4qHPZXKbusGn1uz2Amn2NzH4+cgMTL2DuHnvANr/0m7\n\tkCzr3cjd2Nymh80qWLmk/nvb6AVrgTl1pGY705kdqXiVwmrZ3dPzEvcMof7AkWkd1RVm\n\tSVB0H5ocm/RcUgOGRJxmTRJyLfHTRk6A+25+99PfvAUMegRaTB053MQtlSddyO9J6CNi\n\tF6dKvj+66BbqtwDslXFkRQ6bCXHsauW8EEZPvxlFf4DLdhHZuNVM8AP8cZNnDGc+/VqU\n\tYBZA==","X-Gm-Message-State":"APjAAAUylZeOU7Ty+rFRRwXskFlpOp5fMfs2B7sR60sLHOxpF1c2cvNy\n\tIch8jB/ag2DJAj14RFqAdV122s9BKEU=","X-Google-Smtp-Source":"APXvYqwHPqzqw5C61CQg700PFj+FJIAKtgQwBvx6+8iHRzKN7XSltfbVoLchYrMbv0x/ul6Kct3pmA==","X-Received":"by 2002:a2e:9659:: with SMTP id\n\tz25mr1428294ljh.132.1574119718585; \n\tMon, 18 Nov 2019 15:28:38 -0800 (PST)","Date":"Tue, 19 Nov 2019 00:28:36 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191118232836.GQ8072@bigcity.dyn.berto.se>","References":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>\n\t<20191108205409.18845-24-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20191108205409.18845-24-laurent.pinchart@ideasonboard.com>","User-Agent":"Mutt/1.12.1 (2019-06-15)","Subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","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>","X-List-Received-Date":"Mon, 18 Nov 2019 23:28:39 -0000"}},{"id":3103,"web_url":"https://patchwork.libcamera.org/comment/3103/","msgid":"<20191119162153.oow4lcj44mk54su3@uno.localdomain>","date":"2019-11-19T16:21:53","subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\nOn Fri, Nov 08, 2019 at 10:54:08PM +0200, Laurent Pinchart wrote:\n> Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,\n> and verify that the translation between C and C++ APIs work correctly.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++\n>  test/ipa/meson.build           |   5 +-\n>  2 files changed, 393 insertions(+), 2 deletions(-)\n>  create mode 100644 test/ipa/ipa_wrappers_test.cpp\n>\n> diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp\n> new file mode 100644\n> index 000000000000..c0717f0e301b\n> --- /dev/null\n> +++ b/test/ipa/ipa_wrappers_test.cpp\n> @@ -0,0 +1,390 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers\n> + */\n> +\n> +#include <fcntl.h>\n> +#include <iostream>\n> +#include <memory>\n> +#include <linux/videodev2.h>\n> +#include <sys/stat.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/controls.h>\n> +#include <libipa/ipa_interface_wrapper.h>\n> +\n> +#include \"device_enumerator.h\"\n> +#include \"ipa_context_wrapper.h\"\n> +#include \"media_device.h\"\n> +#include \"utils.h\"\n> +#include \"v4l2_subdevice.h\"\n> +\n> +#include \"test.h\"\n> +\n> +using namespace libcamera;\n> +using namespace std;\n> +\n> +enum Operation {\n> +\tOp_init,\n> +\tOp_configure,\n> +\tOp_mapBuffers,\n> +\tOp_unmapBuffers,\n> +\tOp_processEvent,\n> +};\n> +\n> +class TestIPAInterface : public IPAInterface\n> +{\n> +public:\n> +\tTestIPAInterface()\n> +\t\t: sequence_(0)\n> +\t{\n> +\t}\n> +\n> +\tint init() override\n> +\t{\n> +\t\treport(Op_init, TestPass);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tvoid configure(const std::map<unsigned int, IPAStream> &streamConfig,\n> +\t\t       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override\n> +\t{\n> +\t\t/* Verify streamConfig. */\n> +\t\tif (streamConfig.size() != 2) {\n> +\t\t\tcerr << \"configure(): Invalid number of streams \"\n> +\t\t\t     << streamConfig.size() << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\tauto iter = streamConfig.find(1);\n> +\t\tif (iter == streamConfig.end()) {\n> +\t\t\tcerr << \"configure(): No configuration for stream 1\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\t\tconst IPAStream *stream = &iter->second;\n> +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||\n> +\t\t    stream->size != Size{ 1024, 768 }) {\n> +\t\t\tcerr << \"configure(): Invalid configuration for stream 1\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\titer = streamConfig.find(2);\n> +\t\tif (iter == streamConfig.end()) {\n> +\t\t\tcerr << \"configure(): No configuration for stream 2\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\t\tstream = &iter->second;\n> +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||\n> +\t\t    stream->size != Size{ 800, 600 }) {\n> +\t\t\tcerr << \"configure(): Invalid configuration for stream 2\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\t/* Verify entityControls. */\n> +\t\tauto ctrlIter = entityControls.find(42);\n> +\t\tif (ctrlIter == entityControls.end()) {\n> +\t\t\tcerr << \"configure(): Controls not found\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\tconst ControlInfoMap &infoMap = ctrlIter->second;\n> +\n> +\t\tif (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||\n> +\t\t    infoMap.count(V4L2_CID_CONTRAST) != 1 ||\n> +\t\t    infoMap.count(V4L2_CID_SATURATION) != 1) {\n> +\t\t\tcerr << \"configure(): Invalid control IDs\" << endl;\n> +\t\t\treturn report(Op_configure, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_configure, TestPass);\n> +\t}\n> +\n> +\tvoid mapBuffers(const std::vector<IPABuffer> &buffers) override\n> +\t{\n> +\t\tif (buffers.size() != 2) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid number of buffers \"\n> +\t\t\t     << buffers.size() << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].id != 10 ||\n> +\t\t    buffers[1].id != 11) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid buffer IDs\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes().size() != 3 ||\n> +\t\t    buffers[1].memory.planes().size() != 3) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid number of planes\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes()[0].length() != 4096 ||\n> +\t\t    buffers[0].memory.planes()[1].length() != 0 ||\n> +\t\t    buffers[0].memory.planes()[2].length() != 0 ||\n> +\t\t    buffers[0].memory.planes()[0].length() != 4096 ||\n> +\t\t    buffers[1].memory.planes()[1].length() != 4096 ||\n> +\t\t    buffers[1].memory.planes()[2].length() != 0) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid length\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> +\t\t    buffers[0].memory.planes()[1].dmabuf() != -1 ||\n> +\t\t    buffers[0].memory.planes()[2].dmabuf() != -1 ||\n> +\t\t    buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> +\t\t    buffers[1].memory.planes()[1].dmabuf() == -1 ||\n> +\t\t    buffers[1].memory.planes()[2].dmabuf() != -1) {\n> +\t\t\tcerr << \"mapBuffers(): Invalid dmabuf\" << endl;\n> +\t\t\treturn report(Op_mapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_mapBuffers, TestPass);\n> +\t}\n> +\n> +\tvoid unmapBuffers(const std::vector<unsigned int> &ids) override\n> +\t{\n> +\t\tif (ids.size() != 2) {\n> +\t\t\tcerr << \"unmapBuffers(): Invalid number of ids \"\n> +\t\t\t     << ids.size() << endl;\n> +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\tif (ids[0] != 10 || ids[1] != 11) {\n> +\t\t\tcerr << \"unmapBuffers(): Invalid buffer IDs\" << endl;\n> +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_unmapBuffers, TestPass);\n> +\t}\n> +\n> +\tvoid processEvent(const IPAOperationData &data) override\n> +\t{\n> +\t\t/* Verify operation and data. */\n> +\t\tif (data.operation != Op_processEvent) {\n> +\t\t\tcerr << \"processEvent(): Invalid operation \"\n> +\t\t\t     << data.operation << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\tif (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {\n> +\t\t\tcerr << \"processEvent(): Invalid data\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\t/* Verify controls. */\n> +\t\tif (data.controls.size() != 1) {\n> +\t\t\tcerr << \"processEvent(): Controls not found\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\tconst ControlList &controls = data.controls[0];\n> +\t\tif (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||\n> +\t\t    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||\n> +\t\t    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {\n> +\t\t\tcerr << \"processEvent(): Invalid controls\" << endl;\n> +\t\t\treturn report(Op_processEvent, TestFail);\n> +\t\t}\n> +\n> +\t\treport(Op_processEvent, TestPass);\n> +\t}\n> +\n> +private:\n> +\tvoid report(Operation op, int status)\n> +\t{\n> +\t\tIPAOperationData data;\n> +\t\tdata.operation = op;\n> +\t\tdata.data.resize(1);\n> +\t\tdata.data[0] = status;\n> +\t\tqueueFrameAction.emit(sequence_++, data);\n> +\t}\n> +\n> +\tunsigned int sequence_;\n> +};\n> +\n> +#define INVOKE(method, ...) \\\n> +\tinvoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)\n> +\n> +class IPAWrappersTest : public Test\n> +{\n> +public:\n> +\tIPAWrappersTest()\n> +\t\t: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)\n> +\t{\n> +\t}\n> +\n> +protected:\n> +\tint init() override\n> +\t{\n> +\t\t/* Locate the VIMC Sensor B subdevice. */\n> +\t\tenumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());\n> +\t\tif (!enumerator_) {\n> +\t\t\tcerr << \"Failed to create device enumerator\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tif (enumerator_->enumerate()) {\n> +\t\t\tcerr << \"Failed to enumerate media devices\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tDeviceMatch dm(\"vimc\");\n> +\t\tmedia_ = enumerator_->search(dm);\n> +\t\tif (!media_) {\n> +\t\t\tcerr << \"No VIMC media device found: skip test\" << endl;\n> +\t\t\treturn TestSkip;\n> +\t\t}\n> +\n> +\t\tMediaEntity *entity = media_->getEntityByName(\"Sensor A\");\n> +\t\tif (!entity) {\n> +\t\t\tcerr << \"Unable to find media entity 'Sensor A'\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tsubdev_ = new V4L2Subdevice(entity);\n> +\t\tif (subdev_->open() < 0) {\n> +\t\t\tcerr << \"Unable to open 'Sensor A' subdevice\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\t/* Force usage of the C API as that's what we want to test. */\n> +\t\tint ret = setenv(\"LIBCAMERA_IPA_FORCE_C_API\", \"\", 1);\n> +\t\tif (ret)\n> +\t\t\treturn TestFail;\n> +\n> +\t\tstd::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();\n> +\t\twrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));\n> +\t\twrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);\n> +\n> +\t\t/* Create a file descriptor for the buffer-related operations. */\n> +\t\tfd_ = open(\"/tmp\", O_TMPFILE | O_RDWR, 0600);\n> +\t\tif (fd_ == -1)\n> +\t\t\treturn TestFail;\n> +\n> +\t\tftruncate(fd_, 4096);\n\nWhen building on Chromium, which by default enables the -Wunused-result flag,\nthis line results in a build error.\n\nlibcamera/test/ipa/ipa_wrappers_test.cpp:266:3: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result]\n\n\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tint run() override\n> +\t{\n> +\t\tint ret;\n> +\n> +\t\t/* Test configure(). */\n> +\t\tstd::map<unsigned int, IPAStream> config{\n> +\t\t\t{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },\n> +\t\t\t{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },\n> +\t\t};\n> +\t\tstd::map<unsigned int, const ControlInfoMap &> controlInfo;\n> +\t\tcontrolInfo.emplace(42, subdev_->controls());\n> +\t\tret = INVOKE(configure, config, controlInfo);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test mapBuffers(). */\n> +\t\tstd::vector<IPABuffer> buffers(2);\n> +\t\tbuffers[0].memory.planes().resize(3);\n> +\t\tbuffers[0].id = 10;\n> +\t\tbuffers[0].memory.planes()[0].setDmabuf(fd_, 4096);\n> +\t\tbuffers[1].id = 11;\n> +\t\tbuffers[1].memory.planes().resize(3);\n> +\t\tbuffers[1].memory.planes()[0].setDmabuf(fd_, 4096);\n> +\t\tbuffers[1].memory.planes()[1].setDmabuf(fd_, 4096);\n> +\n> +\t\tret = INVOKE(mapBuffers, buffers);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test unmapBuffers(). */\n> +\t\tstd::vector<unsigned int> bufferIds = { 10, 11 };\n> +\t\tret = INVOKE(unmapBuffers, bufferIds);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/* Test processEvent(). */\n> +\t\tIPAOperationData data;\n> +\t\tdata.operation = Op_processEvent;\n> +\t\tdata.data = { 1, 2, 3, 4 };\n> +\t\tdata.controls.emplace_back(subdev_->controls());\n> +\n> +\t\tControlList &controls = data.controls.back();\n> +\t\tcontrols.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));\n> +\t\tcontrols.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));\n> +\t\tcontrols.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));\n> +\n> +\t\tret = INVOKE(processEvent, data);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\t/*\n> +\t\t * Test init() last to ensure nothing in the wrappers or\n> +\t\t * serializer depends on init() being called first.\n> +\t\t */\n> +\t\tret = INVOKE(init);\n> +\t\tif (ret == TestFail)\n> +\t\t\treturn TestFail;\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tvoid cleanup() override\n> +\t{\n> +\t\tdelete wrapper_;\n> +\t\tdelete subdev_;\n> +\n> +\t\tif (fd_ != -1)\n> +\t\t\tclose(fd_);\n> +\t}\n> +\n> +private:\n> +\ttemplate<typename T, typename... Args1, typename... Args2>\n> +\tint invoke(T (IPAInterface::*func)(Args1...), Operation op,\n> +\t\t   const char *name, Args2... args)\n> +\t{\n> +\t\tdata_ = IPAOperationData();\n> +\t\t(wrapper_->*func)(args...);\n> +\n> +\t\tif (frame_ != sequence_) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): invalid frame number \" << frame_\n> +\t\t\t     << \", expected \" << sequence_;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tsequence_++;\n> +\n> +\t\tif (data_.operation != op) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): failed to propagate\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\tif (data_.data[0] != TestPass) {\n> +\t\t\tcerr << \"IPAInterface::\" << name\n> +\t\t\t     << \"(): reported an error\" << endl;\n> +\t\t\treturn TestFail;\n> +\t\t}\n> +\n> +\t\treturn TestPass;\n> +\t}\n> +\n> +\tvoid queueFrameAction(unsigned int frame, const IPAOperationData &data)\n> +\t{\n> +\t\tframe_ = frame;\n> +\t\tdata_ = data;\n> +\t}\n> +\n> +\tstd::shared_ptr<MediaDevice> media_;\n> +\tstd::unique_ptr<DeviceEnumerator> enumerator_;\n> +\tV4L2Subdevice *subdev_;\n> +\n> +\tIPAContextWrapper *wrapper_;\n> +\tIPAOperationData data_;\n> +\tunsigned int sequence_;\n> +\tunsigned int frame_;\n> +\tint fd_;\n> +};\n> +\n> +TEST_REGISTER(IPAWrappersTest)\n> diff --git a/test/ipa/meson.build b/test/ipa/meson.build\n> index c501fcf99aed..f925c50a085e 100644\n> --- a/test/ipa/meson.build\n> +++ b/test/ipa/meson.build\n> @@ -1,13 +1,14 @@\n>  ipa_test = [\n>      ['ipa_module_test',     'ipa_module_test.cpp'],\n>      ['ipa_interface_test',  'ipa_interface_test.cpp'],\n> +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],\n>  ]\n>\n>  foreach t : ipa_test\n>      exe = executable(t[0], t[1],\n>                       dependencies : libcamera_dep,\n> -                     link_with : test_libraries,\n> -                     include_directories : test_includes_internal)\n> +                     link_with : [libipa, test_libraries],\n> +                     include_directories : [libipa_includes, test_includes_internal])\n>\n>      test(t[0], exe, suite : 'ipa')\n>  endforeach\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net\n\t[217.70.183.199])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AA50060BC2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 Nov 2019 17:19:51 +0100 (CET)","from uno.localdomain (93-34-114-233.ip49.fastwebnet.it\n\t[93.34.114.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay9-d.mail.gandi.net (Postfix) with ESMTPSA id 35F86FF805;\n\tTue, 19 Nov 2019 16:19:51 +0000 (UTC)"],"X-Originating-IP":"93.34.114.233","Date":"Tue, 19 Nov 2019 17:21:53 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191119162153.oow4lcj44mk54su3@uno.localdomain>","References":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>\n\t<20191108205409.18845-24-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"4rsvbkrpxjfo5jf7\"","Content-Disposition":"inline","In-Reply-To":"<20191108205409.18845-24-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","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>","X-List-Received-Date":"Tue, 19 Nov 2019 16:19:51 -0000"}},{"id":3112,"web_url":"https://patchwork.libcamera.org/comment/3112/","msgid":"<20191119224627.GL17590@pendragon.ideasonboard.com>","date":"2019-11-19T22:46:27","subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Kieran,\n\nOn Tue, Nov 19, 2019 at 05:21:53PM +0100, Jacopo Mondi wrote:\n> On Fri, Nov 08, 2019 at 10:54:08PM +0200, Laurent Pinchart wrote:\n> > Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,\n> > and verify that the translation between C and C++ APIs work correctly.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++\n> >  test/ipa/meson.build           |   5 +-\n> >  2 files changed, 393 insertions(+), 2 deletions(-)\n> >  create mode 100644 test/ipa/ipa_wrappers_test.cpp\n> >\n> > diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp\n> > new file mode 100644\n> > index 000000000000..c0717f0e301b\n> > --- /dev/null\n> > +++ b/test/ipa/ipa_wrappers_test.cpp\n> > @@ -0,0 +1,390 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers\n> > + */\n> > +\n> > +#include <fcntl.h>\n> > +#include <iostream>\n> > +#include <memory>\n> > +#include <linux/videodev2.h>\n> > +#include <sys/stat.h>\n> > +#include <unistd.h>\n> > +\n> > +#include <libcamera/controls.h>\n> > +#include <libipa/ipa_interface_wrapper.h>\n> > +\n> > +#include \"device_enumerator.h\"\n> > +#include \"ipa_context_wrapper.h\"\n> > +#include \"media_device.h\"\n> > +#include \"utils.h\"\n> > +#include \"v4l2_subdevice.h\"\n> > +\n> > +#include \"test.h\"\n> > +\n> > +using namespace libcamera;\n> > +using namespace std;\n> > +\n> > +enum Operation {\n> > +\tOp_init,\n> > +\tOp_configure,\n> > +\tOp_mapBuffers,\n> > +\tOp_unmapBuffers,\n> > +\tOp_processEvent,\n> > +};\n> > +\n> > +class TestIPAInterface : public IPAInterface\n> > +{\n> > +public:\n> > +\tTestIPAInterface()\n> > +\t\t: sequence_(0)\n> > +\t{\n> > +\t}\n> > +\n> > +\tint init() override\n> > +\t{\n> > +\t\treport(Op_init, TestPass);\n> > +\t\treturn 0;\n> > +\t}\n> > +\n> > +\tvoid configure(const std::map<unsigned int, IPAStream> &streamConfig,\n> > +\t\t       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override\n> > +\t{\n> > +\t\t/* Verify streamConfig. */\n> > +\t\tif (streamConfig.size() != 2) {\n> > +\t\t\tcerr << \"configure(): Invalid number of streams \"\n> > +\t\t\t     << streamConfig.size() << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tauto iter = streamConfig.find(1);\n> > +\t\tif (iter == streamConfig.end()) {\n> > +\t\t\tcerr << \"configure(): No configuration for stream 1\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\t\tconst IPAStream *stream = &iter->second;\n> > +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||\n> > +\t\t    stream->size != Size{ 1024, 768 }) {\n> > +\t\t\tcerr << \"configure(): Invalid configuration for stream 1\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\n> > +\t\titer = streamConfig.find(2);\n> > +\t\tif (iter == streamConfig.end()) {\n> > +\t\t\tcerr << \"configure(): No configuration for stream 2\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\t\tstream = &iter->second;\n> > +\t\tif (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||\n> > +\t\t    stream->size != Size{ 800, 600 }) {\n> > +\t\t\tcerr << \"configure(): Invalid configuration for stream 2\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\n> > +\t\t/* Verify entityControls. */\n> > +\t\tauto ctrlIter = entityControls.find(42);\n> > +\t\tif (ctrlIter == entityControls.end()) {\n> > +\t\t\tcerr << \"configure(): Controls not found\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tconst ControlInfoMap &infoMap = ctrlIter->second;\n> > +\n> > +\t\tif (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||\n> > +\t\t    infoMap.count(V4L2_CID_CONTRAST) != 1 ||\n> > +\t\t    infoMap.count(V4L2_CID_SATURATION) != 1) {\n> > +\t\t\tcerr << \"configure(): Invalid control IDs\" << endl;\n> > +\t\t\treturn report(Op_configure, TestFail);\n> > +\t\t}\n> > +\n> > +\t\treport(Op_configure, TestPass);\n> > +\t}\n> > +\n> > +\tvoid mapBuffers(const std::vector<IPABuffer> &buffers) override\n> > +\t{\n> > +\t\tif (buffers.size() != 2) {\n> > +\t\t\tcerr << \"mapBuffers(): Invalid number of buffers \"\n> > +\t\t\t     << buffers.size() << endl;\n> > +\t\t\treturn report(Op_mapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (buffers[0].id != 10 ||\n> > +\t\t    buffers[1].id != 11) {\n> > +\t\t\tcerr << \"mapBuffers(): Invalid buffer IDs\" << endl;\n> > +\t\t\treturn report(Op_mapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (buffers[0].memory.planes().size() != 3 ||\n> > +\t\t    buffers[1].memory.planes().size() != 3) {\n> > +\t\t\tcerr << \"mapBuffers(): Invalid number of planes\" << endl;\n> > +\t\t\treturn report(Op_mapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (buffers[0].memory.planes()[0].length() != 4096 ||\n> > +\t\t    buffers[0].memory.planes()[1].length() != 0 ||\n> > +\t\t    buffers[0].memory.planes()[2].length() != 0 ||\n> > +\t\t    buffers[0].memory.planes()[0].length() != 4096 ||\n> > +\t\t    buffers[1].memory.planes()[1].length() != 4096 ||\n> > +\t\t    buffers[1].memory.planes()[2].length() != 0) {\n> > +\t\t\tcerr << \"mapBuffers(): Invalid length\" << endl;\n> > +\t\t\treturn report(Op_mapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> > +\t\t    buffers[0].memory.planes()[1].dmabuf() != -1 ||\n> > +\t\t    buffers[0].memory.planes()[2].dmabuf() != -1 ||\n> > +\t\t    buffers[0].memory.planes()[0].dmabuf() == -1 ||\n> > +\t\t    buffers[1].memory.planes()[1].dmabuf() == -1 ||\n> > +\t\t    buffers[1].memory.planes()[2].dmabuf() != -1) {\n> > +\t\t\tcerr << \"mapBuffers(): Invalid dmabuf\" << endl;\n> > +\t\t\treturn report(Op_mapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\treport(Op_mapBuffers, TestPass);\n> > +\t}\n> > +\n> > +\tvoid unmapBuffers(const std::vector<unsigned int> &ids) override\n> > +\t{\n> > +\t\tif (ids.size() != 2) {\n> > +\t\t\tcerr << \"unmapBuffers(): Invalid number of ids \"\n> > +\t\t\t     << ids.size() << endl;\n> > +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (ids[0] != 10 || ids[1] != 11) {\n> > +\t\t\tcerr << \"unmapBuffers(): Invalid buffer IDs\" << endl;\n> > +\t\t\treturn report(Op_unmapBuffers, TestFail);\n> > +\t\t}\n> > +\n> > +\t\treport(Op_unmapBuffers, TestPass);\n> > +\t}\n> > +\n> > +\tvoid processEvent(const IPAOperationData &data) override\n> > +\t{\n> > +\t\t/* Verify operation and data. */\n> > +\t\tif (data.operation != Op_processEvent) {\n> > +\t\t\tcerr << \"processEvent(): Invalid operation \"\n> > +\t\t\t     << data.operation << endl;\n> > +\t\t\treturn report(Op_processEvent, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tif (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {\n> > +\t\t\tcerr << \"processEvent(): Invalid data\" << endl;\n> > +\t\t\treturn report(Op_processEvent, TestFail);\n> > +\t\t}\n> > +\n> > +\t\t/* Verify controls. */\n> > +\t\tif (data.controls.size() != 1) {\n> > +\t\t\tcerr << \"processEvent(): Controls not found\" << endl;\n> > +\t\t\treturn report(Op_processEvent, TestFail);\n> > +\t\t}\n> > +\n> > +\t\tconst ControlList &controls = data.controls[0];\n> > +\t\tif (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||\n> > +\t\t    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||\n> > +\t\t    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {\n> > +\t\t\tcerr << \"processEvent(): Invalid controls\" << endl;\n> > +\t\t\treturn report(Op_processEvent, TestFail);\n> > +\t\t}\n> > +\n> > +\t\treport(Op_processEvent, TestPass);\n> > +\t}\n> > +\n> > +private:\n> > +\tvoid report(Operation op, int status)\n> > +\t{\n> > +\t\tIPAOperationData data;\n> > +\t\tdata.operation = op;\n> > +\t\tdata.data.resize(1);\n> > +\t\tdata.data[0] = status;\n> > +\t\tqueueFrameAction.emit(sequence_++, data);\n> > +\t}\n> > +\n> > +\tunsigned int sequence_;\n> > +};\n> > +\n> > +#define INVOKE(method, ...) \\\n> > +\tinvoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)\n> > +\n> > +class IPAWrappersTest : public Test\n> > +{\n> > +public:\n> > +\tIPAWrappersTest()\n> > +\t\t: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)\n> > +\t{\n> > +\t}\n> > +\n> > +protected:\n> > +\tint init() override\n> > +\t{\n> > +\t\t/* Locate the VIMC Sensor B subdevice. */\n> > +\t\tenumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());\n> > +\t\tif (!enumerator_) {\n> > +\t\t\tcerr << \"Failed to create device enumerator\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\tif (enumerator_->enumerate()) {\n> > +\t\t\tcerr << \"Failed to enumerate media devices\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\tDeviceMatch dm(\"vimc\");\n> > +\t\tmedia_ = enumerator_->search(dm);\n> > +\t\tif (!media_) {\n> > +\t\t\tcerr << \"No VIMC media device found: skip test\" << endl;\n> > +\t\t\treturn TestSkip;\n> > +\t\t}\n> > +\n> > +\t\tMediaEntity *entity = media_->getEntityByName(\"Sensor A\");\n> > +\t\tif (!entity) {\n> > +\t\t\tcerr << \"Unable to find media entity 'Sensor A'\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\tsubdev_ = new V4L2Subdevice(entity);\n> > +\t\tif (subdev_->open() < 0) {\n> > +\t\t\tcerr << \"Unable to open 'Sensor A' subdevice\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\t/* Force usage of the C API as that's what we want to test. */\n> > +\t\tint ret = setenv(\"LIBCAMERA_IPA_FORCE_C_API\", \"\", 1);\n> > +\t\tif (ret)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\tstd::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();\n> > +\t\twrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));\n> > +\t\twrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);\n> > +\n> > +\t\t/* Create a file descriptor for the buffer-related operations. */\n> > +\t\tfd_ = open(\"/tmp\", O_TMPFILE | O_RDWR, 0600);\n> > +\t\tif (fd_ == -1)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\tftruncate(fd_, 4096);\n> \n> When building on Chromium, which by default enables the -Wunused-result flag,\n> this line results in a build error.\n> \n> libcamera/test/ipa/ipa_wrappers_test.cpp:266:3: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result]\n\nCould it be a libc version issue ? Adding -Wunused-result didn't produce\nany warning for me.\n\nI'll fix it nonetheless.\n\n> > +\n> > +\t\treturn TestPass;\n> > +\t}\n> > +\n> > +\tint run() override\n> > +\t{\n> > +\t\tint ret;\n> > +\n> > +\t\t/* Test configure(). */\n> > +\t\tstd::map<unsigned int, IPAStream> config{\n> > +\t\t\t{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },\n> > +\t\t\t{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },\n> > +\t\t};\n> > +\t\tstd::map<unsigned int, const ControlInfoMap &> controlInfo;\n> > +\t\tcontrolInfo.emplace(42, subdev_->controls());\n> > +\t\tret = INVOKE(configure, config, controlInfo);\n> > +\t\tif (ret == TestFail)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\t/* Test mapBuffers(). */\n> > +\t\tstd::vector<IPABuffer> buffers(2);\n> > +\t\tbuffers[0].memory.planes().resize(3);\n> > +\t\tbuffers[0].id = 10;\n> > +\t\tbuffers[0].memory.planes()[0].setDmabuf(fd_, 4096);\n> > +\t\tbuffers[1].id = 11;\n> > +\t\tbuffers[1].memory.planes().resize(3);\n> > +\t\tbuffers[1].memory.planes()[0].setDmabuf(fd_, 4096);\n> > +\t\tbuffers[1].memory.planes()[1].setDmabuf(fd_, 4096);\n> > +\n> > +\t\tret = INVOKE(mapBuffers, buffers);\n> > +\t\tif (ret == TestFail)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\t/* Test unmapBuffers(). */\n> > +\t\tstd::vector<unsigned int> bufferIds = { 10, 11 };\n> > +\t\tret = INVOKE(unmapBuffers, bufferIds);\n> > +\t\tif (ret == TestFail)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\t/* Test processEvent(). */\n> > +\t\tIPAOperationData data;\n> > +\t\tdata.operation = Op_processEvent;\n> > +\t\tdata.data = { 1, 2, 3, 4 };\n> > +\t\tdata.controls.emplace_back(subdev_->controls());\n> > +\n> > +\t\tControlList &controls = data.controls.back();\n> > +\t\tcontrols.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));\n> > +\t\tcontrols.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));\n> > +\t\tcontrols.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));\n> > +\n> > +\t\tret = INVOKE(processEvent, data);\n> > +\t\tif (ret == TestFail)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\t/*\n> > +\t\t * Test init() last to ensure nothing in the wrappers or\n> > +\t\t * serializer depends on init() being called first.\n> > +\t\t */\n> > +\t\tret = INVOKE(init);\n> > +\t\tif (ret == TestFail)\n> > +\t\t\treturn TestFail;\n> > +\n> > +\t\treturn TestPass;\n> > +\t}\n> > +\n> > +\tvoid cleanup() override\n> > +\t{\n> > +\t\tdelete wrapper_;\n> > +\t\tdelete subdev_;\n> > +\n> > +\t\tif (fd_ != -1)\n> > +\t\t\tclose(fd_);\n> > +\t}\n> > +\n> > +private:\n> > +\ttemplate<typename T, typename... Args1, typename... Args2>\n> > +\tint invoke(T (IPAInterface::*func)(Args1...), Operation op,\n> > +\t\t   const char *name, Args2... args)\n> > +\t{\n> > +\t\tdata_ = IPAOperationData();\n> > +\t\t(wrapper_->*func)(args...);\n> > +\n> > +\t\tif (frame_ != sequence_) {\n> > +\t\t\tcerr << \"IPAInterface::\" << name\n> > +\t\t\t     << \"(): invalid frame number \" << frame_\n> > +\t\t\t     << \", expected \" << sequence_;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\tsequence_++;\n> > +\n> > +\t\tif (data_.operation != op) {\n> > +\t\t\tcerr << \"IPAInterface::\" << name\n> > +\t\t\t     << \"(): failed to propagate\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\tif (data_.data[0] != TestPass) {\n> > +\t\t\tcerr << \"IPAInterface::\" << name\n> > +\t\t\t     << \"(): reported an error\" << endl;\n> > +\t\t\treturn TestFail;\n> > +\t\t}\n> > +\n> > +\t\treturn TestPass;\n> > +\t}\n> > +\n> > +\tvoid queueFrameAction(unsigned int frame, const IPAOperationData &data)\n> > +\t{\n> > +\t\tframe_ = frame;\n> > +\t\tdata_ = data;\n> > +\t}\n> > +\n> > +\tstd::shared_ptr<MediaDevice> media_;\n> > +\tstd::unique_ptr<DeviceEnumerator> enumerator_;\n> > +\tV4L2Subdevice *subdev_;\n> > +\n> > +\tIPAContextWrapper *wrapper_;\n> > +\tIPAOperationData data_;\n> > +\tunsigned int sequence_;\n> > +\tunsigned int frame_;\n> > +\tint fd_;\n> > +};\n> > +\n> > +TEST_REGISTER(IPAWrappersTest)\n> > diff --git a/test/ipa/meson.build b/test/ipa/meson.build\n> > index c501fcf99aed..f925c50a085e 100644\n> > --- a/test/ipa/meson.build\n> > +++ b/test/ipa/meson.build\n> > @@ -1,13 +1,14 @@\n> >  ipa_test = [\n> >      ['ipa_module_test',     'ipa_module_test.cpp'],\n> >      ['ipa_interface_test',  'ipa_interface_test.cpp'],\n> > +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],\n> >  ]\n> >\n> >  foreach t : ipa_test\n> >      exe = executable(t[0], t[1],\n> >                       dependencies : libcamera_dep,\n> > -                     link_with : test_libraries,\n> > -                     include_directories : test_includes_internal)\n> > +                     link_with : [libipa, test_libraries],\n> > +                     include_directories : [libipa_includes, test_includes_internal])\n> >\n> >      test(t[0], exe, suite : 'ipa')\n> >  endforeach","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["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 4AFAA60BEA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 Nov 2019 23:46:36 +0100 (CET)","from pendragon.ideasonboard.com (unknown [122.147.213.64])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C421E311;\n\tTue, 19 Nov 2019 23:46:34 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1574203595;\n\tbh=vv3Vq6eV+KRFy/4A5RwBXClcKFE5FnQ7Ccp1bwJPMYQ=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=uIXfIGq2TAASLrMSYle/nkupKYg67/5+oc+UXuUaXVmZPP+jttervd3dZi61Wj1XF\n\tDW7/s8BMGT0lAWANr4aaJdHrmfWy18eTjIT5aDZsssTuQxzYZDVdAVLaq8eG3LNU2E\n\t3W85WnZIeTwfn+bMuGrw8/Dmvao9OI+pdwOtnW1Q=","Date":"Wed, 20 Nov 2019 00:46:27 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191119224627.GL17590@pendragon.ideasonboard.com>","References":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>\n\t<20191108205409.18845-24-laurent.pinchart@ideasonboard.com>\n\t<20191119162153.oow4lcj44mk54su3@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20191119162153.oow4lcj44mk54su3@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 23/24] test: ipa: Add IPA wrappers\n\ttest","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>","X-List-Received-Date":"Tue, 19 Nov 2019 22:46:36 -0000"}}]