summaryrefslogtreecommitdiff
path: root/test/v4l2_videodevice/controls.cpp
blob: 0f603b85930d1e8f067b60eb1d84307c5b2c31f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * controls.cpp - V4L2 device controls handling test
 */

#include <algorithm>
#include <array>
#include <iostream>
#include <limits.h>

#include "libcamera/internal/v4l2_videodevice.h"

#include "v4l2_videodevice_test.h"

/* These come from the vivid driver. */
#define VIVID_CID_CUSTOM_BASE		(V4L2_CID_USER_BASE | 0xf000)
#define VIVID_CID_INTEGER64		(VIVID_CID_CUSTOM_BASE + 3)
#define VIVID_CID_U8_4D_ARRAY		(VIVID_CID_CUSTOM_BASE + 10)

/* Helper for VIVID_CID_U8_4D_ARRAY control array size: not from kernel. */
#define VIVID_CID_U8_ARRAY_SIZE		(2 * 3 * 4 * 5)

using namespace std;
using namespace libcamera;

class V4L2ControlTest : public V4L2VideoDeviceTest
{
public:
	V4L2ControlTest()
		: V4L2VideoDeviceTest("vivid", "vivid-000-vid-cap")
	{
	}

protected:
	int run()
	{
		const ControlInfoMap &infoMap = capture_->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() ||
		    infoMap.find(V4L2_CID_SATURATION) == infoMap.end() ||
		    infoMap.find(VIVID_CID_INTEGER64) == infoMap.end() ||
		    infoMap.find(VIVID_CID_U8_4D_ARRAY) == infoMap.end()) {
			cerr << "Missing controls" << endl;
			return TestFail;
		}

		const ControlInfo &brightness = infoMap.find(V4L2_CID_BRIGHTNESS)->second;
		const ControlInfo &contrast = infoMap.find(V4L2_CID_CONTRAST)->second;
		const ControlInfo &saturation = infoMap.find(V4L2_CID_SATURATION)->second;
		const ControlInfo &int64 = infoMap.find(VIVID_CID_INTEGER64)->second;
		const ControlInfo &u8 = infoMap.find(VIVID_CID_U8_4D_ARRAY)->second;

		/* Test getting controls. */
		ControlList ctrls = capture_->getControls({ V4L2_CID_BRIGHTNESS,
							    V4L2_CID_CONTRAST,
							    V4L2_CID_SATURATION,
							    VIVID_CID_INTEGER64,
							    VIVID_CID_U8_4D_ARRAY });
		if (ctrls.empty()) {
			cerr << "Failed to get controls" << endl;
			return TestFail;
		}

		if (ctrls.infoMap() != &infoMap) {
			cerr << "Incorrect infoMap for retrieved controls" << endl;
			return TestFail;
		}

		if (ctrls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() == -1 ||
		    ctrls.get(V4L2_CID_CONTRAST).get<int32_t>() == -1 ||
		    ctrls.get(V4L2_CID_SATURATION).get<int32_t>() == -1) {
			cerr << "Incorrect value for retrieved controls" << endl;
			return TestFail;
		}

		/*
		 * The VIVID_CID_INTEGER64 control can take any value, just test
		 * that its value can be retrieved and has the right type.
		 */
		ctrls.get(VIVID_CID_INTEGER64).get<int64_t>();

		uint8_t u8Min = u8.min().get<uint8_t>();
		uint8_t u8Max = u8.max().get<uint8_t>();

		Span<const uint8_t> u8Span = ctrls.get(VIVID_CID_U8_4D_ARRAY).get<Span<const uint8_t>>();
		bool valid = std::all_of(u8Span.begin(), u8Span.end(),
					 [&](uint8_t v) { return v >= u8Min && v <= u8Max; });
		if (!valid) {
			cerr << "Incorrect value for retrieved array control"
			     << endl;
			return TestFail;
		}

		/* Test setting controls. */
		ctrls.set(V4L2_CID_BRIGHTNESS, brightness.min());
		ctrls.set(V4L2_CID_CONTRAST, contrast.max());
		ctrls.set(V4L2_CID_SATURATION, saturation.min());
		ctrls.set(VIVID_CID_INTEGER64, int64.min());

		std::array<uint8_t, VIVID_CID_U8_ARRAY_SIZE> u8Values;
		std::fill(u8Values.begin(), u8Values.end(), u8.min().get<uint8_t>());
		ctrls.set(VIVID_CID_U8_4D_ARRAY, Span<const uint8_t>(u8Values));

		int ret = capture_->setControls(&ctrls);
		if (ret) {
			cerr << "Failed to set controls" << endl;
			return TestFail;
		}

		/* Test setting controls outside of range. */
		ctrls.set(V4L2_CID_BRIGHTNESS, brightness.min().get<int32_t>() - 1);
		ctrls.set(V4L2_CID_CONTRAST, contrast.max().get<int32_t>() + 1);
		ctrls.set(V4L2_CID_SATURATION, saturation.min().get<int32_t>() + 1);

		ret = capture_->setControls(&ctrls);
		if (ret) {
			cerr << "Failed to set controls (out of range)" << endl;
			return TestFail;
		}

		if (ctrls.get(V4L2_CID_BRIGHTNESS) != brightness.min() ||
		    ctrls.get(V4L2_CID_CONTRAST) != contrast.max() ||
		    ctrls.get(V4L2_CID_SATURATION) != saturation.min().get<int32_t>() + 1) {
			cerr << "Controls not updated when set" << endl;
			return TestFail;
		}

		return TestPass;
	}
};

TEST_REGISTER(V4L2ControlTest)
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2022, Google Inc.
 *
 * libcamera YAML parsing helper
 */

#include "libcamera/internal/yaml_parser.h"

#include <cstdlib>
#include <errno.h>
#include <functional>
#include <limits>

#include <libcamera/base/file.h>
#include <libcamera/base/log.h>

#include <yaml.h>

/**
 * \file libcamera/internal/yaml_parser.h
 * \brief A YAML parser helper
 */

namespace libcamera {

LOG_DEFINE_CATEGORY(YamlParser)

namespace {

/* Empty static YamlObject as a safe result for invalid operations */
static const YamlObject empty;

} /* namespace */

/**
 * \class YamlObject
 * \brief A class representing the tree structure of the YAML content
 *
 * The YamlObject class represents the tree structure of YAML content. A
 * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
 * leaf.
 */

YamlObject::YamlObject()
	: type_(Type::Value)
{
}

YamlObject::~YamlObject() = default;

/**
 * \fn YamlObject::isValue()
 * \brief Return whether the YamlObject is a value
 *
 * \return True if the YamlObject is a value, false otherwise
 */

/**
 * \fn YamlObject::isList()
 * \brief Return whether the YamlObject is a list
 *
 * \return True if the YamlObject is a list, false otherwise
 */

/**
 * \fn YamlObject::isDictionary()
 * \brief Return whether the YamlObject is a dictionary
 *
 * \return True if the YamlObject is a dictionary, false otherwise
 */

/**
 * \fn YamlObject::size()
 * \brief Retrieve the number of elements in a dictionary or list YamlObject
 *
 * This function retrieves the size of the YamlObject, defined as the number of
 * child elements it contains. Only YamlObject instances of Dictionary or List
 * types have a size, calling this function on other types of instances is
 * invalid and results in undefined behaviour.
 *
 * \return The size of the YamlObject
 */
std::size_t YamlObject::size() const
{
	switch (type_) {
	case Type::Dictionary:
	case Type::List:
		return list_.size();
	default:
		return 0;
	}
}

/**
 * \fn template<typename T> YamlObject::get<T>() const
 * \brief Parse the YamlObject as a \a T value
 *
 * This function parses the value of the YamlObject as a \a T object, and
 * returns the value. If parsing fails (usually because the YamlObject doesn't
 * store a \a T value), std::nullopt is returned.
 *
 * \return The YamlObject value, or std::nullopt if parsing failed
 */

/**
 * \fn template<typename T, typename U> YamlObject::get<T>(U &&defaultValue) const
 * \brief Parse the YamlObject as a \a T value
 * \param[in] defaultValue The default value when failing to parse
 *
 * This function parses the value of the YamlObject as a \a T object, and
 * returns the value. If parsing fails (usually because the YamlObject doesn't
 * store a \a T value), the \a defaultValue is returned.
 *
 * \return The YamlObject value, or \a defaultValue if parsing failed
 */

#ifndef __DOXYGEN__

template<>
std::optional<bool>
YamlObject::Getter<bool>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	if (obj.value_ == "true")
		return true;
	else if (obj.value_ == "false")
		return false;

	return std::nullopt;
}

namespace {

bool parseSignedInteger(const std::string &str, long min, long max,
			long *result)
{
	if (str == "")
		return false;

	char *end;

	errno = 0;
	long value = std::strtol(str.c_str(), &end, 10);

	if ('\0' != *end || errno == ERANGE || value < min || value > max)
		return false;

	*result = value;
	return true;
}

bool parseUnsignedInteger(const std::string &str, unsigned long max,
			  unsigned long *result)
{
	if (str == "")
		return false;

	/*
	 * strtoul() accepts strings representing a negative number, in which
	 * case it negates the converted value. We don't want to silently accept
	 * negative values and return a large positive number, so check for a
	 * minus sign (after optional whitespace) and return an error.
	 */
	std::size_t found = str.find_first_not_of(" \t");
	if (found != std::string::npos && str[found] == '-')
		return false;

	char *end;

	errno = 0;
	unsigned long value = std::strtoul(str.c_str(), &end, 10);

	if ('\0' != *end || errno == ERANGE || value > max)
		return false;

	*result = value;
	return true;
}

} /* namespace */

template<>
std::optional<int8_t>
YamlObject::Getter<int8_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	long value;

	if (!parseSignedInteger(obj.value_, std::numeric_limits<int8_t>::min(),
				std::numeric_limits<int8_t>::max(), &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<uint8_t>
YamlObject::Getter<uint8_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	unsigned long value;

	if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint8_t>::max(),
				  &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<int16_t>
YamlObject::Getter<int16_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	long value;

	if (!parseSignedInteger(obj.value_, std::numeric_limits<int16_t>::min(),
				std::numeric_limits<int16_t>::max(), &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<uint16_t>
YamlObject::Getter<uint16_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	unsigned long value;

	if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint16_t>::max(),
				  &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<int32_t>
YamlObject::Getter<int32_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	long value;

	if (!parseSignedInteger(obj.value_, std::numeric_limits<int32_t>::min(),
				std::numeric_limits<int32_t>::max(), &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<uint32_t>
YamlObject::Getter<uint32_t>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	unsigned long value;

	if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint32_t>::max(),
				  &value))
		return std::nullopt;

	return value;
}

template<>
std::optional<float>
YamlObject::Getter<float>::get(const YamlObject &obj) const
{
	return obj.get<double>();
}

template<>
std::optional<double>
YamlObject::Getter<double>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	if (obj.value_ == "")
		return std::nullopt;

	char *end;

	errno = 0;
	double value = utils::strtod(obj.value_.c_str(), &end);

	if ('\0' != *end || errno == ERANGE)
		return std::nullopt;

	return value;
}

template<>
std::optional<std::string>
YamlObject::Getter<std::string>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::Value)
		return std::nullopt;

	return obj.value_;
}

template<>
std::optional<Size>
YamlObject::Getter<Size>::get(const YamlObject &obj) const
{
	if (obj.type_ != Type::List)
		return std::nullopt;

	if (obj.list_.size() != 2)
		return std::nullopt;

	auto width = obj.list_[0].value->get<uint32_t>();
	if (!width)
		return std::nullopt;

	auto height = obj.list_[1].value->get<uint32_t>();
	if (!height)
		return std::nullopt;

	return Size(*width, *height);
}

#endif /* __DOXYGEN__ */

/**
 * \fn template<typename T> YamlObject::getList<T>() const
 * \brief Parse the YamlObject as a list of \a T
 *
 * This function parses the value of the YamlObject as a list of \a T objects,
 * and returns the value as a \a std::vector<T>. If parsing fails, std::nullopt
 * is returned.
 *
 * \return The YamlObject value as a std::vector<T>, or std::nullopt if parsing
 * failed
 */

#ifndef __DOXYGEN__

template<typename T,
	 std::enable_if_t<
		 std::is_same_v<bool, T> ||
		 std::is_same_v<float, T> ||
		 std::is_same_v<double, T> ||
		 std::is_same_v<int8_t, T> ||
		 std::is_same_v<uint8_t, T> ||
		 std::is_same_v<int16_t, T> ||
		 std::is_same_v<uint16_t, T> ||
		 std::is_same_v<int32_t, T> ||
		 std::is_same_v<uint32_t, T> ||
		 std::is_same_v<std::string, T> ||
		 std::is_same_v<Size, T>> *>
std::optional<std::vector<T>> YamlObject::getList() const
{
	if (type_ != Type::List)
		return std::nullopt;

	std::vector<T> values;
	values.reserve(list_.size());

	for (const YamlObject &entry : asList()) {
		const auto value = entry.get<T>();
		if (!value)
			return std::nullopt;
		values.emplace_back(*value);
	}

	return values;
}

template std::optional<std::vector<bool>> YamlObject::getList<bool>() const;
template std::optional<std::vector<float>> YamlObject::getList<float>() const;
template std::optional<std::vector<double>> YamlObject::getList<double>() const;
template std::optional<std::vector<int8_t>> YamlObject::getList<int8_t>() const;
template std::optional<std::vector<uint8_t>> YamlObject::getList<uint8_t>() const;