video_v4l2_list.cpp 10.9 KB
Newer Older
1
/*
2
 *  Copyright (C) 2011-2013 Savoir-Faire Linux Inc.
3 4
 *  Copyright © 2009 Rémi Denis-Courmont
 *
5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *  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
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 *  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.
 */

33
#include <cstdio>
34
#include <stdexcept> // for std::runtime_error
35
#include <sstream>
36
#include <algorithm>
37
#include <unistd.h>
38

39 40
#include "logger.h"

41 42 43
#include <libudev.h>
#include <cstring>

44 45 46 47 48 49
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}

50 51
#include <cerrno>

52
#include "video_v4l2_list.h"
53
#include "manager.h"
54
#include "client/video_controls.h"
55

56 57
namespace sfl_video {

58 59
using std::vector;
using std::string;
60

61 62
static int is_v4l2(struct udev_device *dev)
{
63 64 65
    const char *version = udev_device_get_property_value(dev, "ID_V4L_VERSION");
    /* we do not support video4linux 1 */
    return version and strcmp(version, "1");
66 67
}

68
VideoV4l2ListThread::VideoV4l2ListThread() : devices_(),
69
    thread_(0), mutex_(), udev_(0),
70
    udev_mon_(0), probing_(false)
71
{
72 73
    udev_list_entry *devlist;
    udev_enumerate *devenum;
74

75 76
    udev_ = udev_new();
    if (!udev_)
77
        goto udev_failed;
78

79 80
    udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev");
    if (!udev_mon_)
81
        goto udev_failed;
82
    if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL))
83
        goto udev_failed;
84 85

    /* Enumerate existing devices */
86
    devenum = udev_enumerate_new(udev_);
87
    if (devenum == NULL)
88
        goto udev_failed;
89
    if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) {
90
        udev_enumerate_unref(devenum);
91
        goto udev_failed;
92 93
    }

94
    udev_monitor_enable_receiving(udev_mon_);
95 96 97
    /* 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. */
98 99
    udev_enumerate_scan_devices(devenum);
    devlist = udev_enumerate_get_list_entry(devenum);
100
    struct udev_list_entry *deventry;
101
    udev_list_entry_foreach(deventry, devlist) {
102 103
        const char *path = udev_list_entry_get_name(deventry);
        struct udev_device *dev = udev_device_new_from_syspath(udev_, path);
104

105
        if (is_v4l2(dev)) {
106
            const char *devpath = udev_device_get_devnode(dev);
107 108 109
            if (devpath) {
                try {
                    addDevice(devpath);
110
                } catch (const std::runtime_error &e) {
111
                    ERROR("%s", e.what());
112 113 114
                }
            }
        }
115
        udev_device_unref(dev);
116
    }
117
    udev_enumerate_unref(devenum);
118 119 120

    return;

121 122
udev_failed:

123
    ERROR("udev enumeration failed");
124

125
    if (udev_mon_)
126 127 128 129 130
        udev_monitor_unref(udev_mon_);
    if (udev_)
        udev_unref(udev_);
    udev_mon_ = NULL;
    udev_ = NULL;
131 132

    /* fallback : go through /dev/video* */
133
    for (int idx = 0;; ++idx) {
134 135 136
        std::stringstream ss;
        ss << "/dev/video" << idx;
        try {
137
            if (!addDevice(ss.str()))
Rafaël Carré's avatar
Rafaël Carré committed
138
                return;
139
        } catch (const std::runtime_error &e) {
140
            ERROR("%s", e.what());
141
            return;
142
        }
143 144
    }
}
145

146 147 148

void VideoV4l2ListThread::start()
{
149
    probing_ = true;
150 151 152 153 154 155 156 157 158 159 160 161
    pthread_create(&thread_, NULL, &runCallback, this);
}


void *VideoV4l2ListThread::runCallback(void *data)
{
    VideoV4l2ListThread *context = static_cast<VideoV4l2ListThread*>(data);
    context->run();
    return NULL;
}


162 163
namespace {

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    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;
            }
        }
188

189 190 191 192 193 194
        return -1;
    }

    void giveUniqueName(VideoV4l2Device &dev, const vector<VideoV4l2Device> &devices)
    {
start:
195 196
        for (auto &item : devices) {
            if (dev.name == item.name) {
197 198 199 200 201 202 203 204 205 206 207 208 209
                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
            }
        }
    }
210 211
} // end anonymous namespace

212
VideoV4l2ListThread::~VideoV4l2ListThread()
213
{
214
    probing_ = false;
215 216
    if (thread_)
        pthread_join(thread_, NULL);
217 218 219 220
    if (udev_mon_)
        udev_monitor_unref(udev_mon_);
    if (udev_)
        udev_unref(udev_);
221 222
}

223
void VideoV4l2ListThread::run()
224
{
225 226
    if (!udev_mon_) {
        probing_ = false;
227
        pthread_exit(NULL);
228
    }
229

230
    const int udev_fd = udev_monitor_get_fd(udev_mon_);
231 232
    while (probing_) {
        timeval timeout = {0 /* sec */, 500000 /* usec */};
233 234 235 236 237 238
        fd_set set;
        FD_ZERO(&set);
        FD_SET(udev_fd, &set);

        int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout);
        switch (ret) {
239 240
            case 0:
                break;
241
            case 1:
242 243 244 245 246 247
                {
                    udev_device *dev = udev_monitor_receive_device(udev_mon_);
                    if (!is_v4l2(dev)) {
                        udev_device_unref(dev);
                        break;
                    }
248

249 250 251 252 253 254
                    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);
255
                            Manager::instance().getVideoControls()->deviceEvent();
256 257 258 259 260 261
                        } catch (const std::runtime_error &e) {
                            ERROR("%s", e.what());
                        }
                    } else if (!strcmp(action, "remove")) {
                        DEBUG("udev: removing %s", node);
                        delDevice(string(node));
262
                    }
263 264
                    udev_device_unref(dev);
                    break;
265
                }
266

267 268 269 270
            case -1:
                if (errno == EAGAIN)
                    continue;
                ERROR("udev monitoring thread: select failed (%m)");
271
                probing_ = false;
272
                pthread_exit(NULL);
273 274 275

            default:
                ERROR("select() returned %d (%m)", ret);
276
                probing_ = false;
277
                pthread_exit(NULL);
278 279 280
        }
    }
}
281

282
void VideoV4l2ListThread::delDevice(const string &node)
283
{
284
    std::lock_guard<std::mutex> lock(mutex_);
285

286 287 288 289 290 291
    const auto itr = std::find_if(devices_.begin(), devices_.end(),
            [&] (const VideoV4l2Device &d) { return d.device == node; });

    if (itr != devices_.end()) {
        devices_.erase(itr);
        Manager::instance().getVideoControls()->deviceEvent();
292 293 294
    }
}

295
bool VideoV4l2ListThread::addDevice(const string &dev)
296
{
297
    std::lock_guard<std::mutex> lock(mutex_);
298

299
    int fd = open(dev.c_str(), O_RDWR);
300
    if (fd == -1)
Rafaël Carré's avatar
Rafaël Carré committed
301
        return false;
302

303
    const string s(dev);
304
    VideoV4l2Device v(fd, s);
305
    giveUniqueName(v, devices_);
306
    devices_.push_back(v);
307

308
    ::close(fd);
Rafaël Carré's avatar
Rafaël Carré committed
309
    return true;
310 311
}

312 313
vector<string>
VideoV4l2ListThread::getChannelList(const string &dev)
314
{
315
    std::lock_guard<std::mutex> lock(mutex_);
316 317 318 319 320
    Devices::const_iterator iter(findDevice(dev));
    if (iter != devices_.end())
        return iter->getChannelList();
    else
        return vector<string>();
321 322
}

323 324
vector<string>
VideoV4l2ListThread::getSizeList(const string &dev, const string &channel)
325
{
326
    std::lock_guard<std::mutex> lock(mutex_);
327 328 329 330 331
    Devices::const_iterator iter(findDevice(dev));
    if (iter != devices_.end())
        return iter->getChannel(channel).getSizeList();
    else
        return vector<string>();
332
}
Rafaël Carré's avatar
Rafaël Carré committed
333

334 335
vector<string>
VideoV4l2ListThread::getRateList(const string &dev, const string &channel, const std::string &size)
336
{
337
    std::lock_guard<std::mutex> lock(mutex_);
338 339 340 341 342
    Devices::const_iterator iter(findDevice(dev));
    if (iter != devices_.end())
        return iter->getChannel(channel).getSize(size).getRateList();
    else
        return vector<string>();
343 344
}

345
vector<string> VideoV4l2ListThread::getDeviceList()
346
{
347
    std::lock_guard<std::mutex> lock(mutex_);
348
    vector<string> v;
349

350 351
    for (const auto &itr : devices_)
       v.push_back(itr.name.empty() ? itr.device : itr.name);
352 353

    return v;
354 355
}

356 357
Devices::const_iterator
VideoV4l2ListThread::findDevice(const string &name) const
358
{
359 360 361 362
    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;
363 364
}

365
unsigned VideoV4l2ListThread::getChannelNum(const string &dev, const string &name)
366
{
367
    std::lock_guard<std::mutex> lock(mutex_);
368 369 370 371 372
    Devices::const_iterator iter(findDevice(dev));
    if (iter != devices_.end())
        return iter->getChannel(name).idx;
    else
        return 0;
373 374
}

375
string VideoV4l2ListThread::getDeviceNode(const string &name)
376
{
377
    std::lock_guard<std::mutex> lock(mutex_);
378 379 380 381 382
    Devices::const_iterator iter(findDevice(name));
    if (iter != devices_.end())
        return iter->device;
    else
        return "";
383
}
384
} // namespace sfl_video