qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
cameradevice.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2015-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <QApplication>
21 #include <QDebug>
22 #include <QDesktopWidget>
23 #include <QScreen>
24 extern "C" {
25 #pragma GCC diagnostic push
26 #pragma GCC diagnostic ignored "-Wold-style-cast"
27 #include <libavdevice/avdevice.h>
28 #include <libavformat/avformat.h>
29 #pragma GCC diagnostic pop
30 }
31 #include "cameradevice.h"
33 
34 // no longer needed when avformat version < 59 is no longer supported
35 using AvFindInputFormatRet = decltype(av_find_input_format(""));
36 
37 #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
38 #define USING_V4L 1
39 #else
40 #define USING_V4L 0
41 #endif
42 
43 #ifdef Q_OS_WIN
45 #endif
46 #if USING_V4L
48 #endif
49 #ifdef Q_OS_OSX
51 #endif
52 
77 static AvFindInputFormatRet idesktopFormat{nullptr};
78 static AvFindInputFormatRet iformat{nullptr};
79 
80 CameraDevice::CameraDevice(const QString& devName, AVFormatContext* context)
81  : devName{devName}
82  , context{context}
83  , refcount{1}
84 {
85 }
86 
87 CameraDevice* CameraDevice::open(QString devName, AVDictionary** options)
88 {
89  openDeviceLock.lock();
90  AVFormatContext* fctx = nullptr;
91  CameraDevice* dev = openDevices.value(devName);
92  int aduration;
93  std::string devString;
94  if (dev) {
95  goto out;
96  }
97 
98  AvFindInputFormatRet format;
99  if (devName.startsWith("x11grab#")) {
100  devName = devName.mid(8);
101  format = idesktopFormat;
102  } else if (devName.startsWith("gdigrab#")) {
103  devName = devName.mid(8);
104  format = idesktopFormat;
105  } else {
106  format = iformat;
107  }
108 
109  devString = devName.toStdString();
110  if (avformat_open_input(&fctx, devString.c_str(), format, options) < 0) {
111  goto out;
112  }
113 
114 // Fix avformat_find_stream_info hanging on garbage input
115 #if FF_API_PROBESIZE_32
116  aduration = fctx->max_analyze_duration2 = 0;
117 #else
118  aduration = fctx->max_analyze_duration = 0;
119 #endif
120 
121  if (avformat_find_stream_info(fctx, nullptr) < 0) {
122  avformat_close_input(&fctx);
123  goto out;
124  }
125 
126 #if FF_API_PROBESIZE_32
127  fctx->max_analyze_duration2 = aduration;
128 #else
129  fctx->max_analyze_duration = aduration;
130 #endif
131 
132  dev = new CameraDevice{devName, fctx};
133  openDevices[devName] = dev;
134 
135 out:
136  openDeviceLock.unlock();
137  return dev;
138 }
139 
153 {
154  if (!getDefaultInputFormat())
155  return nullptr;
156 
157  if (devName == "none") {
158  qDebug() << "Tried to open the null device";
159  return nullptr;
160  }
161 
162  float FPS = 5;
163  if (mode.FPS > 0.0f) {
164  FPS = mode.FPS;
165  } else {
166  qWarning() << "VideoMode could be invalid!";
167  }
168 
169  const std::string videoSize = QStringLiteral("%1x%2").arg(mode.width).arg(mode.height).toStdString();
170  const std::string framerate = QString{}.setNum(FPS).toStdString();
171 
172  AVDictionary* options = nullptr;
173  if (!iformat)
174  ;
175 #if USING_V4L
176  else if (devName.startsWith("x11grab#")) {
177  QSize screen;
178  if (mode.width && mode.height) {
179  screen.setWidth(mode.width);
180  screen.setHeight(mode.height);
181  } else {
182  QScreen* defaultScreen = QApplication::primaryScreen();
183  qreal pixRatio = defaultScreen->devicePixelRatio();
184 
185  screen = defaultScreen->size();
186  // Workaround https://trac.ffmpeg.org/ticket/4574 by choping 1 px bottom and right
187  // Actually, let's chop two pixels, toxav hates odd resolutions (off by one stride)
188  screen.setWidth((screen.width() * pixRatio) - 2);
189  screen.setHeight((screen.height() * pixRatio) - 2);
190  }
191  const std::string screenVideoSize = QStringLiteral("%1x%2").arg(screen.width()).arg(screen.height()).toStdString();
192  av_dict_set(&options, "video_size", screenVideoSize.c_str(), 0);
193  devName += QString("+%1,%2").arg(QString().setNum(mode.x), QString().setNum(mode.y));
194 
195  av_dict_set(&options, "framerate", framerate.c_str(), 0);
196  } else if (iformat->name == QString("video4linux2,v4l2") && mode) {
197  av_dict_set(&options, "video_size", videoSize.c_str(), 0);
198  av_dict_set(&options, "framerate", framerate.c_str(), 0);
199  const std::string pixelFormatStr = v4l2::getPixelFormatString(mode.pixel_format).toStdString();
200  // don't try to set a format string that doesn't exist
201  if (pixelFormatStr != "unknown" && pixelFormatStr != "invalid") {
202  const char* pixel_format = pixelFormatStr.c_str();
203  av_dict_set(&options, "pixel_format", pixel_format, 0);
204  }
205  }
206 #endif
207 #ifdef Q_OS_WIN
208  else if (devName.startsWith("gdigrab#")) {
209 
210  const std::string offsetX = QString().setNum(mode.x).toStdString();
211  const std::string offsetY = QString().setNum(mode.y).toStdString();
212  av_dict_set(&options, "framerate", framerate.c_str(), 0);
213  av_dict_set(&options, "offset_x", offsetX.c_str(), 0);
214  av_dict_set(&options, "offset_y", offsetY.c_str(), 0);
215  av_dict_set(&options, "video_size", videoSize.c_str(), 0);
216  } else if (iformat->name == QString("dshow") && mode) {
217  av_dict_set(&options, "video_size", videoSize.c_str(), 0);
218  av_dict_set(&options, "framerate", framerate.c_str(), 0);
219  }
220 #endif
221 #ifdef Q_OS_OSX
222  else if (iformat->name == QString("avfoundation")) {
223  if (mode) {
224  av_dict_set(&options, "video_size", videoSize.c_str(), 0);
225  av_dict_set(&options, "framerate", framerate.c_str(), 0);
226  } else if (devName.startsWith(avfoundation::CAPTURE_SCREEN)) {
227  av_dict_set(&options, "framerate", framerate.c_str(), 0);
228  av_dict_set_int(&options, "capture_cursor", 1, 0);
229  av_dict_set_int(&options, "capture_mouse_clicks", 1, 0);
230  }
231  }
232 #endif
233  else if (mode) {
234  qWarning().nospace() << "No known options for " << iformat->name << ", using defaults.";
235  Q_UNUSED(mode)
236  }
237 
238  CameraDevice* dev = open(devName, &options);
239  if (options) {
240  av_dict_free(&options);
241  }
242 
243  return dev;
244 }
245 
250 {
251  ++refcount;
252 }
253 
261 {
262  if (--refcount > 0)
263  return false;
264 
265  openDeviceLock.lock();
266  openDevices.remove(devName);
267  openDeviceLock.unlock();
268  avformat_close_input(&context);
269  delete this;
270  return true;
271 }
272 
278 QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
279 {
280  QVector<QPair<QString, QString>> devices;
281 
282  if (!getDefaultInputFormat())
283  return devices;
284 
285  // Alloc an input device context
286  AVFormatContext* s;
287  if (!(s = avformat_alloc_context()))
288  return devices;
289 
290  if (!iformat->priv_class || !AV_IS_INPUT_DEVICE(iformat->priv_class->category)) {
291  avformat_free_context(s);
292  return devices;
293  }
294 
295  s->iformat = iformat;
296  if (s->iformat->priv_data_size > 0) {
297  s->priv_data = av_mallocz(s->iformat->priv_data_size);
298  if (!s->priv_data) {
299  avformat_free_context(s);
300  return devices;
301  }
302  if (s->iformat->priv_class) {
303  *static_cast<const AVClass**>(s->priv_data) = s->iformat->priv_class;
304  av_opt_set_defaults(s->priv_data);
305  }
306  } else {
307  s->priv_data = nullptr;
308  }
309 
310  // List the devices for this context
311  AVDeviceInfoList* devlist = nullptr;
312  AVDictionary* tmp = nullptr;
313  av_dict_copy(&tmp, nullptr, 0);
314  if (av_opt_set_dict2(s, &tmp, AV_OPT_SEARCH_CHILDREN) < 0) {
315  av_dict_free(&tmp);
316  avformat_free_context(s);
317  return devices;
318  }
319  avdevice_list_devices(s, &devlist);
320  av_dict_free(&tmp);
321  avformat_free_context(s);
322  if (!devlist) {
323  qWarning() << "avdevice_list_devices failed";
324  return devices;
325  }
326 
327  // Convert the list to a QVector
328  devices.resize(devlist->nb_devices);
329  for (int i = 0; i < devlist->nb_devices; ++i) {
330  AVDeviceInfo* dev = devlist->devices[i];
331  devices[i].first = dev->device_name;
332  devices[i].second = dev->device_description;
333  }
334  avdevice_free_list_devices(&devlist);
335  return devices;
336 }
337 
343 QVector<QPair<QString, QString>> CameraDevice::getDeviceList()
344 {
345  QVector<QPair<QString, QString>> devices;
346 
347  devices.append({"none", QObject::tr("None", "No camera device set")});
348 
349  if (!getDefaultInputFormat())
350  return devices;
351 
352  if (!iformat)
353  ;
354 #ifdef Q_OS_WIN
355  else if (iformat->name == QString("dshow"))
356  devices += DirectShow::getDeviceList();
357 #endif
358 #if USING_V4L
359  else if (iformat->name == QString("video4linux2,v4l2"))
360  devices += v4l2::getDeviceList();
361 #endif
362 #ifdef Q_OS_OSX
363  else if (iformat->name == QString("avfoundation"))
364  devices += avfoundation::getDeviceList();
365 #endif
366  else
367  devices += getRawDeviceListGeneric();
368 
369  if (idesktopFormat) {
370  if (idesktopFormat->name == QString("x11grab")) {
371  QString dev = "x11grab#";
372  QByteArray display = qgetenv("DISPLAY");
373 
374  if (display.isNull())
375  dev += ":0";
376  else
377  dev += display.constData();
378 
379  devices.push_back(QPair<QString, QString>{
380  dev, QObject::tr("Desktop", "Desktop as a camera input for screen sharing")});
381  }
382  if (idesktopFormat->name == QString("gdigrab"))
383  devices.push_back(QPair<QString, QString>{
384  "gdigrab#desktop",
385  QObject::tr("Desktop", "Desktop as a camera input for screen sharing")});
386  }
387 
388  return devices;
389 }
390 
397 {
398  QString defaultdev = Settings::getInstance().getVideoDev();
399 
400  if (!getDefaultInputFormat())
401  return defaultdev;
402 
403  QVector<QPair<QString, QString>> devlist = getDeviceList();
404  for (const QPair<QString, QString>& device : devlist)
405  if (defaultdev == device.first)
406  return defaultdev;
407 
408  if (devlist.isEmpty())
409  return defaultdev;
410 
411  return devlist[0].first;
412 }
413 
419 bool CameraDevice::isScreen(const QString& devName)
420 {
421  return devName.startsWith("x11grab") || devName.startsWith("gdigrab");
422 }
423 
428 QVector<VideoMode> CameraDevice::getScreenModes()
429 {
430  QList<QScreen*> screens = QApplication::screens();
431  QVector<VideoMode> result;
432 
433  std::for_each(screens.begin(), screens.end(), [&result](QScreen* s) {
434  QRect rect = s->geometry();
435  QPoint p = rect.topLeft();
436  qreal pixRatio = s->devicePixelRatio();
437 
438  VideoMode mode(rect.width() * pixRatio, rect.height() * pixRatio, p.x() * pixRatio,
439  p.y() * pixRatio);
440  result.push_back(mode);
441  });
442 
443  return result;
444 }
445 
451 QVector<VideoMode> CameraDevice::getVideoModes(QString devName)
452 {
453  Q_UNUSED(devName)
454 
455  if (!iformat)
456  ;
457  else if (isScreen(devName))
458  return getScreenModes();
459 #ifdef Q_OS_WIN
460  else if (iformat->name == QString("dshow"))
462 #endif
463 #if USING_V4L
464  else if (iformat->name == QString("video4linux2,v4l2"))
466 #endif
467 #ifdef Q_OS_OSX
468  else if (iformat->name == QString("avfoundation"))
470 #endif
471  else
472  qWarning() << "Video mode listing not implemented for input " << iformat->name;
473 
474  return {};
475 }
476 
482 QString CameraDevice::getPixelFormatString(uint32_t pixel_format)
483 {
484 #if USING_V4L
485  return v4l2::getPixelFormatString(pixel_format);
486 #else
487  return QString("unknown");
488 #endif
489 }
490 
498 bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b)
499 {
500 #if USING_V4L
501  return v4l2::betterPixelFormat(a, b);
502 #else
503  return false;
504 #endif
505 }
506 
512 {
513  QMutexLocker locker(&iformatLock);
514  if (iformat)
515  return true;
516 
517  avdevice_register_all();
518 
519 // Desktop capture input formats
520 #if USING_V4L
521  idesktopFormat = av_find_input_format("x11grab");
522 #endif
523 #ifdef Q_OS_WIN
524  idesktopFormat = av_find_input_format("gdigrab");
525 #endif
526 
527 // Webcam input formats
528 #if USING_V4L
529  if ((iformat = av_find_input_format("v4l2")))
530  return true;
531 #endif
532 
533 #ifdef Q_OS_WIN
534  if ((iformat = av_find_input_format("dshow")))
535  return true;
536  if ((iformat = av_find_input_format("vfwcap")))
537  return true;
538 #endif
539 
540 #ifdef Q_OS_OSX
541  if ((iformat = av_find_input_format("avfoundation")))
542  return true;
543  if ((iformat = av_find_input_format("qtkit")))
544  return true;
545 #endif
546 
547  qWarning() << "No valid input format found";
548  return false;
549 }
v4l2::getPixelFormatString
QString getPixelFormatString(uint32_t pixel_format)
Definition: v4l2.cpp:216
CameraDevice::open
void open()
Opens the device again. Never fails.
Definition: cameradevice.cpp:249
avfoundation::CAPTURE_SCREEN
const QString CAPTURE_SCREEN
Definition: avfoundation.h:31
CameraDevice::getDeviceList
static QVector< QPair< QString, QString > > getDeviceList()
Get device list with desciption.
Definition: cameradevice.cpp:343
CameraDevice::refcount
std::atomic_int refcount
Number of times the device was opened.
Definition: cameradevice.h:64
settings.h
VideoMode::x
int x
Definition: videomode.h:28
AvFindInputFormatRet
decltype(av_find_input_format("")) AvFindInputFormatRet
Definition: cameradevice.cpp:35
CameraDevice::getRawDeviceListGeneric
static QVector< QPair< QString, QString > > getRawDeviceListGeneric()
Get raw device list.
Definition: cameradevice.cpp:278
CameraDevice::getScreenModes
static QVector< VideoMode > getScreenModes()
Get list of resolutions and position of screens.
Definition: cameradevice.cpp:428
CameraDevice::getVideoModes
static QVector< VideoMode > getVideoModes(QString devName)
Get the list of video modes for a device.
Definition: cameradevice.cpp:451
VideoMode::pixel_format
uint32_t pixel_format
Definition: videomode.h:30
QList
Definition: friendlist.h:25
VideoMode::height
int height
Displayed video resolution (NOT frame resolution).
Definition: videomode.h:27
DirectShow::getDeviceModes
QVector< VideoMode > getDeviceModes(QString devName)
Definition: directshow.cpp:179
cameradevice.h
DirectShow::getDeviceList
QVector< QPair< QString, QString > > getDeviceList()
Definition: directshow.cpp:52
v4l2.h
avfoundation::getDeviceList
QVector< QPair< QString, QString > > getDeviceList()
CameraDevice::betterPixelFormat
static bool betterPixelFormat(uint32_t a, uint32_t b)
Compare two pixel formats.
Definition: cameradevice.cpp:498
CameraDevice::getDefaultInputFormat
static bool getDefaultInputFormat()
Sets CameraDevice::iformat to default.
Definition: cameradevice.cpp:511
QHash< QString, CameraDevice * >
CameraDevice::context
AVFormatContext * context
Context of the open device, must always be valid.
Definition: cameradevice.h:61
CameraDevice::iformatLock
static QMutex iformatLock
Definition: cameradevice.h:66
Settings::getInstance
static Settings & getInstance()
Returns the singleton instance.
Definition: settings.cpp:88
directshow.h
VideoMode::width
int width
Definition: videomode.h:27
VideoMode::y
int y
Coordinates of upper-left corner.
Definition: videomode.h:28
v4l2::getDeviceList
QVector< QPair< QString, QString > > getDeviceList()
Definition: v4l2.cpp:184
v4l2::getDeviceModes
QVector< VideoMode > getDeviceModes(QString devName)
Definition: v4l2.cpp:125
avfoundation::getDeviceModes
QVector< VideoMode > getDeviceModes(QString devName)
CameraDevice::openDeviceLock
static QMutex openDeviceLock
Definition: cameradevice.h:66
VideoMode
Describes a video mode supported by a device.
Definition: videomode.h:25
CameraDevice::close
bool close()
Closes the device. Never fails.
Definition: cameradevice.cpp:260
VideoMode::FPS
float FPS
Frames per second supported by the device at this resolution.
Definition: videomode.h:29
CameraDevice::devName
const QString devName
Short name of the device.
Definition: cameradevice.h:60
CameraDevice
Definition: cameradevice.h:35
avfoundation.h
CameraDevice::getPixelFormatString
static QString getPixelFormatString(uint32_t pixel_format)
Get the name of the pixel format of a video mode.
Definition: cameradevice.cpp:482
CameraDevice::CameraDevice
CameraDevice(const QString &devName, AVFormatContext *context)
Definition: cameradevice.cpp:80
Settings::getVideoDev
QString getVideoDev() const override
Definition: settings.cpp:1741
CameraDevice::isScreen
static bool isScreen(const QString &devName)
Checks if a device name specifies a display.
Definition: cameradevice.cpp:419
CameraDevice::getDefaultDeviceName
static QString getDefaultDeviceName()
Get the default device name.
Definition: cameradevice.cpp:396
v4l2::betterPixelFormat
bool betterPixelFormat(uint32_t a, uint32_t b)
Definition: v4l2.cpp:225
CameraDevice::openDevices
static QHash< QString, CameraDevice * > openDevices
Definition: cameradevice.h:65