LCOV - code coverage report
Current view: top level - src/media/video/v4l2 - video_device_monitor_impl.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 60 136 44.1 %
Date: 2024-12-21 08:56:24 Functions: 7 9 77.8 %

          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

Generated by: LCOV version 1.14