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