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