Message ID | 20201215004811.602429-3-niklas.soderlund@ragnatech.se |
---|---|
State | Superseded |
Delegated to: | Niklas Söderlund |
Headers | show |
Series |
|
Related | show |
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@ragnatech.se> > Reviewed-by: Jacopo Mondi <jacopo@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 ? > + 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. > + > + /* 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. > + > + 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. > + 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. > + } > + > + 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'],
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@ragnatech.se> > > Reviewed-by: Jacopo Mondi <jacopo@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
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()); + if (dev_->open()) { + cerr << "Failed to open video device" << endl; + return TestFail; + } + + const ControlInfoMap &infoMap = dev_->controls(); + + /* Test control enumeration. */ + 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++) { + 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>(); + 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(); + + /* 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; + } + + 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); + 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; + } + + 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. */ + 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'],