[libcamera-devel] [PATCH v4 2/8] test: delayed_controls: Add test case for DelayedControls
Niklas Söderlund
niklas.soderlund at ragnatech.se
Sat Jan 16 12:42:18 CET 2021
Hi Laurent,
Thanks for your comments,
On 2021-01-10 17:21:08 +0200, Laurent Pinchart wrote:
> Hi Niklas,
>
> Thank you for the patch.
>
> On Tue, Dec 15, 2020 at 01:48:05AM +0100, Niklas Söderlund wrote:
> > Add a test-case for DelayedControls that exercise the setting of
>
> s/test-case/test case/
> s/exercise/exercises/
>
> > controls and reading back what controls were used for a particular
> > frame. Also exercise corner case such as a V4L2 devices that do not
>
> s/case/cases/
> s/devices/device/
> s/do/does/
>
> > reset its sequence number to 0 at stream on and sequence number wrapping
> > around the uint32_t value space.
> >
> > Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> > Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
> > ---
> > * Changes since v3
> > - Update commit message.
> > - Update header documentation.
> > - Add protected and override.
> > - Use unsigned int instead of int32_t in loops.
> >
> > * Changes since v2
> > - Remove a unused LoC.
> > - Add and remove blank lines.
> > - Align number of loop iterations.
> > ---
> > test/delayed_contols.cpp | 304 +++++++++++++++++++++++++++++++++++++++
> > test/meson.build | 1 +
> > 2 files changed, 305 insertions(+)
> > create mode 100644 test/delayed_contols.cpp
> >
> > diff --git a/test/delayed_contols.cpp b/test/delayed_contols.cpp
> > new file mode 100644
> > index 0000000000000000..0a2b47aebf06b7fb
> > --- /dev/null
> > +++ b/test/delayed_contols.cpp
> > @@ -0,0 +1,304 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * delayed_controls.cpp - libcamera delayed controls test
> > + */
> > +
> > +#include <iostream>
> > +
> > +#include "libcamera/internal/delayed_controls.h"
> > +#include "libcamera/internal/device_enumerator.h"
> > +#include "libcamera/internal/media_device.h"
> > +#include "libcamera/internal/v4l2_videodevice.h"
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +using namespace libcamera;
> > +
> > +class DelayedControlsTest : public Test
> > +{
> > +public:
> > + DelayedControlsTest()
> > + : dev_(nullptr)
> > + {
> > + }
> > +
> > +protected:
> > + int init() override
> > + {
> > + enumerator_ = DeviceEnumerator::create();
> > + if (!enumerator_) {
> > + cerr << "Failed to create device enumerator" << endl;
> > + return TestFail;
> > + }
> > +
> > + if (enumerator_->enumerate()) {
> > + cerr << "Failed to enumerate media devices" << endl;
> > + return TestFail;
> > + }
> > +
> > + DeviceMatch dm("vivid");
> > + dm.add("vivid-000-vid-cap");
> > +
> > + media_ = enumerator_->search(dm);
> > + if (!media_) {
> > + cerr << "vivid video device found" << endl;
> > + return TestSkip;
> > + }
> > +
> > + MediaEntity *entity = media_->getEntityByName("vivid-000-vid-cap");
> > + dev_ = new V4L2VideoDevice(entity->deviceNode());
>
> You can use V4L2VideoDevice::fromEntityName() and turn dev_ into a
> std::unique_ptr<>.
>
> > + if (dev_->open()) {
> > + cerr << "Failed to open video device" << endl;
> > + return TestFail;
> > + }
> > +
> > + const ControlInfoMap &infoMap = dev_->controls();
> > +
> > + /* Test control enumeration. */
>
> I'd write
>
> /* Make sure the controls we require are present. */
>
> as this isn't a test.
>
> > + if (infoMap.empty()) {
> > + cerr << "Failed to enumerate controls" << endl;
> > + return TestFail;
> > + }
> > +
> > + if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
> > + infoMap.find(V4L2_CID_CONTRAST) == infoMap.end()) {
> > + cerr << "Missing controls" << endl;
> > + return TestFail;
> > + }
> > +
> > + return TestPass;
> > + }
> > +
> > + int singleControlNoDelay()
> > + {
> > + std::unordered_map<uint32_t, unsigned int> delays = {
> > + { V4L2_CID_BRIGHTNESS, 0 },
> > + };
> > + std::unique_ptr<DelayedControls> delayed =
> > + std::make_unique<DelayedControls>(dev_, delays);
> > + ControlList ctrls;
> > +
> > + /* Reset control to value not used in test. */
> > + ctrls.set(V4L2_CID_BRIGHTNESS, 1);
> > + dev_->setControls(&ctrls);
> > +
> > + /* Test control without delay are set at once. */
> > + for (int32_t i = 0; i < 100; i++) {
>
> unsigned int i would do. Same below.
>
> > + int32_t value = 100 + i;
> > +
> > + ctrls.set(V4L2_CID_BRIGHTNESS, value);
> > + delayed->push(ctrls);
> > +
> > + delayed->applyControls(i);
> > +
> > + ControlList result = delayed->get(i);
> > + int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
>
> Should we also read controls back from dev_ to test that the correct
> values is applied ?
We could, but is that not a test for V4L2VideoDevice?
>
> > + if (brightness != value) {
> > + cerr << "Failed single control without delay"
> > + << " frame " << i
> > + << " expected " << value
> > + << " got " << brightness
> > + << endl;
> > + return TestFail;
> > + }
> > + }
> > +
> > + return TestPass;
> > + }
> > +
> > + int singleControlWithDelay()
> > + {
> > + std::unordered_map<uint32_t, unsigned int> delays = {
> > + { V4L2_CID_BRIGHTNESS, 1 },
> > + };
> > + std::unique_ptr<DelayedControls> delayed =
> > + std::make_unique<DelayedControls>(dev_, delays);
> > + ControlList ctrls;
> > +
> > + /* Reset control to value that will be first in test. */
> > + int32_t expected = 4;
> > + ctrls.set(V4L2_CID_BRIGHTNESS, expected);
> > + dev_->setControls(&ctrls);
> > + delayed->reset();
>
> If you set the controls on the device before creating the
> DelayedControls instance, you could save the reset() call. Same for the
> other test cases.
True, but I want to test reset() here as I suspect the usage pattern in
pipeline will be to create the DelayedControls at init time but reset()
the controls at start() time.
>
> > +
> > + /* Test single control with delay. */
> > + for (int32_t i = 0; i < 100; i++) {
> > + int32_t value = 10 + i;
> > +
> > + ctrls.set(V4L2_CID_BRIGHTNESS, value);
> > + delayed->push(ctrls);
> > +
> > + delayed->applyControls(i);
> > +
> > + ControlList result = delayed->get(i);
> > + int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
> > + if (brightness != expected) {
> > + cerr << "Failed single control with delay"
> > + << " frame " << i
> > + << " expected " << expected
> > + << " got " << brightness
> > + << endl;
> > + return TestFail;
> > + }
> > +
> > + expected = value;
> > + }
> > +
> > + return TestPass;
> > + }
>
> I wonder if these two functions could be merged, with the delay passed
> as a parameter.
I'm sure they could but one test 1 parameter and the other 2. So the
code IMHO would be hard to read if we add 'if (dual) ...;' checks all
over the place.
>
> > +
> > + int dualControlsWithDelay(uint32_t startOffset)
> > + {
> > + std::unordered_map<uint32_t, unsigned int> delays = {
> > + { V4L2_CID_BRIGHTNESS, 1 },
> > + { V4L2_CID_CONTRAST, 2 },
> > + };
> > + std::unique_ptr<DelayedControls> delayed =
> > + std::make_unique<DelayedControls>(dev_, delays);
> > + ControlList ctrls;
> > +
> > + /* Reset control to value that will be first two frames in test. */
> > + int32_t expected = 200;
> > + ctrls.set(V4L2_CID_BRIGHTNESS, expected);
> > + ctrls.set(V4L2_CID_CONTRAST, expected);
>
> It could be worth setting different values for the two controls (and
> below too) to test the right value is applied to the right control.
Good idea, will do so for v5.
>
> > + dev_->setControls(&ctrls);
> > + delayed->reset();
> > +
> > + /* Test dual control with delay. */
> > + for (int32_t i = 0; i < 100; i++) {
> > + uint32_t frame = startOffset + i;
> > + int32_t value = 10 + i;
> > +
> > + ctrls.set(V4L2_CID_BRIGHTNESS, value);
> > + ctrls.set(V4L2_CID_CONTRAST, value);
> > + delayed->push(ctrls);
> > +
> > + delayed->applyControls(frame);
> > +
> > + ControlList result = delayed->get(frame);
> > + int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
> > + int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
> > + if (brightness != expected || contrast != expected) {
> > + cerr << "Failed dual controls"
> > + << " frame " << frame
> > + << " brightness " << brightness
> > + << " contrast " << contrast
> > + << " expected " << expected
> > + << endl;
> > + return TestFail;
> > + }
> > +
> > + expected = i < 1 ? expected : value - 1;
>
> This puzzles me. get() is documented as returning the controls in effect
> at a sequence number. I'd expect this to work for the brightness control
> as the corresponding delay is 1, but not for the contrast control as the
> delay is 2.
This needs to documented better, Delayed controls return garbage data
until _pipeline depth_ frames have been completed. In the RPI pipeline
handler this is handled by dropping the N first frames. I'm sure we can
improve here but as stated in previous patches the goal of this series
is to profligate the StaggerdCtls design as-is as it improves the design
in other ares. I'm sure we will rework this in the future.
>
> > + }
> > +
> > + return TestPass;
> > + }
> > +
> > + int dualControlsMultiQueue()
> > + {
> > + std::unordered_map<uint32_t, unsigned int> delays = {
> > + { V4L2_CID_BRIGHTNESS, 1 },
> > + { V4L2_CID_CONTRAST, 2 },
> > + };
> > + std::unique_ptr<DelayedControls> delayed =
> > + std::make_unique<DelayedControls>(dev_, delays);
> > + ControlList ctrls;
> > +
> > + /* Reset control to value that will be first two frames in test. */
> > + int32_t expected = 100;
> > + ctrls.set(V4L2_CID_BRIGHTNESS, expected);
> > + ctrls.set(V4L2_CID_CONTRAST, expected);
> > + dev_->setControls(&ctrls);
> > + delayed->reset();
> > +
> > + /*
> > + * Queue all controls before any fake frame start. Note we
> > + * can't queue up more then the delayed controls history size
> > + * which is 16. Where one spot is used by the reset control.
> > + */
> > + for (unsigned int i = 0; i < 15; i++) {
> > + int32_t value = 10 + i;
> > +
> > + ctrls.set(V4L2_CID_BRIGHTNESS, value);
> > + ctrls.set(V4L2_CID_CONTRAST, value);
> > + delayed->push(ctrls);
> > + }
> > +
> > + /* Process all queued controls. */
> > + for (unsigned int i = 0; i < 16; i++) {
> > + int32_t value = 10 + i;
> > +
> > + delayed->applyControls(i);
> > +
> > + ControlList result = delayed->get(i);
> > +
> > + int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
> > + int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
> > + if (brightness != expected || contrast != expected) {
> > + cerr << "Failed multi queue"
> > + << " frame " << i
> > + << " brightness " << brightness
> > + << " contrast " << contrast
> > + << " expected " << expected
> > + << endl;
> > + return TestFail;
> > + }
> > +
> > + expected = i < 1 ? expected : value - 1;
> > + }
> > +
> > + return TestPass;
> > + }
> > +
> > + int run() override
> > + {
> > + int ret;
> > +
> > + /* Test single control without delay. */
> > + ret = singleControlNoDelay();
> > + if (ret)
> > + return ret;
> > +
> > + /* Test single control with delay. */
> > + ret = singleControlWithDelay();
> > + if (ret)
> > + return ret;
> > +
> > + /* Test dual controls with different delays. */
> > + ret = dualControlsWithDelay(0);
> > + if (ret)
> > + return ret;
> > +
> > + /* Test dual controls with non-zero sequence start. */
> > + ret = dualControlsWithDelay(10000);
> > + if (ret)
> > + return ret;
> > +
> > + /* Test dual controls with sequence number wraparound. */
> > + ret = dualControlsWithDelay(UINT32_MAX - 50);
> > + if (ret)
> > + return ret;
> > +
> > + /* Test control values produced faster then consumed. */
>
> s/then/than/
>
> > + ret = dualControlsMultiQueue();
> > + if (ret)
> > + return ret;
> > +
> > + return TestPass;
> > + }
> > +
> > + void cleanup() override
> > + {
> > + delete dev_;
> > + }
> > +
> > +private:
> > + std::unique_ptr<DeviceEnumerator> enumerator_;
> > + std::shared_ptr<MediaDevice> media_;
> > + V4L2VideoDevice *dev_;
> > +};
> > +
> > +TEST_REGISTER(DelayedControlsTest)
> > diff --git a/test/meson.build b/test/meson.build
> > index 0a1d434e399641bb..a683a657a439b4ff 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -25,6 +25,7 @@ public_tests = [
> > internal_tests = [
> > ['byte-stream-buffer', 'byte-stream-buffer.cpp'],
> > ['camera-sensor', 'camera-sensor.cpp'],
> > + ['delayed_contols', 'delayed_contols.cpp'],
> > ['event', 'event.cpp'],
> > ['event-dispatcher', 'event-dispatcher.cpp'],
> > ['event-thread', 'event-thread.cpp'],
>
> --
> Regards,
>
> Laurent Pinchart
--
Regards,
Niklas Söderlund
More information about the libcamera-devel
mailing list