Ring Daemon 16.0.0
Loading...
Searching...
No Matches
v4l2/video_device_monitor_impl.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2009 Rémi Denis-Courmont
3 * Copyright (C) 2004-2025 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
36extern "C" {
37#include <fcntl.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40}
41
42namespace jami {
43namespace video {
44
45using std::vector;
46using std::string;
47
48class VideoDeviceMonitorImpl
49{
50public:
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 */
61
62 void start();
63
64 std::map<std::string, std::string> currentPathToId_ {};
65
66private:
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
80std::string
82{
83 if (auto serial = udev_device_get_property_value(udev_device, "ID_SERIAL"))
84 return serial;
85 throw std::invalid_argument("No ID_SERIAL detected");
86}
87
88static int
90{
91 const char* version = udev_device_get_property_value(dev, "ID_V4L_VERSION");
92 /* we do not support video4linux 1 */
93 return version and strcmp(version, "1");
94}
95
96VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor)
97 : monitor_(monitor)
98 , thread_()
99 , mutex_()
100 , udev_(0)
101 , udev_mon_(0)
102 , probing_(false)
103{
106
107 udev_ = udev_new();
108 if (!udev_)
109 goto udev_failed;
110
111 udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev");
112 if (!udev_mon_)
113 goto udev_failed;
114 if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL))
115 goto udev_failed;
116
117 /* Enumerate existing devices */
119 if (devenum == NULL)
120 goto udev_failed;
121 if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) {
123 goto udev_failed;
124 }
125
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. */
134 {
135 const char* path = udev_list_entry_get_name(deventry);
136 struct udev_device* dev = udev_device_new_from_syspath(udev_, path);
137
138 if (is_v4l2(dev)) {
139 const char* path = udev_device_get_devnode(dev);
140 if (path && std::string(path).find("/dev") != 0) {
141 // udev_device_get_devnode will fail
142 continue;
143 }
144 try {
146 JAMI_DBG("udev: adding device with id %s", unique_name.c_str());
147 if (monitor_->addDevice(unique_name, {{{"devPath", path}}}))
148 currentPathToId_.emplace(path, unique_name);
149 } catch (const std::exception& e) {
150 JAMI_WARN("udev: %s, fallback on path (your camera may be a fake camera)", e.what());
151 if (monitor_->addDevice(path, {{{"devPath", path}}}))
152 currentPathToId_.emplace(path, path);
153 }
154 }
156 }
157 udev_enumerate_unref(devenum);
158
159 return;
160
161udev_failed:
162
163 JAMI_ERR("udev enumeration failed");
164
165 if (udev_mon_)
166 udev_monitor_unref(udev_mon_);
167 if (udev_)
168 udev_unref(udev_);
169 udev_mon_ = NULL;
170 udev_ = NULL;
171
172 /* fallback : go through /dev/video* */
173 for (int idx = 0;; ++idx) {
174 try {
175 if (!monitor_->addDevice("/dev/video" + std::to_string(idx)))
176 break;
177 } catch (const std::runtime_error& e) {
178 JAMI_ERR("%s", e.what());
179 return;
180 }
181 }
182}
183
184void
185VideoDeviceMonitorImpl::start()
186{
187 probing_ = true;
188 thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
189}
190
191VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl()
192{
193 probing_ = false;
194 if (thread_.joinable())
195 thread_.join();
196 if (udev_mon_)
197 udev_monitor_unref(udev_mon_);
198 if (udev_)
199 udev_unref(udev_);
200}
201
202void
203VideoDeviceMonitorImpl::run()
204{
205 if (!udev_mon_) {
206 probing_ = false;
207 return;
208 }
209
210 const int udev_fd = udev_monitor_get_fd(udev_mon_);
211 while (probing_) {
212 timeval timeout = {0 /* sec */, 500000 /* usec */};
213 fd_set set;
214 FD_ZERO(&set);
215 FD_SET(udev_fd, &set);
216
217 int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout);
218 switch (ret) {
219 case 0:
220 break;
221 case 1: {
222 udev_device* dev = udev_monitor_receive_device(udev_mon_);
223 if (is_v4l2(dev)) {
224 const char* path = udev_device_get_devnode(dev);
225 if (path && std::string(path).find("/dev") != 0) {
226 // udev_device_get_devnode will fail
227 break;
228 }
229 try {
230 auto unique_name = getDeviceString(dev);
231
232 const char* action = udev_device_get_action(dev);
233 if (!strcmp(action, "add")) {
234 JAMI_DBG("udev: adding device with id %s", unique_name.c_str());
235 if (monitor_->addDevice(unique_name, {{{"devPath", path}}}))
236 currentPathToId_.emplace(path, unique_name);
237 } else if (!strcmp(action, "remove")) {
238 auto it = currentPathToId_.find(path);
239 if (it != currentPathToId_.end()) {
240 JAMI_DBG("udev: removing %s", it->second.c_str());
241 monitor_->removeDevice(it->second);
242 currentPathToId_.erase(it);
243 } else {
244 // In case of fallback
245 JAMI_DBG("udev: removing %s", path);
246 monitor_->removeDevice(path);
247 }
248 }
249 } catch (const std::exception& e) {
250 JAMI_ERR("%s", e.what());
251 }
252 }
253 udev_device_unref(dev);
254 break;
255 }
256
257 case -1:
258 if (errno == EAGAIN)
259 continue;
260 JAMI_ERR("udev monitoring thread: select failed (%m)");
261 probing_ = false;
262 return;
263
264 default:
265 JAMI_ERR("select() returned %d (%m)", ret);
266 probing_ = false;
267 return;
268 }
269 }
270}
271
272VideoDeviceMonitor::VideoDeviceMonitor()
273 : preferences_()
274 , devices_()
275 , monitorImpl_(new VideoDeviceMonitorImpl(this))
276{
277 monitorImpl_->start();
279}
280
281VideoDeviceMonitor::~VideoDeviceMonitor() {}
282
283} // namespace video
284} // namespace jami
VideoDeviceMonitorImpl(VideoDeviceMonitor *monitor)
std::map< std::string, std::string > currentPathToId_
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
Definition Address.h:25
static constexpr int version
std::string getDeviceString(struct udev_device *udev_device)
static constexpr const char DEVICE_DESKTOP[]
static int is_v4l2(struct udev_device *dev)
void emitSignal(Args... args)
Definition ring_signal.h:64
int32_t addDevice(const std::string &accountId, const std::string &uri)
Simple macro to hide class' copy constructor and assignment operator.
#define NON_COPYABLE(ClassName)
Definition noncopyable.h:30