[libcamera-devel,v4,2/8] test: delayed_controls: Add test case for DelayedControls
diff mbox series

Message ID 20201215004811.602429-3-niklas.soderlund@ragnatech.se
State Superseded
Delegated to: Niklas Söderlund
Headers show
Series
  • libcamera: Add helper for controls that take effect with a delay
Related show

Commit Message

Niklas Söderlund Dec. 15, 2020, 12:48 a.m. UTC
Add a test-case for DelayedControls that exercise the setting of
controls and reading back what controls were used for a particular
frame. Also exercise corner case such as a V4L2 devices that do not
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

Comments

Laurent Pinchart Jan. 10, 2021, 3:21 p.m. UTC | #1
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'],
Niklas Söderlund Jan. 16, 2021, 11:42 a.m. UTC | #2
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

Patch
diff mbox series

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'],