Files
jami-daemon/daemon/src/video/video_v4l2_list.cpp
Tristan Matthews c166017951 * #28351: daemon: rename iter to item for range-based for loops
"item" correctly reflects that in these loops, the object in question
is not an iterator but a reference to the actual object in the
collection.
2013-08-14 11:46:52 -04:00

390 lines
11 KiB
C++

/*
* Copyright (C) 2011-2012 Savoir-Faire Linux Inc.
* Copyright © 2009 Rémi Denis-Courmont
*
* Author: Rafaël Carré <rafael.carre@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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#include <cstdio>
#include <stdexcept> // for std::runtime_error
#include <sstream>
#include <algorithm>
#include <unistd.h>
#include "logger.h"
#include "scoped_lock.h"
#include <libudev.h>
#include <cstring>
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
#include <cerrno>
#include "video_v4l2_list.h"
#include "manager.h"
#include "client/video_controls.h"
namespace sfl_video {
using std::vector;
using std::string;
using sfl::ScopedLock;
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");
}
VideoV4l2ListThread::VideoV4l2ListThread() : devices_(),
thread_(0), mutex_(), udev_(0),
udev_mon_(0), probing_(false)
{
pthread_mutex_init(&mutex_, NULL);
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 {
addDevice(devpath);
} catch (const std::runtime_error &e) {
ERROR("%s", e.what());
}
}
}
udev_device_unref(dev);
}
udev_enumerate_unref(devenum);
return;
udev_failed:
ERROR("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 {
if (!addDevice(ss.str()))
return;
} catch (const std::runtime_error &e) {
ERROR("%s", e.what());
return;
}
}
}
void VideoV4l2ListThread::start()
{
probing_ = true;
pthread_create(&thread_, NULL, &runCallback, this);
}
void *VideoV4l2ListThread::runCallback(void *data)
{
VideoV4l2ListThread *context = static_cast<VideoV4l2ListThread*>(data);
context->run();
return NULL;
}
namespace {
typedef std::vector<VideoV4l2Device> Devices;
struct DeviceComparator {
explicit DeviceComparator(const std::string &name) : name_(name) {}
inline bool operator()(const VideoV4l2Device &d) const { return d.name == name_; }
private:
const std::string name_;
};
int getNumber(const string &name, size_t *sharp)
{
size_t len = name.length();
// name is too short to be numbered
if (len < 3)
return -1;
for (size_t c = len; c; --c) {
if (name[c] == '#') {
unsigned i;
if (sscanf(name.substr(c).c_str(), "#%u", &i) != 1)
return -1;
*sharp = c;
return i;
}
}
return -1;
}
void giveUniqueName(VideoV4l2Device &dev, const vector<VideoV4l2Device> &devices)
{
start:
for (auto &item : devices) {
if (dev.name == item.name) {
size_t sharp;
int num = getNumber(dev.name, &sharp);
if (num < 0) // not numbered
dev.name += " #0";
else {
std::stringstream ss;
ss << num + 1;
dev.name.replace(sharp + 1, ss.str().length(), ss.str());
}
goto start; // we changed the name, let's look again if it is unique
}
}
}
} // end anonymous namespace
VideoV4l2ListThread::~VideoV4l2ListThread()
{
probing_ = false;
if (thread_)
pthread_join(thread_, NULL);
if (udev_mon_)
udev_monitor_unref(udev_mon_);
if (udev_)
udev_unref(udev_);
pthread_mutex_destroy(&mutex_);
}
void VideoV4l2ListThread::run()
{
if (!udev_mon_) {
probing_ = false;
pthread_exit(NULL);
}
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")) {
DEBUG("udev: adding %s", node);
try {
addDevice(node);
Manager::instance().getVideoControls()->deviceEvent();
} catch (const std::runtime_error &e) {
ERROR("%s", e.what());
}
} else if (!strcmp(action, "remove")) {
DEBUG("udev: removing %s", node);
delDevice(string(node));
}
udev_device_unref(dev);
break;
}
case -1:
if (errno == EAGAIN)
continue;
ERROR("udev monitoring thread: select failed (%m)");
probing_ = false;
pthread_exit(NULL);
default:
ERROR("select() returned %d (%m)", ret);
probing_ = false;
pthread_exit(NULL);
}
}
}
void VideoV4l2ListThread::delDevice(const string &node)
{
ScopedLock lock(mutex_);
for (auto itr = devices_.begin(); itr != devices_.end(); ++itr) {
if (itr->device == node) {
devices_.erase(itr);
Manager::instance().getVideoControls()->deviceEvent();
return;
}
}
}
bool VideoV4l2ListThread::addDevice(const string &dev)
{
ScopedLock lock(mutex_);
int fd = open(dev.c_str(), O_RDWR);
if (fd == -1)
return false;
const string s(dev);
VideoV4l2Device v(fd, s);
giveUniqueName(v, devices_);
devices_.push_back(v);
::close(fd);
return true;
}
vector<string>
VideoV4l2ListThread::getChannelList(const string &dev)
{
ScopedLock lock(mutex_);
Devices::const_iterator iter(findDevice(dev));
if (iter != devices_.end())
return iter->getChannelList();
else
return vector<string>();
}
vector<string>
VideoV4l2ListThread::getSizeList(const string &dev, const string &channel)
{
ScopedLock lock(mutex_);
Devices::const_iterator iter(findDevice(dev));
if (iter != devices_.end())
return iter->getChannel(channel).getSizeList();
else
return vector<string>();
}
vector<string>
VideoV4l2ListThread::getRateList(const string &dev, const string &channel, const std::string &size)
{
ScopedLock lock(mutex_);
Devices::const_iterator iter(findDevice(dev));
if (iter != devices_.end())
return iter->getChannel(channel).getSize(size).getRateList();
else
return vector<string>();
}
vector<string> VideoV4l2ListThread::getDeviceList()
{
ScopedLock lock(mutex_);
vector<string> v;
for (const auto &itr : devices_)
v.push_back(itr.name.empty() ? itr.device : itr.name);
return v;
}
Devices::const_iterator
VideoV4l2ListThread::findDevice(const string &name) const
{
Devices::const_iterator iter(std::find_if(devices_.begin(), devices_.end(), DeviceComparator(name)));
if (iter == devices_.end())
ERROR("Device %s not found", name.c_str());
return iter;
}
unsigned VideoV4l2ListThread::getChannelNum(const string &dev, const string &name)
{
ScopedLock lock(mutex_);
Devices::const_iterator iter(findDevice(dev));
if (iter != devices_.end())
return iter->getChannel(name).idx;
else
return 0;
}
string VideoV4l2ListThread::getDeviceNode(const string &name)
{
ScopedLock lock(mutex_);
Devices::const_iterator iter(findDevice(name));
if (iter != devices_.end())
return iter->device;
else
return "";
}
} // namespace sfl_video