summaryrefslogtreecommitdiff
path: root/test/hotplug-cameras.cpp
blob: 94cb859352ec232aec58ecdb43af02728720e268 (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
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2020, Umang Jain <email@uajain.com>
 *
 * hotplug-cameras.cpp - Test cameraAdded/cameraRemoved signals in CameraManager
 */

#include <dirent.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include <unistd.h>

#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>

#include "libcamera/internal/event_dispatcher.h"
#include "libcamera/internal/file.h"
#include "libcamera/internal/thread.h"
#include "libcamera/internal/timer.h"

#include "test.h"

using namespace libcamera;

class HotplugTest : public Test
{
protected:
	void cameraAddedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
	{
		cameraAdded_ = true;
	}

	void cameraRemovedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
	{
		cameraRemoved_ = true;
	}

	int init()
	{
		if (!File::exists("/sys/module/uvcvideo")) {
			std::cout << "uvcvideo driver is not loaded, skipping" << std::endl;
			return TestSkip;
		}

		if (geteuid() != 0) {
			std::cout << "This test requires root permissions, skipping" << std::endl;
			return TestSkip;
		}

		cm_ = new CameraManager();
		if (cm_->start()) {
			std::cout << "Failed to start camera manager" << std::endl;
			return TestFail;
		}

		cameraAdded_ = false;
		cameraRemoved_ = false;

		cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);

		return 0;
	}

	int run()
	{
		DIR *dir;
		struct dirent *dirent;
		std::string uvcDeviceDir;

		dir = opendir(uvcDriverDir_.c_str());
		/* Find a UVC device directory, which we can bind/unbind. */
		while ((dirent = readdir(dir)) != nullptr) {
			if (!File::exists(uvcDriverDir_ + dirent->d_name + "/video4linux"))
				continue;

			uvcDeviceDir = dirent->d_name;
			break;
		}
		closedir(dir);

		/* If no UVC device found, skip the test. */
		if (uvcDeviceDir.empty())
			return TestSkip;

		/* Unbind a camera and process events. */
		std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
			<< uvcDeviceDir;
		Timer timer;
		timer.start(1000);
		while (timer.isRunning() && !cameraRemoved_)
			Thread::current()->eventDispatcher()->processEvents();
		if (!cameraRemoved_) {
			std::cout << "Camera unplug not detected" << std::endl;
			return TestFail;
		}

		/* Bind the camera again and process events. */
		std::ofstream(uvcDriverDir_ + "bind", std::ios::binary)
			<< uvcDeviceDir;
		timer.start(1000);
		while (timer.isRunning() && !cameraAdded_)
			Thread::current()->eventDispatcher()->processEvents();
		if (!cameraAdded_) {
			std::cout << "Camera plug not detected" << std::endl;
			return TestFail;
		}

		return TestPass;
	}

	void cleanup()
	{
		cm_->stop();
		delete cm_;
	}

private:
	CameraManager *cm_;
	static const std::string uvcDriverDir_;
	bool cameraRemoved_;
	bool cameraAdded_;
};

const std::string HotplugTest::uvcDriverDir_ = "/sys/bus/usb/drivers/uvcvideo/";

TEST_REGISTER(HotplugTest)
="hl kwb">uint8_t> { static constexpr ControlType value = ControlTypeByte; }; template<> struct control_type<int32_t> { static constexpr ControlType value = ControlTypeInteger32; }; template<> struct control_type<int64_t> { static constexpr ControlType value = ControlTypeInteger64; }; template<> struct control_type<float> { static constexpr ControlType value = ControlTypeFloat; }; template<> struct control_type<std::string> { static constexpr ControlType value = ControlTypeString; }; template<> struct control_type<Rectangle> { static constexpr ControlType value = ControlTypeRectangle; }; template<> struct control_type<Size> { static constexpr ControlType value = ControlTypeSize; }; template<typename T, std::size_t N> struct control_type<Span<T, N>> : public control_type<std::remove_cv_t<T>> { }; } /* namespace details */ class ControlValue { public: ControlValue(); #ifndef __DOXYGEN__ template<typename T, typename std::enable_if_t<!details::is_span<T>::value && details::control_type<T>::value && !std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> ControlValue(const T &value) : type_(ControlTypeNone), numElements_(0) { set(details::control_type<std::remove_cv_t<T>>::value, false, &value, 1, sizeof(T)); } template<typename T, typename std::enable_if_t<details::is_span<T>::value || std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> #else template<typename T> #endif ControlValue(const T &value) : type_(ControlTypeNone), numElements_(0) { set(details::control_type<std::remove_cv_t<T>>::value, true, value.data(), value.size(), sizeof(typename T::value_type)); } ~ControlValue(); ControlValue(const ControlValue &other); ControlValue &operator=(const ControlValue &other); ControlType type() const { return type_; } bool isNone() const { return type_ == ControlTypeNone; } bool isArray() const { return isArray_; } std::size_t numElements() const { return numElements_; } Span<const uint8_t> data() const; Span<uint8_t> data(); std::string toString() const; bool operator==(const ControlValue &other) const; bool operator!=(const ControlValue &other) const { return !(*this == other); } #ifndef __DOXYGEN__ template<typename T, typename std::enable_if_t<!details::is_span<T>::value && !std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> T get() const { assert(type_ == details::control_type<std::remove_cv_t<T>>::value); assert(!isArray_); return *reinterpret_cast<const T *>(data().data()); } template<typename T, typename std::enable_if_t<details::is_span<T>::value || std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> #else template<typename T> #endif T get() const { assert(type_ == details::control_type<std::remove_cv_t<T>>::value); assert(isArray_); using V = typename T::value_type; const V *value = reinterpret_cast<const V *>(data().data()); return { value, numElements_ }; } #ifndef __DOXYGEN__ template<typename T, typename std::enable_if_t<!details::is_span<T>::value && !std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> void set(const T &value) { set(details::control_type<std::remove_cv_t<T>>::value, false, reinterpret_cast<const void *>(&value), 1, sizeof(T)); } template<typename T, typename std::enable_if_t<details::is_span<T>::value || std::is_same<std::string, std::remove_cv_t<T>>::value, std::nullptr_t> = nullptr> #else template<typename T> #endif void set(const T &value) { set(details::control_type<std::remove_cv_t<T>>::value, true, value.data(), value.size(), sizeof(typename T::value_type)); } void reserve(ControlType type, bool isArray = false, std::size_t numElements = 1); private: ControlType type_ : 8; bool isArray_; std::size_t numElements_ : 32; union { uint64_t value_; void *storage_; }; void release(); void set(ControlType type, bool isArray, const void *data, std::size_t numElements, std::size_t elementSize); }; class ControlId { public: ControlId(unsigned int id, const std::string &name, ControlType type) : id_(id), name_(name), type_(type) { } unsigned int id() const { return id_; } const std::string &name() const { return name_; } ControlType type() const { return type_; } private: LIBCAMERA_DISABLE_COPY_AND_MOVE(ControlId) unsigned int id_; std::string name_; ControlType type_; }; static inline bool operator==(unsigned int lhs, const ControlId &rhs) { return lhs == rhs.id(); } static inline bool operator!=(unsigned int lhs, const ControlId &rhs) { return !(lhs == rhs); } static inline bool operator==(const ControlId &lhs, unsigned int rhs) { return lhs.id() == rhs; } static inline bool operator!=(const ControlId &lhs, unsigned int rhs) { return !(lhs == rhs); } template<typename T> class Control : public ControlId { public: using type = T; Control(unsigned int id, const char *name) : ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value) { } private: LIBCAMERA_DISABLE_COPY_AND_MOVE(Control) }; class ControlInfo { public: explicit ControlInfo(const ControlValue &min = 0, const ControlValue &max = 0, const ControlValue &def = 0); explicit ControlInfo(Span<const ControlValue> values, const ControlValue &def = {}); const ControlValue &min() const { return min_; } const ControlValue &max() const { return max_; } const ControlValue &def() const { return def_; } const std::vector<ControlValue> &values() const { return values_; } std::string toString() const; bool operator==(const ControlInfo &other) const { return min_ == other.min_ && max_ == other.max_; } bool operator!=(const ControlInfo &other) const { return !(*this == other); } private: ControlValue min_; ControlValue max_; ControlValue def_; std::vector<ControlValue> values_; }; using ControlIdMap = std::unordered_map<unsigned int, const ControlId *>; class ControlInfoMap : private std::unordered_map<const ControlId *, ControlInfo> { public: using Map = std::unordered_map<const ControlId *, ControlInfo>; ControlInfoMap() = default; ControlInfoMap(const ControlInfoMap &other) = default; ControlInfoMap(std::initializer_list<Map::value_type> init); ControlInfoMap(Map &&info); ControlInfoMap &operator=(const ControlInfoMap &other) = default; ControlInfoMap &operator=(std::initializer_list<Map::value_type> init); ControlInfoMap &operator=(Map &&info); using Map::key_type; using Map::mapped_type; using Map::value_type; using Map::size_type; using Map::iterator; using Map::const_iterator; using Map::begin; using Map::cbegin; using Map::end; using Map::cend; using Map::at; using Map::empty; using Map::size; using Map::count; using Map::find; mapped_type &at(unsigned int key); const mapped_type &at(unsigned int key) const; size_type count(unsigned int key) const; iterator find(unsigned int key); const_iterator find(unsigned int key) const; const ControlIdMap &idmap() const { return idmap_; } private: void generateIdmap(); ControlIdMap idmap_; }; class ControlList { private: using ControlListMap = std::unordered_map<unsigned int, ControlValue>; public: ControlList(); ControlList(const ControlIdMap &idmap, ControlValidator *validator = nullptr); ControlList(const ControlInfoMap &infoMap, ControlValidator *validator = nullptr); using iterator = ControlListMap::iterator; using const_iterator = ControlListMap::const_iterator; iterator begin() { return controls_.begin(); } iterator end() { return controls_.end(); } const_iterator begin() const { return controls_.begin(); } const_iterator end() const { return controls_.end(); } bool empty() const { return controls_.empty(); } std::size_t size() const { return controls_.size(); } void clear() { controls_.clear(); } void merge(const ControlList &source); bool contains(const ControlId &id) const; bool contains(unsigned int id) const; template<typename T> T get(const Control<T> &ctrl) const { const ControlValue *val = find(ctrl.id()); if (!val) return T{}; return val->get<T>(); } template<typename T, typename V> void set(const Control<T> &ctrl, const V &value) { ControlValue *val = find(ctrl.id()); if (!val) return; val->set<T>(value); } template<typename T, typename V> void set(const Control<T> &ctrl, const std::initializer_list<V> &value) { ControlValue *val = find(ctrl.id()); if (!val) return; val->set<T>(Span<const typename std::remove_cv_t<V>>{ value.begin(), value.size() }); } const ControlValue &get(unsigned int id) const; void set(unsigned int id, const ControlValue &value); const ControlInfoMap *infoMap() const { return infoMap_; } private: const ControlValue *find(unsigned int id) const; ControlValue *find(unsigned int id); ControlValidator *validator_; const ControlIdMap *idmap_; const ControlInfoMap *infoMap_; ControlListMap controls_; }; } /* namespace libcamera */ #endif /* __LIBCAMERA_CONTROLS_H__ */