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-04-20 08:06:59 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             :  *  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

Generated by: LCOV version 1.14