blob: b60774ab2b1c9503cd5575eccbd57404da60a509 [file] [log] [blame]
/*
* Copyright (C) 2009 Rémi Denis-Courmont
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
*
* Author: Rafaël Carré <rafael.carre@savoirfairelinux.com>
* Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <libudev.h>
#include <mutex>
#include <sstream>
#include <stdexcept> // for std::runtime_error
#include <string>
#include <thread>
#include <unistd.h>
#include <vector>
#include "../video_device_monitor.h"
#include "logger.h"
#include "noncopyable.h"
extern "C" {
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
}
namespace jami { namespace video {
using std::vector;
using std::string;
class VideoDeviceMonitorImpl {
public:
/*
* This is the only restriction to the pImpl design:
* as the Linux implementation has a thread, it needs a way to notify
* devices addition and deletion.
*
* This class should maybe inherit from VideoDeviceMonitor instead of
* being its pImpl.
*/
VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor);
~VideoDeviceMonitorImpl();
void start();
private:
NON_COPYABLE(VideoDeviceMonitorImpl);
VideoDeviceMonitor* monitor_;
void run();
std::thread thread_;
mutable std::mutex mutex_;
udev *udev_;
udev_monitor *udev_mon_;
bool probing_;
};
static int is_v4l2(struct udev_device *dev)
{
const char *version = udev_device_get_property_value(dev, "ID_V4L_VERSION");
/* we do not support video4linux 1 */
return version and strcmp(version, "1");
}
VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor) :
monitor_(monitor),
thread_(), mutex_(),
udev_(0), udev_mon_(0),
probing_(false)
{
udev_list_entry *devlist;
udev_enumerate *devenum;
udev_ = udev_new();
if (!udev_)
goto udev_failed;
udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev");
if (!udev_mon_)
goto udev_failed;
if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL))
goto udev_failed;
/* Enumerate existing devices */
devenum = udev_enumerate_new(udev_);
if (devenum == NULL)
goto udev_failed;
if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) {
udev_enumerate_unref(devenum);
goto udev_failed;
}
udev_monitor_enable_receiving(udev_mon_);
/* Note that we enumerate _after_ monitoring is enabled so that we do not
* loose device events occuring while we are enumerating. We could still
* loose events if the Netlink socket receive buffer overflows. */
udev_enumerate_scan_devices(devenum);
devlist = udev_enumerate_get_list_entry(devenum);
struct udev_list_entry *deventry;
udev_list_entry_foreach(deventry, devlist) {
const char *path = udev_list_entry_get_name(deventry);
struct udev_device *dev = udev_device_new_from_syspath(udev_, path);
if (is_v4l2(dev)) {
const char *devpath = udev_device_get_devnode(dev);
if (devpath) {
try {
monitor_->addDevice(string(devpath));
} catch (const std::runtime_error &e) {
JAMI_ERR("%s", e.what());
}
}
}
udev_device_unref(dev);
}
udev_enumerate_unref(devenum);
return;
udev_failed:
JAMI_ERR("udev enumeration failed");
if (udev_mon_)
udev_monitor_unref(udev_mon_);
if (udev_)
udev_unref(udev_);
udev_mon_ = NULL;
udev_ = NULL;
/* fallback : go through /dev/video* */
for (int idx = 0;; ++idx) {
std::stringstream ss;
ss << "/dev/video" << idx;
try {
monitor_->addDevice(ss.str());
} catch (const std::runtime_error &e) {
JAMI_ERR("%s", e.what());
return;
}
}
}
void VideoDeviceMonitorImpl::start()
{
probing_ = true;
thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
}
VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl()
{
probing_ = false;
if (thread_.joinable())
thread_.join();
if (udev_mon_)
udev_monitor_unref(udev_mon_);
if (udev_)
udev_unref(udev_);
}
void VideoDeviceMonitorImpl::run()
{
if (!udev_mon_) {
probing_ = false;
return;
}
const int udev_fd = udev_monitor_get_fd(udev_mon_);
while (probing_) {
timeval timeout = {0 /* sec */, 500000 /* usec */};
fd_set set;
FD_ZERO(&set);
FD_SET(udev_fd, &set);
int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout);
switch (ret) {
case 0:
break;
case 1:
{
udev_device *dev = udev_monitor_receive_device(udev_mon_);
if (!is_v4l2(dev)) {
udev_device_unref(dev);
break;
}
const char *node = udev_device_get_devnode(dev);
const char *action = udev_device_get_action(dev);
if (!strcmp(action, "add")) {
JAMI_DBG("udev: adding %s", node);
try {
monitor_->addDevice(node);
} catch (const std::runtime_error &e) {
JAMI_ERR("%s", e.what());
}
} else if (!strcmp(action, "remove")) {
JAMI_DBG("udev: removing %s", node);
monitor_->removeDevice(string(node));
}
udev_device_unref(dev);
break;
}
case -1:
if (errno == EAGAIN)
continue;
JAMI_ERR("udev monitoring thread: select failed (%m)");
probing_ = false;
return;
default:
JAMI_ERR("select() returned %d (%m)", ret);
probing_ = false;
return;
}
}
}
VideoDeviceMonitor::VideoDeviceMonitor() :
preferences_(), devices_(),
monitorImpl_(new VideoDeviceMonitorImpl(this))
{
monitorImpl_->start();
}
VideoDeviceMonitor::~VideoDeviceMonitor()
{}
}} // namespace jami::video