<div dir="ltr"><div dir="ltr">Hi Niklas,<div><br></div><div>Thank you for your work, this looks quite interesting.  However, I have a high level question, see below.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 29 Jan 2021 at 16:53, Niklas Söderlund <<a href="mailto:niklas.soderlund@ragnatech.se">niklas.soderlund@ragnatech.se</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Add a compliance tool to ease testing of cameras on target. In contrast<br>
to the unit-tests under test/ that aims to test the internal components<br>
of libcamera the compliance aims to test application use-cases and to<br>
some extend the public API.<br>
<br>
This change adds the boilerplate code for a simple framework to write<br>
tests in as well as two simple test. The tests aims both to demonstrate<br>
the tool and to catch real problems. The tests added are:<br>
<br>
 - Test that if one queues exactly N requests to a camera exactly N<br>
  Â requests are eventually completed.<br>
<br>
 - Test that a configured camera can be started and stopped multiple<br>
  Â times in an attempt to exercise cleanup code paths otherwise not<br>
  Â often tested with 'cam' for example.<br>
<br>
Example pass run on Raspberry Pi:<br>
<br>
  $ LIBCAMERA_LOG_LEVELS="*:ERROR" lc-compliance -c "/base/soc/i2c0mux/i2c@1/imx219@10"<br>
  [2:36:17.672447527] [11896] ERROR V4L2 v4l2_device.cpp:191 'imx219 10-0010': Control 0x009a0922 not found<br>
  [2:36:17.673095289] [11896] ERROR V4L2 v4l2_subdevice.cpp:285 'imx219 10-0010': Unable to get rectangle 2 on pad 0: Invalid argument<br>
  Using camera /base/soc/i2c0mux/i2c@1/imx219@10<br>
  Test single capture cycles<br>
  - SKIP - Camera needs 4 requests, can't test only 1<br>
  - SKIP - Camera needs 4 requests, can't test only 2<br>
  - SKIP - Camera needs 4 requests, can't test only 3<br></blockquote><div><br></div><div>I'm a bit curious about the above 3 lines.  From what I make out, it is saying that the pipeline cannot run with < 4 on-the-go requests, is that correct?  Does that determination come from the buffer count returned by PipelineHandler::GenerateConfiguration()?  I *think* the Raspberry Pi stack should be able to handle any number of on-the-go requests.  I say think, because I do not normally run in that configuration, but I do know some of our users might have.  The buffer count of 4 returned by PipelineHandlerRPi::generateConfiguration() is a somewhat educated guess for a reasonable number of buffers.  Is this something we need to update in our pipeline handler for this to work?</div><div><br></div><div>Regards,</div><div>Naush</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
  - PASS - Balanced capture of 5 requests with 1 start cycles<br>
  - PASS - Balanced capture of 8 requests with 1 start cycles<br>
  - PASS - Balanced capture of 13 requests with 1 start cycles<br>
  - PASS - Balanced capture of 21 requests with 1 start cycles<br>
  - PASS - Balanced capture of 34 requests with 1 start cycles<br>
  - PASS - Balanced capture of 55 requests with 1 start cycles<br>
  - PASS - Balanced capture of 89 requests with 1 start cycles<br>
  Test multiple start/stop cycles<br>
  - SKIP - Camera needs 4 requests, can't test only 1<br>
  - SKIP - Camera needs 4 requests, can't test only 2<br>
  - SKIP - Camera needs 4 requests, can't test only 3<br>
  - PASS - Balanced capture of 5 requests with 3 start cycles<br>
  - PASS - Balanced capture of 8 requests with 3 start cycles<br>
  - PASS - Balanced capture of 13 requests with 3 start cycles<br>
  - PASS - Balanced capture of 21 requests with 3 start cycles<br>
  - PASS - Balanced capture of 34 requests with 3 start cycles<br>
  - PASS - Balanced capture of 55 requests with 3 start cycles<br>
  - PASS - Balanced capture of 89 requests with 3 start cycles<br>
  20 tests executed, 14 tests passed, 6 tests skipped and 0 tests failed<br>
<br>
Example fail run on IPU3:<br>
<br>
  - LIBCAMERA_LOG_LEVELS="*:ERROR" lc-compliance -c "\_SB_.PCI0.I2C2.CAM0"<br>
  - [0:31:36.992763102] [6420] ERROR V4L2 v4l2_device.cpp:191 'ov13858 8-0010': Control 0x009a0922 not found<br>
  - [0:31:36.992815436] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 2 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.992847049] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 1 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.992869029] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993152932] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993172833] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov13858 8-0010': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993208149] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993224502] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov13858 8-0010': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993354770] [6420] ERROR V4L2 v4l2_device.cpp:191 'ov5670 10-0036': Control 0x009a0922 not found<br>
  - [0:31:36.993377622] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 2 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993398093] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 1 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993415611] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993660192] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993678492] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov5670 10-0036': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993724648] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993741032] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov5670 10-0036': The analogue crop rectangle has been defaulted to the active area size<br>
  - Using camera \_SB_.PCI0.I2C2.CAM0<br>
  - Test single capture cycles<br>
  - - SKIP - Camera needs 4 requests, can't test only 1<br>
  - - SKIP - Camera needs 4 requests, can't test only 2<br>
  - - SKIP - Camera needs 4 requests, can't test only 3<br>
  - - PASS - Balanced capture of 5 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 8 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 13 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 21 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 34 requests with 1 start cycles<br>
  - [0:31:41.969783243] [6420] ERROR IPU3 cio2.cpp:264 CIO2 buffer underrun<br>
  <Application lockup here as the pipeline error is not propagated to the application><br>
<br>
Signed-off-by: Niklas Söderlund <<a href="mailto:niklas.soderlund@ragnatech.se" target="_blank">niklas.soderlund@ragnatech.se</a>><br>
---<br>
Hi,<br>
<br>
I have tested this on RkISP1, RPi and IPU3 on latest master [1]. I plan<br>
to post separate patches which starts to address some of the problems<br>
found while working. A fix for 'cam --captur=N' is coming shortly for<br>
example.<br>
<br>
I still have to find a good fix for a races found in the<br>
IPU3 pipeline between the CIO2 and IMGU (as showed above). What I don't<br>
(yet) have a plan for is how to fix that request queueing errors are not<br>
propagated in our application facing API leading the test application to<br>
wait forever to a request the library knows have failed, ideas welcome.<br>
<br>
1. 958c80a4f1c28301 ("android: camera_device: Set AE precapture trigger according to request")<br>
---<br>
 src/lc-compliance/main.cpp  Â  Â  Â  Â  | 139 ++++++++++++++++++<br>
 src/lc-compliance/meson.build  Â  Â  Â |  24 ++++<br>
 src/lc-compliance/results.cpp  Â  Â  Â |  75 ++++++++++<br>
 src/lc-compliance/results.h  Â  Â  Â  Â |  45 ++++++<br>
 src/lc-compliance/single_stream.cpp | 210 ++++++++++++++++++++++++++++<br>
 src/lc-compliance/tests.h  Â  Â  Â  Â  Â |  16 +++<br>
 src/meson.build  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â |  Â 2 +<br>
 7 files changed, 511 insertions(+)<br>
 create mode 100644 src/lc-compliance/main.cpp<br>
 create mode 100644 src/lc-compliance/meson.build<br>
 create mode 100644 src/lc-compliance/results.cpp<br>
 create mode 100644 src/lc-compliance/results.h<br>
 create mode 100644 src/lc-compliance/single_stream.cpp<br>
 create mode 100644 src/lc-compliance/tests.h<br>
<br>
diff --git a/src/lc-compliance/main.cpp b/src/lc-compliance/main.cpp<br>
new file mode 100644<br>
index 0000000000000000..e1cbce7eac3df2bc<br>
--- /dev/null<br>
+++ b/src/lc-compliance/main.cpp<br>
@@ -0,0 +1,139 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * main.cpp - lc-compliance - The libcamera compliance tool<br>
+ */<br>
+<br>
+#include <iomanip><br>
+#include <iostream><br>
+#include <string.h><br>
+<br>
+#include <libcamera/libcamera.h><br>
+<br>
+#include "../cam/options.h"<br>
+#include "tests.h"<br>
+<br>
+using namespace libcamera;<br>
+<br>
+class Harness<br>
+{<br>
+public:<br>
+  Â  Â  Â Harness();<br>
+  Â  Â  Â ~Harness();<br>
+<br>
+  Â  Â  Â int exec(int argc, char **argv);<br>
+<br>
+private:<br>
+  Â  Â  Â enum {<br>
+  Â  Â  Â  Â  Â  Â  Â OptCamera = 'c',<br>
+  Â  Â  Â  Â  Â  Â  Â OptHelp = 'h',<br>
+  Â  Â  Â };<br>
+<br>
+  Â  Â  Â int parseOptions(int argc, char **argv);<br>
+  Â  Â  Â int init(int argc, char **argv);<br>
+<br>
+  Â  Â  Â OptionsParser::Options options_;<br>
+  Â  Â  Â std::unique_ptr<CameraManager> cm_;<br>
+  Â  Â  Â std::shared_ptr<Camera> camera_;<br>
+};<br>
+<br>
+Harness::Harness()<br>
+{<br>
+  Â  Â  Â cm_ = std::make_unique<CameraManager>();<br>
+}<br>
+<br>
+Harness::~Harness()<br>
+{<br>
+  Â  Â  Â if (camera_) {<br>
+  Â  Â  Â  Â  Â  Â  Â camera_->release();<br>
+  Â  Â  Â  Â  Â  Â  Â camera_.reset();<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â cm_->stop();<br>
+}<br>
+<br>
+int Harness::exec(int argc, char **argv)<br>
+{<br>
+  Â  Â  Â int ret = init(argc, argv);<br>
+  Â  Â  Â if (ret)<br>
+  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+<br>
+  Â  Â  Â std::vector<Results> results;<br>
+<br>
+  Â  Â  Â results.push_back(testSingleStream(camera_));<br>
+<br>
+  Â  Â  Â for (const Results &result : results) {<br>
+  Â  Â  Â  Â  Â  Â  Â ret = result.summary();<br>
+  Â  Â  Â  Â  Â  Â  Â if (ret)<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â return 0;<br>
+}<br>
+<br>
+int Harness::init(int argc, char **argv)<br>
+{<br>
+  Â  Â  Â int ret = parseOptions(argc, argv);<br>
+  Â  Â  Â if (ret < 0)<br>
+  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+<br>
+  Â  Â  Â ret = cm_->start();<br>
+  Â  Â  Â if (ret) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::cout << "Failed to start camera manager: "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â << strerror(-ret) << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â if (!options_.isSet(OptCamera)) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::cout << "No camera specified, available cameras:" << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â for (const std::shared_ptr<Camera> &cam : cm_->cameras())<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â std::cout << "- " << cam.get()->id() << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â return -ENODEV;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â const std::string &cameraId = options_[OptCamera];<br>
+  Â  Â  Â camera_ = cm_->get(cameraId);<br>
+  Â  Â  Â if (!camera_) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â for (const std::shared_ptr<Camera> &cam : cm_->cameras())<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â std::cout << "- " << cam.get()->id() << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â return -ENODEV;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â if (camera_->acquire()) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::cout << "Failed to acquire camera" << std::endl;<br>
+  Â  Â  Â  Â  Â  Â  Â return -EINVAL;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â std::cout << "Using camera " << cameraId << std::endl;<br>
+<br>
+  Â  Â  Â return 0;<br>
+}<br>
+<br>
+int Harness::parseOptions(int argc, char **argv)<br>
+{<br>
+  Â  Â  Â OptionsParser parser;<br>
+  Â  Â  Â parser.addOption(OptCamera, OptionString,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  "Specify which camera to operate on, by id", "camera",<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  ArgumentRequired, "camera");<br>
+  Â  Â  Â parser.addOption(OptHelp, OptionNone, "Display this help message",<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  "help");<br>
+<br>
+  Â  Â  Â options_ = parser.parse(argc, argv);<br>
+  Â  Â  Â if (!options_.valid())<br>
+  Â  Â  Â  Â  Â  Â  Â return -EINVAL;<br>
+<br>
+  Â  Â  Â if (options_.empty() || options_.isSet(OptHelp)) {<br>
+  Â  Â  Â  Â  Â  Â  Â parser.usage();<br>
+  Â  Â  Â  Â  Â  Â  Â return options_.empty() ? -EINVAL : -EINTR;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â return 0;<br>
+}<br>
+<br>
+int main(int argc, char **argv)<br>
+{<br>
+  Â  Â  Â Harness harness;<br>
+  Â  Â  Â return harness.exec(argc, argv) ? EXIT_FAILURE : 0;<br>
+}<br>
diff --git a/src/lc-compliance/meson.build b/src/lc-compliance/meson.build<br>
new file mode 100644<br>
index 0000000000000000..d700a307c71405b4<br>
--- /dev/null<br>
+++ b/src/lc-compliance/meson.build<br>
@@ -0,0 +1,24 @@<br>
+# SPDX-License-Identifier: CC0-1.0<br>
+<br>
+libevent = dependency('libevent_pthreads', required : false)<br>
+<br>
+if not libevent.found()<br>
+  Â  warning('libevent_pthreads not found, \'lc-compliance\' application will not be compiled')<br>
+  Â  subdir_done()<br>
+endif<br>
+<br>
+lc_compliance_sources = files([<br>
+  Â  '../cam/event_loop.cpp',<br>
+  Â  '../cam/options.cpp',<br>
+  Â  'main.cpp',<br>
+  Â  'results.cpp',<br>
+  Â  'single_stream.cpp',<br>
+])<br>
+<br>
+lc_compliance  = executable('lc-compliance', lc_compliance_sources,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  dependencies : [<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  libatomic,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  libcamera_dep,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  libevent,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  ],<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  install : true)<br>
diff --git a/src/lc-compliance/results.cpp b/src/lc-compliance/results.cpp<br>
new file mode 100644<br>
index 0000000000000000..fb4242bf49a3268b<br>
--- /dev/null<br>
+++ b/src/lc-compliance/results.cpp<br>
@@ -0,0 +1,75 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * results.cpp - Test result aggregator<br>
+ */<br>
+<br>
+#include "results.h"<br>
+<br>
+#include <iostream><br>
+<br>
+void Results::add(const Result &result)<br>
+{<br>
+  Â  Â  Â if (result.first == Pass)<br>
+  Â  Â  Â  Â  Â  Â  Â passed_++;<br>
+  Â  Â  Â else if (result.first == Fail)<br>
+  Â  Â  Â  Â  Â  Â  Â failed_++;<br>
+  Â  Â  Â else if (result.first == Skip)<br>
+  Â  Â  Â  Â  Â  Â  Â skipped_++;<br>
+<br>
+  Â  Â  Â printResult(result);<br>
+}<br>
+<br>
+void Results::add(Status status, const std::string &message)<br>
+{<br>
+  Â  Â  Â add({ status, message });<br>
+}<br>
+<br>
+void Results::fail(const std::string &message)<br>
+{<br>
+  Â  Â  Â add(Fail, message);<br>
+}<br>
+<br>
+void Results::pass(const std::string &message)<br>
+{<br>
+  Â  Â  Â add(Pass, message);<br>
+}<br>
+<br>
+void Results::skip(const std::string &message)<br>
+{<br>
+  Â  Â  Â add(Skip, message);<br>
+}<br>
+<br>
+int Results::summary() const<br>
+{<br>
+  Â  Â  Â if (failed_ + passed_ + skipped_ != planned_) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::cout << "Planned and executed numer of tests differ "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â << failed_ + passed_ + skipped_ << " executed "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â << planned_ << " planned" << std::endl;<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â return -EINVAL;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â std::cout << planned_ << " tests executed, "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â << passed_ << " tests passed, "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â << skipped_ << " tests skipped and "<br>
+  Â  Â  Â  Â  Â  Â  Â  Â << failed_ << " tests failed " << std::endl;<br>
+<br>
+  Â  Â  Â return 0;<br>
+}<br>
+<br>
+void Results::printResult(const Result &result)<br>
+{<br>
+  Â  Â  Â std::string prefix;<br>
+<br>
+  Â  Â  Â /* \todo Make parsable as TAP. */<br>
+  Â  Â  Â if (result.first == Pass)<br>
+  Â  Â  Â  Â  Â  Â  Â prefix = "PASS";<br>
+  Â  Â  Â else if (result.first == Fail)<br>
+  Â  Â  Â  Â  Â  Â  Â prefix = "FAIL";<br>
+  Â  Â  Â else if (result.first == Skip)<br>
+  Â  Â  Â  Â  Â  Â  Â prefix = "SKIP";<br>
+<br>
+  Â  Â  Â std::cout << "- " << prefix << " - " << result.second << std::endl;<br>
+}<br>
diff --git a/src/lc-compliance/results.h b/src/lc-compliance/results.h<br>
new file mode 100644<br>
index 0000000000000000..a02fd5ab46edd62c<br>
--- /dev/null<br>
+++ b/src/lc-compliance/results.h<br>
@@ -0,0 +1,45 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * results.h - Test result aggregator<br>
+ */<br>
+#ifndef __LC_COMPLIANCE_RESULTS_H__<br>
+#define __LC_COMPLIANCE_RESULTS_H__<br>
+<br>
+#include <string><br>
+<br>
+class Results<br>
+{<br>
+public:<br>
+  Â  Â  Â enum Status {<br>
+  Â  Â  Â  Â  Â  Â  Â Fail,<br>
+  Â  Â  Â  Â  Â  Â  Â Pass,<br>
+  Â  Â  Â  Â  Â  Â  Â Skip,<br>
+  Â  Â  Â };<br>
+<br>
+  Â  Â  Â using Result = std::pair<Status, std::string>;<br>
+<br>
+  Â  Â  Â Results(unsigned int planned)<br>
+  Â  Â  Â  Â  Â  Â  Â : planned_(planned), passed_(0), failed_(0), skipped_(0)<br>
+  Â  Â  Â {<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â void add(const Result &result);<br>
+  Â  Â  Â void add(Status status, const std::string &message);<br>
+  Â  Â  Â void fail(const std::string &message);<br>
+  Â  Â  Â void pass(const std::string &message);<br>
+  Â  Â  Â void skip(const std::string &message);<br>
+<br>
+  Â  Â  Â int summary() const;<br>
+<br>
+private:<br>
+  Â  Â  Â void printResult(const Result &result);<br>
+<br>
+  Â  Â  Â unsigned int planned_;<br>
+  Â  Â  Â unsigned int passed_;<br>
+  Â  Â  Â unsigned int failed_;<br>
+  Â  Â  Â unsigned int skipped_;<br>
+};<br>
+<br>
+#endif /* __LC_COMPLIANCE_RESULTS_H__ */<br>
diff --git a/src/lc-compliance/single_stream.cpp b/src/lc-compliance/single_stream.cpp<br>
new file mode 100644<br>
index 0000000000000000..d31b6f8d06487a85<br>
--- /dev/null<br>
+++ b/src/lc-compliance/single_stream.cpp<br>
@@ -0,0 +1,210 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * single_stream.cpp - Test a single camera stream<br>
+ */<br>
+<br>
+#include <iostream><br>
+<br>
+#include "../cam/event_loop.h"<br>
+#include "tests.h"<br>
+<br>
+using namespace libcamera;<br>
+<br>
+class SimpleCapture<br>
+{<br>
+public:<br>
+  Â  Â  Â SimpleCapture(std::shared_ptr<Camera> camera)<br>
+  Â  Â  Â  Â  Â  Â  Â : camera_(camera), allocator_(std::make_unique<FrameBufferAllocator>(camera))<br>
+  Â  Â  Â {<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â Results::Result configure(StreamRole role);<br>
+  Â  Â  Â Results::Result start();<br>
+  Â  Â  Â Results::Result capture(unsigned int numRequests);<br>
+  Â  Â  Â Results::Result stop();<br>
+<br>
+private:<br>
+  Â  Â  Â int queueRequest(Request *request);<br>
+  Â  Â  Â void requestComplete(Request *request);<br>
+<br>
+  Â  Â  Â std::shared_ptr<libcamera::Camera> camera_;<br>
+  Â  Â  Â std::unique_ptr<FrameBufferAllocator> allocator_;<br>
+  Â  Â  Â std::unique_ptr<libcamera::CameraConfiguration> config_;<br>
+<br>
+  Â  Â  Â EventLoop *loop_;<br>
+  Â  Â  Â unsigned int queueCount_;<br>
+  Â  Â  Â unsigned int captureCount_;<br>
+  Â  Â  Â unsigned int captureLimit_;<br>
+};<br>
+<br>
+Results::Result SimpleCapture::configure(StreamRole role)<br>
+{<br>
+  Â  Â  Â config_ = camera_->generateConfiguration({ role });<br>
+<br>
+  Â  Â  Â if (config_->validate() != CameraConfiguration::Valid) {<br>
+  Â  Â  Â  Â  Â  Â  Â config_.reset();<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Configuration not valid" };<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â if (camera_->configure(config_.get())) {<br>
+  Â  Â  Â  Â  Â  Â  Â config_.reset();<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Failed to configure camera" };<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â return { Results::Pass, "Configure camera" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::start()<br>
+{<br>
+  Â  Â  Â Stream *stream = config_->at(0).stream();<br>
+  Â  Â  Â if (allocator_->allocate(stream) < 0)<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Failed to allocate buffers camera" };<br>
+<br>
+  Â  Â  Â if (camera_->start())<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Failed to start camera" };<br>
+<br>
+  Â  Â  Â camera_->requestCompleted.connect(this, &SimpleCapture::requestComplete);<br>
+<br>
+  Â  Â  Â return { Results::Pass, "Started camera" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::capture(unsigned int numRequests)<br>
+{<br>
+  Â  Â  Â Stream *stream = config_->at(0).stream();<br>
+  Â  Â  Â const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);<br>
+<br>
+  Â  Â  Â /* No point in testing less requests then the camera depth. */<br>
+  Â  Â  Â if (buffers.size() > numRequests)<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Skip, "Camera needs " + std::to_string(buffers.size()) + " requests, can't test only " + std::to_string(numRequests) };<br>
+<br>
+  Â  Â  Â queueCount_ = 0;<br>
+  Â  Â  Â captureCount_ = 0;<br>
+  Â  Â  Â captureLimit_ = numRequests;<br>
+<br>
+  Â  Â  Â /* Queue the camera recommended number of reqeuests. */<br>
+  Â  Â  Â std::vector<std::unique_ptr<libcamera::Request>> requests;<br>
+  Â  Â  Â for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {<br>
+  Â  Â  Â  Â  Â  Â  Â std::unique_ptr<Request> request = camera_->createRequest();<br>
+  Â  Â  Â  Â  Â  Â  Â if (!request)<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Can't create request" };<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â if (request->addBuffer(stream, buffer.get()))<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Can't set buffer for request" };<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â if (queueRequest(request.get()) < 0)<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Failed to queue request" };<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â requests.push_back(std::move(request));<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â /* Run capture session. */<br>
+  Â  Â  Â loop_ = new EventLoop();<br>
+  Â  Â  Â loop_->exec();<br>
+  Â  Â  Â delete loop_;<br>
+<br>
+  Â  Â  Â if (captureCount_ != captureLimit_)<br>
+  Â  Â  Â  Â  Â  Â  Â return { Results::Fail, "Got " + std::to_string(captureCount_) + " request, wanted " + std::to_string(captureLimit_) };<br>
+<br>
+  Â  Â  Â return { Results::Pass, "Balanced capture of " + std::to_string(numRequests) + " requests" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::stop()<br>
+{<br>
+  Â  Â  Â Stream *stream = config_->at(0).stream();<br>
+<br>
+  Â  Â  Â camera_->stop();<br>
+<br>
+  Â  Â  Â camera_->requestCompleted.disconnect(this, &SimpleCapture::requestComplete);<br>
+<br>
+  Â  Â  Â allocator_->free(stream);<br>
+<br>
+  Â  Â  Â return { Results::Pass, "Stopped camera" };<br>
+}<br>
+<br>
+int SimpleCapture::queueRequest(Request *request)<br>
+{<br>
+  Â  Â  Â queueCount_++;<br>
+  Â  Â  Â if (queueCount_ > captureLimit_)<br>
+  Â  Â  Â  Â  Â  Â  Â return 0;<br>
+<br>
+  Â  Â  Â return camera_->queueRequest(request);<br>
+}<br>
+<br>
+void SimpleCapture::requestComplete(Request *request)<br>
+{<br>
+  Â  Â  Â captureCount_++;<br>
+  Â  Â  Â if (captureCount_ >= captureLimit_) {<br>
+  Â  Â  Â  Â  Â  Â  Â loop_->exit(0);<br>
+  Â  Â  Â  Â  Â  Â  Â return;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â request->reuse(Request::ReuseBuffers);<br>
+  Â  Â  Â if (queueRequest(request))<br>
+  Â  Â  Â  Â  Â  Â  Â loop_->exit(-EINVAL);<br>
+}<br>
+<br>
+Results::Result testRequestBalance(std::shared_ptr<Camera> camera,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  unsigned int startCycles,<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  unsigned int numRequests)<br>
+{<br>
+  Â  Â  Â SimpleCapture capture(camera);<br>
+  Â  Â  Â Results::Result ret;<br>
+<br>
+  Â  Â  Â ret = capture.configure(Viewfinder);<br>
+  Â  Â  Â if (ret.first != Results::Pass)<br>
+  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+<br>
+  Â  Â  Â for (unsigned int starts = 0; starts < startCycles; starts++) {<br>
+  Â  Â  Â  Â  Â  Â  Â ret = capture.start();<br>
+  Â  Â  Â  Â  Â  Â  Â if (ret.first != Results::Pass)<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â ret = capture.capture(numRequests);<br>
+  Â  Â  Â  Â  Â  Â  Â if (ret.first != Results::Pass) {<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â capture.stop();<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+  Â  Â  Â  Â  Â  Â  Â }<br>
+<br>
+  Â  Â  Â  Â  Â  Â  Â ret = capture.stop();<br>
+  Â  Â  Â  Â  Â  Â  Â if (ret.first != Results::Pass)<br>
+  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â return ret;<br>
+  Â  Â  Â }<br>
+<br>
+  Â  Â  Â return { Results::Pass, "Balanced capture of " + std::to_string(numRequests) + " requests with " + std::to_string(startCycles) + " start cycles" };<br>
+}<br>
+<br>
+Results testSingleStream(std::shared_ptr<Camera> camera)<br>
+{<br>
+  Â  Â  Â const std::vector<unsigned int> numRequests = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };<br>
+<br>
+  Â  Â  Â Results results(numRequests.size() * 2);<br>
+<br>
+  Â  Â  Â if (!camera)<br>
+  Â  Â  Â  Â  Â  Â  Â return results;<br>
+<br>
+  Â  Â  Â /*<br>
+  Â  Â  Â  * Test single capture cycles<br>
+  Â  Â  Â  *<br>
+  Â  Â  Â  * Makes sure the camera completes the exact number of requests queued.<br>
+  Â  Â  Â  * Example failure is a camera that needs N+M requests queued to<br>
+  Â  Â  Â  * complete N requests to the application.<br>
+  Â  Â  Â  */<br>
+  Â  Â  Â std::cout << "Test single capture cycles" << std::endl;<br>
+  Â  Â  Â for (unsigned int num : numRequests)<br>
+  Â  Â  Â  Â  Â  Â  Â results.add(testRequestBalance(camera, 1, num));<br>
+<br>
+  Â  Â  Â /*<br>
+  Â  Â  Â  * Test multiple start/stop cycles<br>
+  Â  Â  Â  *<br>
+  Â  Â  Â  * Makes sure the camera supports multiple start/stop cycles.<br>
+  Â  Â  Â  * Example failure is a camera that does not clean up correctly in its<br>
+  Â  Â  Â  * error path but is only tested by single-capture applications.<br>
+  Â  Â  Â  */<br>
+  Â  Â  Â std::cout << "Test multiple start/stop cycles" << std::endl;<br>
+  Â  Â  Â for (unsigned int num : numRequests)<br>
+  Â  Â  Â  Â  Â  Â  Â results.add(testRequestBalance(camera, 3, num));<br>
+<br>
+  Â  Â  Â return results;<br>
+}<br>
diff --git a/src/lc-compliance/tests.h b/src/lc-compliance/tests.h<br>
new file mode 100644<br>
index 0000000000000000..396605214e4b8980<br>
--- /dev/null<br>
+++ b/src/lc-compliance/tests.h<br>
@@ -0,0 +1,16 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * tests.h - Test modules<br>
+ */<br>
+#ifndef __LC_COMPLIANCE_TESTS_H__<br>
+#define __LC_COMPLIANCE_TESTS_H__<br>
+<br>
+#include <libcamera/libcamera.h><br>
+<br>
+#include "results.h"<br>
+<br>
+Results testSingleStream(std::shared_ptr<libcamera::Camera> camera);<br>
+<br>
+#endif /* __LC_COMPLIANCE_TESTS_H__ */<br>
diff --git a/src/meson.build b/src/meson.build<br>
index 4b75f05878bcb702..a8e1af7adf2ca9c8 100644<br>
--- a/src/meson.build<br>
+++ b/src/meson.build<br>
@@ -18,6 +18,8 @@ subdir('android')<br>
 subdir('libcamera')<br>
 subdir('ipa')<br>
<br>
+subdir('lc-compliance')<br>
+<br>
 subdir('cam')<br>
 subdir('qcam')<br>
<br>
-- <br>
2.30.0<br>
<br>
_______________________________________________<br>
libcamera-devel mailing list<br>
<a href="mailto:libcamera-devel@lists.libcamera.org" target="_blank">libcamera-devel@lists.libcamera.org</a><br>
<a href="https://lists.libcamera.org/listinfo/libcamera-devel" rel="noreferrer" target="_blank">https://lists.libcamera.org/listinfo/libcamera-devel</a><br>
</blockquote></div></div>