1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2  * @file   WebcamSource.cpp
3  * @author Sebastien Fourey
4  * @date   July 2010
5  * @brief Definition of methods of the class WebcamGrabber
6  *
7  * This file is part of the ZArt software's source code.
8  *
9  * Copyright Sebastien Fourey / GREYC Ensicaen (2010-...)
10  *
11  *                    https://foureys.users.greyc.fr/
12  *
13  * This software is a computer program whose purpose is to demonstrate
14  * the possibilities of the GMIC image processing language by offering the
15  * choice of several manipulations on a video stream acquired from a webcam. In
16  * other words, ZArt is a GUI for G'MIC real-time manipulations on the output
17  * of a webcam.
18  *
19  * This software is governed by the CeCILL  license under French law and
20  * abiding by the rules of distribution of free software.  You can  use,
21  * modify and/ or redistribute the software under the terms of the CeCILL
22  * license as circulated by CEA, CNRS and INRIA at the following URL
23  * "http://www.cecill.info". See also the directory "Licence" which comes
24  * with this source code for the full text of the CeCILL license.
25  *
26  * As a counterpart to the access to the source code and  rights to copy,
27  * modify and redistribute granted by the license, users are provided only
28  * with a limited warranty  and the software's author,  the holder of the
29  * economic rights,  and the successive licensors  have only  limited
30  * liability.
31  *
32  * In this respect, the user's attention is drawn to the risks associated
33  * with loading,  using,  modifying and/or developing or reproducing the
34  * software by the user in light of its specific status of free software,
35  * that may mean  that it is complicated to manipulate,  and  that  also
36  * therefore means  that it is reserved for developers  and  experienced
37  * professionals having in-depth computer knowledge. Users are therefore
38  * encouraged to load and test the software's suitability as regards their
39  * requirements in conditions enabling the security of their systems and/or
40  * data to be ensured and,  more generally, to use and operate it in the
41  * same conditions as regards security.
42  *
43  * The fact that you are presently reading this means that you have had
44  * knowledge of the CeCILL license and that you accept its terms.
45  */
46 #include "WebcamSource.h"
47 #include <QCoreApplication>
48 #include <QDir>
49 #include <QFile>
50 #include <QList>
51 #include <QSettings>
52 #include <QSplashScreen>
53 #include <QStatusBar>
54 #include <QStringList>
55 #include <QtGlobal>
56 #include <algorithm>
57 #include <chrono>
58 #include <set>
59 #include <thread>
60 #include "Common.h"
61 using namespace std;
62 
63 #if CV_MAJOR_VERSION >= 3
64 #define ZART_CV_CAP_PROP_FRAME_WIDTH cv::VideoCaptureProperties::CAP_PROP_FRAME_WIDTH
65 #define ZART_CV_CAP_PROP_FRAME_HEIGHT cv::VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT
66 #else
67 #define ZART_CV_CAP_PROP_FRAME_WIDTH CV_CAP_PROP_FRAME_WIDTH
68 #define ZART_CV_CAP_PROP_FRAME_HEIGHT CV_CAP_PROP_FRAME_HEIGHT
69 #endif
70 
71 #if (CV_MAJOR_VERSION > 3) || ((CV_MAJOR_VERSION == 3) && (CV_MINOR_VERSION > 4)) || ((CV_MAJOR_VERSION == 3) && (CV_MINOR_VERSION == 4) && (CV_SUBMINOR_VERSION >= 4))
72 #define CVCAPTURE_HAS_BACKEND_METHOD
73 #endif
74 
75 #ifdef HAS_V4L2
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <linux/videodev2.h>
79 #include <sys/ioctl.h>
80 #include <sys/stat.h>
81 #include <sys/types.h>
82 #include <unistd.h>
83 #endif
84 
85 QVector<QList<QSize>> WebcamSource::_webcamResolutions;
86 QList<int> WebcamSource::_webcamList;
87 
88 namespace
89 {
90 class QSizeCompare {
91 public:
operator ()(const QSize & a,const QSize & b) const92   bool operator()(const QSize & a, const QSize & b) const { return ((a.width() < b.width()) || ((a.width() == b.width()) && (a.height() < b.height()))); }
93 };
94 } // namespace
95 
WebcamSource()96 WebcamSource::WebcamSource() : _capture(nullptr), _cameraIndex(-1), _captureSize(640, 480) {}
97 
~WebcamSource()98 WebcamSource::~WebcamSource()
99 {
100   if (_capture) {
101     delete _capture;
102     _capture = nullptr;
103   }
104 }
105 
capture()106 void WebcamSource::capture()
107 {
108   cv::Mat * anImage = new cv::Mat;
109   if (_capture && _capture->read(*anImage)) {
110     setImage(anImage);
111   } else {
112     delete anImage;
113   }
114 }
115 
getWebcamList()116 const QList<int> & WebcamSource::getWebcamList()
117 {
118   _webcamList.clear();
119 #if defined(HAS_V4L2)
120   for (int i = 0; i <= 10; ++i) {
121     QString filename = QString("/dev/video%1").arg(i);
122     if (!QFileInfo(filename).isReadable()) {
123       continue;
124     }
125     int fd = open(filename.toLocal8Bit().constData(), O_RDWR);
126     if (fd != -1) {
127 
128       bool isCapture = false;
129       v4l2_capability cap;
130       if (!ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
131         if (cap.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
132           qDebug("[Device %d] is a capture", i);
133           isCapture = true;
134         }
135       }
136 
137       bool camera = false;
138       v4l2_input input;
139       input.index = 0;
140       while (!ioctl(fd, VIDIOC_ENUMINPUT, &input)) {
141         if (input.type == V4L2_INPUT_TYPE_CAMERA) {
142           qDebug("[Device %d] is a camera", i);
143           camera = true;
144         }
145         ++input.index;
146       }
147 
148       close(fd);
149       if (isCapture && camera) {
150         _webcamList.push_back(i);
151       }
152     }
153   }
154 #elif defined(_IS_UNIX_)
155   const QString os = osName();
156   const bool skipOdd = (os == "Fedora" || os == "FreeBSD");
157   for (int i = 0; i <= 10; i += (1 + skipOdd)) {
158     QFile file(QString("/dev/video%1").arg(i));
159     if (file.open(QFile::ReadOnly)) {
160       file.close();
161       cv::VideoCapture capture;
162       capture.open(i);
163       if (captureIsValid(capture, i)) {
164         _webcamList.push_back(i);
165       }
166       capture.release();
167     }
168   }
169 #else
170   cv::VideoCapture * capture;
171   for (int i = 0; i <= 10; ++i) {
172     capture = new cv::VideoCapture;
173     if (capture && capture->open(i)) {
174       capture->release();
175       delete capture;
176       _webcamList.push_back(i);
177     }
178   }
179 #endif
180   TSHOW(_webcamList);
181   return _webcamList;
182 }
183 
getFirstUnusedWebcam()184 int WebcamSource::getFirstUnusedWebcam()
185 {
186   if (!_webcamList.size()) {
187     getWebcamList();
188   }
189   QList<int>::iterator it = _webcamList.begin();
190   while (it != _webcamList.end()) {
191     TRACE << "Looking for first unused webcam : " << *it;
192     if (isWebcamUnused(*it)) {
193       return *it;
194     }
195     ++it;
196   }
197   return -1;
198 }
199 
isWebcamUnused(int index)200 bool WebcamSource::isWebcamUnused(int index)
201 {
202   try {
203     cv::VideoCapture capture;
204 #if defined(_IS_UNIX_)
205     bool canOpenFile = false;
206     {
207       QFile file(QString("/dev/video%1").arg(index));
208       if (file.open(QFile::ReadOnly)) {
209         canOpenFile = true;
210         file.close();
211       }
212     }
213 #else
214     const bool canOpenFile = true;
215 #endif
216     if (canOpenFile && capture.open(index)) {
217       cv::Mat cvImage;
218       TRACE << "Trying to read an image from webcam " << index;
219       if (capture.read(cvImage)) {
220         std::cout << "[ZArt] Webcam " << index << " is available (" << cvImage.cols << "x" << cvImage.rows << ")\n";
221         return true;
222       }
223     }
224   } catch (const cv::Exception &) {
225     TRACE << "DONE (exception) index " << index;
226   }
227   TRACE << "DONE index " << index;
228   std::cout << "[ZArt] Cannot use webcam " << index << std::endl;
229   return false;
230 }
231 
canOpenDeviceFile(int index)232 bool WebcamSource::canOpenDeviceFile(int index)
233 {
234 #if defined(_IS_UNIX_)
235   QFile file(QString("/dev/video%1").arg(index));
236   if (file.open(QFile::ReadOnly)) {
237     file.close();
238     return true;
239   }
240   return false;
241 #else
242   Q_UNUSED(index)
243   return true;
244 #endif
245 }
246 
getCachedWebcamList()247 const QList<int> & WebcamSource::getCachedWebcamList()
248 {
249   return _webcamList;
250 }
251 
setCameraIndex(int i)252 void WebcamSource::setCameraIndex(int i)
253 {
254   _cameraIndex = i;
255 }
256 
stop()257 void WebcamSource::stop()
258 {
259   if (_capture) {
260     delete _capture;
261     _capture = nullptr;
262   }
263 }
264 
start()265 void WebcamSource::start()
266 {
267   if (!_capture && _cameraIndex != -1) {
268     _capture = new cv::VideoCapture;
269     cv::Mat * capturedImage = new cv::Mat;
270     if (_capture && _capture->open(_cameraIndex)) {
271       try {
272         _capture->read(*capturedImage); // TODO : Check
273       } catch (cv::Exception &) {
274         capturedImage = nullptr;
275       }
276     } else {
277       delete capturedImage;
278       capturedImage = nullptr;
279     }
280     setImage(capturedImage);
281     if (image()) {
282       QSize imageSize(image()->cols, image()->rows);
283       if (imageSize != _captureSize) {
284         _capture->set(ZART_CV_CAP_PROP_FRAME_WIDTH, _captureSize.width());
285         _capture->set(ZART_CV_CAP_PROP_FRAME_HEIGHT, _captureSize.height());
286         cv::Mat * anImage = new cv::Mat;
287         // Read two images!
288         _capture->grab();
289         // _capture->read(*anImage);
290         if (_capture->read(*anImage)) {
291           setImage(anImage);
292         } else {
293           delete anImage;
294         }
295       }
296       setWidth(image()->cols);
297       setHeight(image()->rows);
298     }
299   }
300 }
301 
setCaptureSize(int width,int height)302 void WebcamSource::setCaptureSize(int width, int height)
303 {
304   setCaptureSize(QSize(width, height));
305 }
306 
setCaptureSize(const QSize & size)307 void WebcamSource::setCaptureSize(const QSize & size)
308 {
309   _captureSize = size;
310   if (_capture) {
311     stop();
312     start();
313   }
314 }
315 
captureSize()316 QSize WebcamSource::captureSize()
317 {
318   return _captureSize;
319 }
320 
cameraIndex()321 int WebcamSource::cameraIndex()
322 {
323   return _cameraIndex;
324 }
325 
retrieveWebcamResolutions(const QList<int> & camList,QSplashScreen * splashScreen,QStatusBar * statusBar)326 void WebcamSource::retrieveWebcamResolutions(const QList<int> & camList, QSplashScreen * splashScreen, QStatusBar * statusBar)
327 {
328 #if defined(HAS_V4L2)
329   retrieveWebcamResolutionsV4L2(camList);
330   Q_UNUSED(splashScreen)
331   Q_UNUSED(statusBar)
332 #else
333   retrieveWebcamResolutionsOpenCV(camList, splashScreen, statusBar);
334 #endif
335   return;
336 }
337 
retrieveWebcamResolutionsOpenCV(const QList<int> & camList,QSplashScreen * splashScreen,QStatusBar * statusBar)338 void WebcamSource::retrieveWebcamResolutionsOpenCV(const QList<int> & camList, QSplashScreen * splashScreen, QStatusBar * statusBar)
339 {
340   QSettings settings;
341   QList<std::set<QSize, QSizeCompare>> resolutions;
342   QList<int>::const_iterator it = camList.begin();
343   int iCam = 0;
344   while (it != camList.end()) {
345     resolutions.push_back(std::set<QSize, QSizeCompare>());
346     cv::VideoCapture capture;
347     if (canOpenDeviceFile(*it) && capture.open(*it) && captureIsValid(capture, *it)) {
348       QStringList resolutionsStrList = settings.value(QString("WebcamSource/ResolutionsListForCam%1").arg(camList[iCam])).toStringList();
349       bool settingsAreFine = !resolutionsStrList.isEmpty();
350       for (int i = 0; i < resolutionsStrList.size() && settingsAreFine; ++i) {
351         QStringList str = resolutionsStrList.at(i).split(QChar('x'));
352         int w = str[0].toInt();
353         int h = str[1].toInt();
354         bool ok1 = capture.set(ZART_CV_CAP_PROP_FRAME_WIDTH, w);
355         bool ok2 = capture.set(ZART_CV_CAP_PROP_FRAME_HEIGHT, h);
356         if (!ok1 || !ok2) {
357           continue;
358         }
359         cv::Mat tmpA;
360         capture.read(tmpA);
361         QSize size(static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_WIDTH)), static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_HEIGHT)));
362         if (size == QSize(w, h)) {
363           resolutions.back().insert(size);
364         } else {
365           settingsAreFine = false;
366         }
367       }
368 
369       if (!settingsAreFine) {
370         if (splashScreen) {
371           splashScreen->showMessage(QString("Retrieving webcam #%1 resolutions...\n(almost-brute-force checking!)\nResult will be saved.").arg(iCam + 1), Qt::AlignBottom);
372           qApp->processEvents();
373         }
374         if (statusBar) {
375           statusBar->showMessage(QString("Retrieving webcam #%1 resolutions...").arg(iCam + 1), Qt::AlignBottom);
376           qApp->processEvents();
377         }
378         resolutions.back().clear();
379 
380         // Default size ?
381         {
382           cv::Mat tmp;
383           capture.read(tmp);
384           QSize defaultSize(static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_WIDTH)), static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_HEIGHT)));
385           if (defaultSize.isValid() && !defaultSize.isEmpty()) {
386             resolutions.back().insert(defaultSize);
387           }
388         }
389 
390         int ratioWidth[] = {4, 16, 0};
391         int ratioHeight[] = {3, 9, 0};
392         int widths[] = {320, 640, 800, 1024, 1280, 1600, 1920, 0};
393 
394         for (int i = 0; widths[i]; ++i) {
395           int w = widths[i];
396           for (int ratio = 0; ratioWidth[ratio]; ++ratio) {
397             int h = w * ratioHeight[ratio] / ratioWidth[ratio];
398             QSize requestedSize(w, h);
399             try {
400               cv::Mat tmp;
401               capture.read(tmp);
402               capture.set(ZART_CV_CAP_PROP_FRAME_WIDTH, w);
403               capture.set(ZART_CV_CAP_PROP_FRAME_HEIGHT, h);
404               QSize size(static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_WIDTH)), static_cast<int>(capture.get(ZART_CV_CAP_PROP_FRAME_HEIGHT)));
405               if ((splashScreen || statusBar) && resolutions.back().find(size) == resolutions.back().end()) {
406                 if (splashScreen) {
407                   splashScreen->showMessage(QString("Retrieving webcam #%1 resolutions... %2x%3\n(brute-force checking!)\nResult will be saved.").arg(iCam + 1).arg(size.width()).arg(size.height()),
408                                             Qt::AlignBottom);
409                 }
410                 if (statusBar) {
411                   statusBar->showMessage(QString("Retrieving webcam #%1 resolutions... %2x%3").arg(iCam + 1).arg(size.width()).arg(size.height()));
412                 }
413                 qApp->processEvents();
414               }
415               if (size.isValid() && !size.isNull()) {
416                 resolutions.back().insert(size);
417               }
418             } catch (cv::Exception &) {
419               std::cerr << "Cannot set capture size " << w << "x" << h << std::endl;
420             }
421           }
422         }
423       }
424     } else {
425       if (splashScreen) {
426         splashScreen->showMessage(QString("Webcam #%1 cannot be opened of used.").arg(iCam + 1), Qt::AlignBottom);
427         qApp->processEvents();
428       }
429       if (statusBar) {
430         statusBar->showMessage(QString("Webcam #%1 cannot be opened or used.").arg(iCam + 1), Qt::AlignBottom);
431         qApp->processEvents();
432       }
433       resolutions.back().clear();
434     }
435     ++it;
436     ++iCam;
437   }
438 
439   qDebug("Done checking resolutions");
440 
441   _webcamResolutions.clear();
442   _webcamResolutions.resize(camList.size());
443   iCam = 0;
444   QList<std::set<QSize, QSizeCompare>>::iterator itCam = resolutions.begin();
445   while (itCam != resolutions.end()) {
446     QStringList resolutionsList;
447     std::set<QSize, QSizeCompare>::iterator itRes = itCam->begin();
448     while (itRes != itCam->end()) {
449       _webcamResolutions[iCam].push_back(*itRes);
450       resolutionsList << QString("%1x%2").arg(itRes->width()).arg(itRes->height());
451       ++itRes;
452     }
453     settings.setValue(QString("WebcamSource/ResolutionsListForCam%1").arg(camList[iCam]), resolutionsList);
454     ++itCam;
455     ++iCam;
456   }
457 }
458 
retrieveWebcamResolutionsV4L2(const QList<int> & camList)459 void WebcamSource::retrieveWebcamResolutionsV4L2(const QList<int> & camList)
460 {
461   ENTERING;
462 #if defined(HAS_V4L2)
463   QSettings settings;
464   QList<std::set<QSize, QSizeCompare>> resolutions;
465   QList<int>::const_iterator it = camList.begin();
466   int iCam = 0;
467   for (; it != camList.end(); ++it, ++iCam) {
468     resolutions.push_back(std::set<QSize, QSizeCompare>());
469     QString filename = QString("/dev/video%1").arg(*it);
470     if (!QFileInfo(filename).isReadable()) {
471       continue;
472     }
473     int fd = open(filename.toLocal8Bit().constData(), O_RDWR);
474     if (fd != -1) {
475       // Is a camera?
476       bool camera = false;
477       v4l2_input input;
478       input.index = 0;
479       while (!ioctl(fd, VIDIOC_ENUMINPUT, &input)) {
480         if (input.type == V4L2_INPUT_TYPE_CAMERA) {
481           qDebug("[Device %d] is a camera", *it);
482           camera = true;
483         }
484         ++input.index;
485       }
486       if (camera) {
487         v4l2_fmtdesc fmt;
488         fmt.index = 0;
489         fmt.type = V4L2_CAP_VIDEO_CAPTURE;
490         QList<unsigned int> pixelFormats;
491         while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) {
492           qDebug("[Device %d] pixel format %c%c%c%c", *it, (char)(fmt.pixelformat & 0xFF), (char)((fmt.pixelformat & 0xFF00) >> 8), (char)((fmt.pixelformat & 0xFF0000) >> 16),
493                  (char)((fmt.pixelformat & 0xFF000000) >> 24));
494           pixelFormats += fmt.pixelformat;
495           ++fmt.index;
496         }
497         for (unsigned int pixelformat : pixelFormats) {
498           v4l2_frmsizeenum framesize;
499           framesize.index = 0;
500           framesize.pixel_format = pixelformat;
501           while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &framesize)) {
502             if (framesize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
503               QSize size(framesize.discrete.width, framesize.discrete.height);
504               resolutions.back().insert(size);
505             }
506             ++framesize.index;
507           }
508         }
509       }
510       close(fd);
511     }
512   }
513   qDebug("Done checking resolutions");
514   _webcamResolutions.clear();
515   _webcamResolutions.resize(camList.size());
516   iCam = 0;
517   QList<std::set<QSize, QSizeCompare>>::iterator itCam = resolutions.begin();
518   while (itCam != resolutions.end()) {
519     QStringList resolutionsList;
520     std::set<QSize, QSizeCompare>::iterator itRes = itCam->begin();
521     while (itRes != itCam->end()) {
522       _webcamResolutions[iCam].push_back(*itRes);
523       resolutionsList << QString("%1x%2").arg(itRes->width()).arg(itRes->height());
524       ++itRes;
525     }
526     settings.setValue(QString("WebcamSource/ResolutionsListForCam%1").arg(camList[iCam]), resolutionsList);
527     ++iCam;
528     ++itCam;
529   }
530 #else
531   Q_UNUSED(camList)
532 #endif
533 }
534 
webcamResolutions(int index)535 const QList<QSize> & WebcamSource::webcamResolutions(int index)
536 {
537   static const QList<QSize> empty;
538   return index >= 0 ? _webcamResolutions[index] : empty;
539 }
540 
clearSavedSettings()541 void WebcamSource::clearSavedSettings()
542 {
543   QSettings settings;
544   for (int i = 0; i < 50; ++i) {
545     settings.remove(QString("WebcamSource/ResolutionsListForCam%1").arg(i));
546     settings.remove(QString("WebcamSource/DefaultResolutionCam%1").arg(i));
547   }
548 }
549 
osName()550 QString WebcamSource::osName()
551 {
552 #ifdef _IS_FREEBSD_
553   return "FreeBSD";
554 #else
555   QFile file("/etc/os-release");
556   if (!file.open(QIODevice::ReadOnly)) {
557     qWarning("Warning: Cannot determine the os name (/etc/os-release file missing?)");
558     return "Unknown";
559   }
560   while (file.bytesAvailable()) {
561     QString line = file.readLine().trimmed();
562     if (line.startsWith("NAME=")) {
563       line.replace("NAME=", "");
564       if (line.size() && line[0] == '"') {
565         line.remove(0, 1);
566       }
567       if (line.size() && line[line.size() - 1] == '"') {
568         line.chop(0);
569       }
570       return line;
571     }
572   }
573   return "Unknown";
574 #endif
575 }
576 
captureIsValid(const cv::VideoCapture & capture,int index)577 bool WebcamSource::captureIsValid(const cv::VideoCapture & capture, int index)
578 {
579 #if defined(HAS_V4L2)
580   // WebcamSource::getWebcamList() already returns valid webcams only
581   Q_UNUSED(capture)
582   Q_UNUSED(index)
583   return true;
584 #elif defined(CVCAPTURE_HAS_BACKEND_METHOD)
585   Q_UNUSED(index)
586   return std::string("UNICAP") != capture.getBackendName().c_str();
587 #else
588   Q_UNUSED(capture)
589   QString os = osName();
590   return (os != "Fedora" && os != "FreeBSD") || !(index % 2);
591 #endif
592 }
593