LCOV - code coverage report
Current view: top level - src/media/video/v4l2 - video_device_monitor_impl.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 43.8 % 137 60
Test Date: 2026-06-13 09:18:46 Functions: 24.1 % 29 7

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2009 Rémi Denis-Courmont
       3              :  *  Copyright (C) 2004-2026 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           64 : is_v4l2(struct udev_device* dev)
      90              : {
      91           64 :     const char* version = udev_device_get_property_value(dev, "ID_V4L_VERSION");
      92              :     /* we do not support video4linux 1 */
      93           64 :     return version and strcmp(version, "1");
      94              : }
      95              : 
      96           32 : VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor)
      97           32 :     : monitor_(monitor)
      98           32 :     , thread_()
      99           32 :     , mutex_()
     100           32 :     , udev_(0)
     101           32 :     , udev_mon_(0)
     102           32 :     , probing_(false)
     103              : {
     104              :     udev_list_entry* devlist;
     105              :     udev_enumerate* devenum;
     106              : 
     107           32 :     udev_ = udev_new();
     108           32 :     if (!udev_)
     109            0 :         goto udev_failed;
     110              : 
     111           32 :     udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev");
     112           32 :     if (!udev_mon_)
     113            0 :         goto udev_failed;
     114           32 :     if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL))
     115            0 :         goto udev_failed;
     116              : 
     117              :     /* Enumerate existing devices */
     118           32 :     devenum = udev_enumerate_new(udev_);
     119           32 :     if (devenum == NULL)
     120            0 :         goto udev_failed;
     121           32 :     if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) {
     122            0 :         udev_enumerate_unref(devenum);
     123            0 :         goto udev_failed;
     124              :     }
     125              : 
     126           32 :     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           32 :     udev_enumerate_scan_devices(devenum);
     131           32 :     devlist = udev_enumerate_get_list_entry(devenum);
     132              :     struct udev_list_entry* deventry;
     133           96 :     udev_list_entry_foreach(deventry, devlist)
     134              :     {
     135           64 :         const char* path = udev_list_entry_get_name(deventry);
     136           64 :         struct udev_device* dev = udev_device_new_from_syspath(udev_, path);
     137              : 
     138           64 :         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_LOG("udev: adding device with id {}", unique_name);
     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_WARNING("udev: {}, 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           64 :         udev_device_unref(dev);
     156              :     }
     157           32 :     udev_enumerate_unref(devenum);
     158              : 
     159           32 :     return;
     160              : 
     161            0 : udev_failed:
     162              : 
     163            0 :     JAMI_ERROR("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_ERROR("{}", e.what());
     179            0 :             return;
     180            0 :         }
     181            0 :     }
     182            0 : }
     183              : 
     184              : void
     185           32 : VideoDeviceMonitorImpl::start()
     186              : {
     187           32 :     probing_ = true;
     188           32 :     thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
     189           32 : }
     190              : 
     191           32 : VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl()
     192              : {
     193           32 :     probing_ = false;
     194           32 :     if (thread_.joinable())
     195           32 :         thread_.join();
     196           32 :     if (udev_mon_)
     197           32 :         udev_monitor_unref(udev_mon_);
     198           32 :     if (udev_)
     199           32 :         udev_unref(udev_);
     200           32 : }
     201              : 
     202              : void
     203           32 : VideoDeviceMonitorImpl::run()
     204              : {
     205           32 :     if (!udev_mon_) {
     206            0 :         probing_ = false;
     207            0 :         return;
     208              :     }
     209              : 
     210           32 :     const int udev_fd = udev_monitor_get_fd(udev_mon_);
     211        14206 :     while (probing_) {
     212        14174 :         timeval timeout = {0 /* sec */, 500000 /* usec */};
     213              :         fd_set set;
     214       240958 :         FD_ZERO(&set);
     215        14174 :         FD_SET(udev_fd, &set);
     216              : 
     217        14174 :         int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout);
     218        14174 :         switch (ret) {
     219        14174 :         case 0:
     220        14174 :             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_LOG("udev: adding device with id {}", unique_name);
     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_LOG("udev: removing {}", it->second);
     241            0 :                             monitor_->removeDevice(it->second);
     242            0 :                             currentPathToId_.erase(it);
     243              :                         } else {
     244              :                             // In case of fallback
     245            0 :                             JAMI_LOG("udev: removing {}", path);
     246            0 :                             monitor_->removeDevice(path);
     247              :                         }
     248              :                     }
     249            0 :                 } catch (const std::exception& e) {
     250            0 :                     JAMI_ERROR("{}", 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_ERROR("udev monitoring thread: select failed ({})", strerror(errno));
     261            0 :             probing_ = false;
     262            0 :             return;
     263              : 
     264            0 :         default:
     265            0 :             JAMI_ERROR("select() returned {} ({})", ret, strerror(errno));
     266            0 :             probing_ = false;
     267            0 :             return;
     268              :         }
     269              :     }
     270            0 : }
     271              : 
     272           32 : VideoDeviceMonitor::VideoDeviceMonitor()
     273           32 :     : preferences_()
     274           32 :     , devices_()
     275          128 :     , monitorImpl_(new VideoDeviceMonitorImpl(this))
     276              : {
     277           32 :     monitorImpl_->start();
     278           96 :     addDevice(DEVICE_DESKTOP, {});
     279           32 : }
     280              : 
     281           32 : VideoDeviceMonitor::~VideoDeviceMonitor() {}
     282              : 
     283              : } // namespace video
     284              : } // namespace jami
        

Generated by: LCOV version 2.0-1