Line data Source code
1 : /*
2 : * Copyright (C) 2009 RĂ©mi Denis-Courmont
3 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation, either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 :
19 : #include <algorithm>
20 : #include <cerrno>
21 : #include <cstdio>
22 : #include <cstring>
23 : #include <libudev.h>
24 : #include <mutex>
25 : #include <sstream>
26 : #include <stdexcept> // for std::runtime_error
27 : #include <string>
28 : #include <thread>
29 : #include <unistd.h>
30 : #include <vector>
31 :
32 : #include "../video_device_monitor.h"
33 : #include "logger.h"
34 : #include "noncopyable.h"
35 :
36 : extern "C" {
37 : #include <fcntl.h>
38 : #include <sys/stat.h>
39 : #include <sys/types.h>
40 : }
41 :
42 : namespace jami {
43 : namespace video {
44 :
45 : using std::vector;
46 : using std::string;
47 :
48 : class VideoDeviceMonitorImpl
49 : {
50 : public:
51 : /*
52 : * This is the only restriction to the pImpl design:
53 : * as the Linux implementation has a thread, it needs a way to notify
54 : * devices addition and deletion.
55 : *
56 : * This class should maybe inherit from VideoDeviceMonitor instead of
57 : * being its pImpl.
58 : */
59 : VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor);
60 : ~VideoDeviceMonitorImpl();
61 :
62 : void start();
63 :
64 : std::map<std::string, std::string> currentPathToId_ {};
65 :
66 : private:
67 : NON_COPYABLE(VideoDeviceMonitorImpl);
68 :
69 : VideoDeviceMonitor* monitor_;
70 :
71 : void run();
72 : std::thread thread_;
73 : mutable std::mutex mutex_;
74 :
75 : udev* udev_;
76 : udev_monitor* udev_mon_;
77 : bool probing_;
78 : };
79 :
80 : std::string
81 0 : getDeviceString(struct udev_device* udev_device)
82 : {
83 0 : if (auto serial = udev_device_get_property_value(udev_device, "ID_SERIAL"))
84 0 : return serial;
85 0 : throw std::invalid_argument("No ID_SERIAL detected");
86 : }
87 :
88 : static int
89 76 : is_v4l2(struct udev_device* dev)
90 : {
91 76 : const char* version = udev_device_get_property_value(dev, "ID_V4L_VERSION");
92 : /* we do not support video4linux 1 */
93 76 : return version and strcmp(version, "1");
94 : }
95 :
96 38 : VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor)
97 38 : : monitor_(monitor)
98 38 : , thread_()
99 38 : , mutex_()
100 38 : , udev_(0)
101 38 : , udev_mon_(0)
102 38 : , probing_(false)
103 : {
104 : udev_list_entry* devlist;
105 : udev_enumerate* devenum;
106 :
107 38 : udev_ = udev_new();
108 38 : if (!udev_)
109 0 : goto udev_failed;
110 :
111 38 : udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev");
112 38 : if (!udev_mon_)
113 0 : goto udev_failed;
114 38 : if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL))
115 0 : goto udev_failed;
116 :
117 : /* Enumerate existing devices */
118 38 : devenum = udev_enumerate_new(udev_);
119 38 : if (devenum == NULL)
120 0 : goto udev_failed;
121 38 : if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) {
122 0 : udev_enumerate_unref(devenum);
123 0 : goto udev_failed;
124 : }
125 :
126 38 : udev_monitor_enable_receiving(udev_mon_);
127 : /* Note that we enumerate _after_ monitoring is enabled so that we do not
128 : * loose device events occuring while we are enumerating. We could still
129 : * loose events if the Netlink socket receive buffer overflows. */
130 38 : udev_enumerate_scan_devices(devenum);
131 38 : devlist = udev_enumerate_get_list_entry(devenum);
132 : struct udev_list_entry* deventry;
133 114 : udev_list_entry_foreach(deventry, devlist)
134 : {
135 76 : const char* path = udev_list_entry_get_name(deventry);
136 76 : struct udev_device* dev = udev_device_new_from_syspath(udev_, path);
137 :
138 76 : if (is_v4l2(dev)) {
139 0 : const char* path = udev_device_get_devnode(dev);
140 0 : if (path && std::string(path).find("/dev") != 0) {
141 : // udev_device_get_devnode will fail
142 0 : continue;
143 : }
144 : try {
145 0 : auto unique_name = getDeviceString(dev);
146 0 : JAMI_DBG("udev: adding device with id %s", unique_name.c_str());
147 0 : if (monitor_->addDevice(unique_name, {{{"devPath", path}}}))
148 0 : currentPathToId_.emplace(path, unique_name);
149 0 : } catch (const std::exception& e) {
150 0 : JAMI_WARN("udev: %s, fallback on path (your camera may be a fake camera)", e.what());
151 0 : if (monitor_->addDevice(path, {{{"devPath", path}}}))
152 0 : currentPathToId_.emplace(path, path);
153 0 : }
154 : }
155 76 : udev_device_unref(dev);
156 : }
157 38 : udev_enumerate_unref(devenum);
158 :
159 38 : return;
160 :
161 0 : udev_failed:
162 :
163 0 : JAMI_ERR("udev enumeration failed");
164 :
165 0 : if (udev_mon_)
166 0 : udev_monitor_unref(udev_mon_);
167 0 : if (udev_)
168 0 : udev_unref(udev_);
169 0 : udev_mon_ = NULL;
170 0 : udev_ = NULL;
171 :
172 : /* fallback : go through /dev/video* */
173 0 : for (int idx = 0;; ++idx) {
174 : try {
175 0 : if (!monitor_->addDevice("/dev/video" + std::to_string(idx)))
176 0 : break;
177 0 : } catch (const std::runtime_error& e) {
178 0 : JAMI_ERR("%s", e.what());
179 0 : return;
180 0 : }
181 0 : }
182 0 : }
183 :
184 : void
185 38 : VideoDeviceMonitorImpl::start()
186 : {
187 38 : probing_ = true;
188 38 : thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
189 38 : }
190 :
191 38 : VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl()
192 : {
193 38 : probing_ = false;
194 38 : if (thread_.joinable())
195 38 : thread_.join();
196 38 : if (udev_mon_)
197 38 : udev_monitor_unref(udev_mon_);
198 38 : if (udev_)
199 38 : udev_unref(udev_);
200 38 : }
201 :
202 : void
203 38 : VideoDeviceMonitorImpl::run()
204 : {
205 38 : if (!udev_mon_) {
206 0 : probing_ = false;
207 0 : return;
208 : }
209 :
210 38 : const int udev_fd = udev_monitor_get_fd(udev_mon_);
211 14945 : while (probing_) {
212 14907 : timeval timeout = {0 /* sec */, 500000 /* usec */};
213 : fd_set set;
214 253419 : FD_ZERO(&set);
215 14907 : FD_SET(udev_fd, &set);
216 :
217 14907 : int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout);
218 14907 : switch (ret) {
219 14907 : case 0:
220 14907 : break;
221 0 : case 1: {
222 0 : udev_device* dev = udev_monitor_receive_device(udev_mon_);
223 0 : if (is_v4l2(dev)) {
224 0 : const char* path = udev_device_get_devnode(dev);
225 0 : if (path && std::string(path).find("/dev") != 0) {
226 : // udev_device_get_devnode will fail
227 0 : break;
228 : }
229 : try {
230 0 : auto unique_name = getDeviceString(dev);
231 :
232 0 : const char* action = udev_device_get_action(dev);
233 0 : if (!strcmp(action, "add")) {
234 0 : JAMI_DBG("udev: adding device with id %s", unique_name.c_str());
235 0 : if (monitor_->addDevice(unique_name, {{{"devPath", path}}}))
236 0 : currentPathToId_.emplace(path, unique_name);
237 0 : } else if (!strcmp(action, "remove")) {
238 0 : auto it = currentPathToId_.find(path);
239 0 : if (it != currentPathToId_.end()) {
240 0 : JAMI_DBG("udev: removing %s", it->second.c_str());
241 0 : monitor_->removeDevice(it->second);
242 0 : currentPathToId_.erase(it);
243 : } else {
244 : // In case of fallback
245 0 : JAMI_DBG("udev: removing %s", path);
246 0 : monitor_->removeDevice(path);
247 : }
248 : }
249 0 : } catch (const std::exception& e) {
250 0 : JAMI_ERR("%s", e.what());
251 0 : }
252 : }
253 0 : udev_device_unref(dev);
254 0 : break;
255 : }
256 :
257 0 : case -1:
258 0 : if (errno == EAGAIN)
259 0 : continue;
260 0 : JAMI_ERR("udev monitoring thread: select failed (%m)");
261 0 : probing_ = false;
262 0 : return;
263 :
264 0 : default:
265 0 : JAMI_ERR("select() returned %d (%m)", ret);
266 0 : probing_ = false;
267 0 : return;
268 : }
269 : }
270 : }
271 :
272 38 : VideoDeviceMonitor::VideoDeviceMonitor()
273 38 : : preferences_()
274 38 : , devices_()
275 76 : , monitorImpl_(new VideoDeviceMonitorImpl(this))
276 : {
277 38 : monitorImpl_->start();
278 38 : addDevice(DEVICE_DESKTOP, {});
279 38 : }
280 :
281 38 : VideoDeviceMonitor::~VideoDeviceMonitor() {}
282 :
283 : } // namespace video
284 : } // namespace jami
|