qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
camerasource.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 extern "C" {
21 #pragma GCC diagnostic push
22 #pragma GCC diagnostic ignored "-Wold-style-cast"
23 #include <libavcodec/avcodec.h>
24 #include <libavdevice/avdevice.h>
25 #include <libavformat/avformat.h>
26 #include <libswscale/swscale.h>
27 #pragma GCC diagnostic pop
28 }
29 #include "cameradevice.h"
30 #include "camerasource.h"
31 #include "videoframe.h"
33 #include <QDebug>
34 #include <QReadLocker>
35 #include <QWriteLocker>
36 #include <QtConcurrent/QtConcurrentRun>
37 #include <functional>
38 #include <memory>
39 
94 
96  : deviceThread{new QThread}
97  , deviceName{"none"}
98  , device{nullptr}
99  , mode(VideoMode())
100  // clang-format off
101  , cctx{nullptr}
102 #if LIBAVCODEC_VERSION_INT < 3747941
103  , cctxOrig{nullptr}
104 #endif
105  , videoStreamIndex{-1}
106  , _isNone{true}
107  , subscriptions{0}
108 {
109  qRegisterMetaType<VideoMode>("VideoMode");
110  deviceThread->setObjectName("Device thread");
111  deviceThread->start();
112  moveToThread(deviceThread);
113 
114  subscriptions = 0;
115 
116 // TODO(sudden6): remove code when minimum ffmpeg version >= 4.0
117 #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
118  av_register_all();
119 #endif
120  avdevice_register_all();
121 }
122 
123 // clang-format on
124 
129 {
130  if (!instance)
131  instance = new CameraSource();
132  return *instance;
133 }
134 
136 {
137  delete instance;
138  instance = nullptr;
139 }
140 
146 {
148  bool isScreen = CameraDevice::isScreen(deviceName);
149  VideoMode mode = VideoMode(Settings::getInstance().getScreenRegion());
150  if (!isScreen) {
151  mode = VideoMode(Settings::getInstance().getCamVideoRes());
153  }
154 
156 }
157 
162 void CameraSource::setupDevice(const QString& DeviceName, const VideoMode& Mode)
163 {
164  if (QThread::currentThread() != deviceThread) {
165  QMetaObject::invokeMethod(this, "setupDevice", Q_ARG(const QString&, DeviceName),
166  Q_ARG(const VideoMode&, Mode));
167  return;
168  }
169 
170  QWriteLocker locker{&deviceMutex};
171 
172  if (DeviceName == deviceName && Mode == mode) {
173  return;
174  }
175 
176  if (subscriptions) {
177  // To force close, ignoring optimization
178  int subs = subscriptions;
179  subscriptions = 0;
180  closeDevice();
181  subscriptions = subs;
182  }
183 
184  deviceName = DeviceName;
185  mode = Mode;
186  _isNone = (deviceName == "none");
187 
188  if (subscriptions && !_isNone) {
189  openDevice();
190  }
191 }
192 
194 {
195  return _isNone;
196 }
197 
199 {
200  QWriteLocker locker{&streamMutex};
201  QWriteLocker locker2{&deviceMutex};
202 
203  // Stop the device thread
204  deviceThread->exit(0);
205  deviceThread->wait();
206  delete deviceThread;
207 
208  if (_isNone) {
209  return;
210  }
211 
212  // Free all remaining VideoFrame
213  VideoFrame::untrackFrames(id, true);
214 
215  if (cctx) {
216  avcodec_free_context(&cctx);
217  }
218 #if LIBAVCODEC_VERSION_INT < 3747941
219  if (cctxOrig) {
220  avcodec_close(cctxOrig);
221  }
222 #endif
223 
224  if (device) {
225  for (int i = 0; i < subscriptions; ++i)
226  device->close();
227 
228  device = nullptr;
229  }
230 
231  locker.unlock();
232 
233  // Synchronize with our stream thread
234  while (streamFuture.isRunning())
235  QThread::yieldCurrentThread();
236 }
237 
239 {
240  QWriteLocker locker{&deviceMutex};
241 
242  ++subscriptions;
243  openDevice();
244 }
245 
247 {
248  QWriteLocker locker{&deviceMutex};
249 
250  --subscriptions;
251  if (subscriptions == 0) {
252  closeDevice();
253  }
254 }
255 
261 {
262  if (QThread::currentThread() != deviceThread) {
263  QMetaObject::invokeMethod(this, "openDevice");
264  return;
265  }
266 
267  QWriteLocker locker{&streamMutex};
268  if (subscriptions == 0) {
269  return;
270  }
271 
272  qDebug() << "Opening device" << deviceName << "subscriptions:" << subscriptions;
273 
274  if (device) {
275  device->open();
276  emit openFailed();
277  return;
278  }
279 
280  // We need to create a new CameraDevice
282 
283  if (!device) {
284  qWarning() << "Failed to open device!";
285  emit openFailed();
286  return;
287  }
288 
289  // We need to open the device as many time as we already have subscribers,
290  // otherwise the device could get closed while we still have subscribers
291  for (int i = 0; i < subscriptions; ++i) {
292  device->open();
293  }
294 
295  // Find the first video stream, if any
296  for (unsigned i = 0; i < device->context->nb_streams; ++i) {
297  AVMediaType type;
298 #if LIBAVCODEC_VERSION_INT < 3747941
299  type = device->context->streams[i]->codec->codec_type;
300 #else
301  type = device->context->streams[i]->codecpar->codec_type;
302 #endif
303  if (type == AVMEDIA_TYPE_VIDEO) {
304  videoStreamIndex = i;
305  break;
306  }
307  }
308 
309  if (videoStreamIndex == -1) {
310  qWarning() << "Video stream not found";
311  emit openFailed();
312  return;
313  }
314 
315  AVCodecID codecId;
316 #if LIBAVCODEC_VERSION_INT < 3747941
317  cctxOrig = device->context->streams[videoStreamIndex]->codec;
318  codecId = cctxOrig->codec_id;
319 #else
320  // Get the stream's codec's parameters and find a matching decoder
321  AVCodecParameters* cparams = device->context->streams[videoStreamIndex]->codecpar;
322  codecId = cparams->codec_id;
323 #endif
324  const AVCodec* codec = avcodec_find_decoder(codecId);
325  if (!codec) {
326  qWarning() << "Codec not found";
327  emit openFailed();
328  return;
329  }
330 
331 
332 #if LIBAVCODEC_VERSION_INT < 3747941
333  // Copy context, since we apparently aren't allowed to use the original
334  cctx = avcodec_alloc_context3(codec);
335  if (avcodec_copy_context(cctx, cctxOrig) != 0) {
336  qWarning() << "Can't copy context";
337  emit openFailed();
338  return;
339  }
340 
341  cctx->refcounted_frames = 1;
342 #else
343  // Create a context for our codec, using the existing parameters
344  cctx = avcodec_alloc_context3(codec);
345  if (avcodec_parameters_to_context(cctx, cparams) < 0) {
346  qWarning() << "Can't create AV context from parameters";
347  emit openFailed();
348  return;
349  }
350 #endif
351 
352  // Open codec
353  if (avcodec_open2(cctx, codec, nullptr) < 0) {
354  qWarning() << "Can't open codec";
355  avcodec_free_context(&cctx);
356  emit openFailed();
357  return;
358  }
359 
360  if (streamFuture.isRunning())
361  qDebug() << "The stream thread is already running! Keeping the current one open.";
362  else
363  streamFuture = QtConcurrent::run(std::bind(&CameraSource::stream, this));
364 
365  // Synchronize with our stream thread
366  while (!streamFuture.isRunning())
367  QThread::yieldCurrentThread();
368 
369  emit deviceOpened();
370 }
371 
377 {
378  if (QThread::currentThread() != deviceThread) {
379  QMetaObject::invokeMethod(this, "closeDevice");
380  return;
381  }
382 
383  QWriteLocker locker{&streamMutex};
384  if (subscriptions != 0) {
385  return;
386  }
387 
388  qDebug() << "Closing device" << deviceName << "subscriptions:" << subscriptions;
389 
390  // Free all remaining VideoFrame
391  VideoFrame::untrackFrames(id, true);
392 
393  // Free our resources and close the device
394  videoStreamIndex = -1;
395  avcodec_free_context(&cctx);
396 #if LIBAVCODEC_VERSION_INT < 3747941
397  avcodec_close(cctxOrig);
398  cctxOrig = nullptr;
399 #endif
400  while (device && !device->close()) {
401  }
402  device = nullptr;
403 }
404 
410 {
411  auto streamLoop = [=]() {
412  AVPacket packet;
413  if (av_read_frame(device->context, &packet) != 0) {
414  return;
415  }
416 
417 #if LIBAVCODEC_VERSION_INT < 3747941
418  AVFrame* frame = av_frame_alloc();
419  if (!frame) {
420  return;
421  }
422 
423  // Only keep packets from the right stream;
424  if (packet.stream_index == videoStreamIndex) {
425  // Decode video frame
426  int frameFinished;
427  avcodec_decode_video2(cctx, frame, &frameFinished, &packet);
428  if (!frameFinished) {
429  return;
430  }
431 
432  VideoFrame* vframe = new VideoFrame(id, frame);
433  emit frameAvailable(vframe->trackFrame());
434  }
435 #else
436 
437  // Forward packets to the decoder and grab the decoded frame
438  bool isVideo = packet.stream_index == videoStreamIndex;
439  bool readyToRecive = isVideo && !avcodec_send_packet(cctx, &packet);
440 
441  if (readyToRecive) {
442  AVFrame* frame = av_frame_alloc();
443  if (frame && !avcodec_receive_frame(cctx, frame)) {
444  VideoFrame* vframe = new VideoFrame(id, frame);
445  emit frameAvailable(vframe->trackFrame());
446  } else {
447  av_frame_free(&frame);
448  }
449  }
450 #endif
451 
452  av_packet_unref(&packet);
453  };
454 
455  forever
456  {
457  QReadLocker locker{&streamMutex};
458 
459  // Exit if device is no longer valid
460  if (!device) {
461  break;
462  }
463 
464  streamLoop();
465  }
466 }
CameraDevice::open
void open()
Opens the device again. Never fails.
Definition: cameradevice.cpp:249
CameraSource::setupDefault
void setupDefault()
Setup default device.
Definition: camerasource.cpp:145
CameraSource::unsubscribe
void unsubscribe() override
Stop emitting frameAvailable signals, and free associated resources if necessary.
Definition: camerasource.cpp:246
CameraSource::instance
static CameraSource * instance
Definition: camerasource.h:82
CameraSource::cctx
AVCodecContext * cctx
Codec context of the camera's selected video stream.
Definition: camerasource.h:71
Settings::getCamVideoFPS
float getCamVideoFPS() const override
Definition: settings.cpp:1845
settings.h
VideoSource::frameAvailable
void frameAvailable(std::shared_ptr< VideoFrame > frame)
Emitted when new frame available to use.
CameraSource::~CameraSource
~CameraSource()
Definition: camerasource.cpp:198
CameraSource::deviceName
QString deviceName
Short name of the device for CameraDevice's open(QString)
Definition: camerasource.h:68
CameraSource
This class is a wrapper to share a camera's captured video frames.
Definition: camerasource.h:34
videoframe.h
CameraSource::openDevice
void openDevice()
Opens the video device and starts streaming.
Definition: camerasource.cpp:260
CameraSource::isNone
bool isNone() const
Definition: camerasource.cpp:193
CameraSource::mode
VideoMode mode
What mode we tried to open the device in, all zeros means default mode.
Definition: camerasource.h:70
CameraSource::cctxOrig
AVCodecContext * cctxOrig
Codec context of the camera's selected video stream.
Definition: camerasource.h:73
VideoFrame::trackFrame
std::shared_ptr< VideoFrame > trackFrame()
Causes the VideoFrame class to maintain an internal reference for the frame.
Definition: videoframe.cpp:200
CameraSource::deviceOpened
void deviceOpened()
CameraSource::setupDevice
void setupDevice(const QString &deviceName, const VideoMode &mode)
Change the device and mode.
Definition: camerasource.cpp:162
CameraSource::videoStreamIndex
int videoStreamIndex
A camera can have multiple streams, this is the one we're decoding.
Definition: camerasource.h:74
CameraSource::device
CameraDevice * device
Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences when become...
Definition: camerasource.h:69
cameradevice.h
camerasource.h
VideoFrame
An ownernship and management class for AVFrames.
Definition: videoframe.h:55
CameraSource::stream
void stream()
Blocking. Decodes video stream and emits new frames.
Definition: camerasource.cpp:409
CameraSource::deviceThread
QThread * deviceThread
Definition: camerasource.h:66
CameraDevice::context
AVFormatContext * context
Context of the open device, must always be valid.
Definition: cameradevice.h:61
CameraSource::deviceMutex
QReadWriteLock deviceMutex
Definition: camerasource.h:76
CameraSource::destroyInstance
static void destroyInstance()
Definition: camerasource.cpp:135
Settings::getInstance
static Settings & getInstance()
Returns the singleton instance.
Definition: settings.cpp:88
CameraSource::streamFuture
QFuture< void > streamFuture
Future of the streaming thread.
Definition: camerasource.h:65
CameraSource::getInstance
static CameraSource & getInstance()
Returns the singleton instance.
Definition: camerasource.cpp:128
CameraSource::CameraSource
CameraSource()
Definition: camerasource.cpp:95
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
CameraSource::openFailed
void openFailed()
VideoMode::FPS
float FPS
Frames per second supported by the device at this resolution.
Definition: videomode.h:29
CameraSource::closeDevice
void closeDevice()
Closes the video device and stops streaming.
Definition: camerasource.cpp:376
CameraSource::streamMutex
QReadWriteLock streamMutex
Definition: camerasource.h:77
CameraSource::subscribe
void subscribe() override
If subscribe sucessfully opens the source, it will start emitting frameAvailable signals.
Definition: camerasource.cpp:238
CameraSource::subscriptions
std::atomic_int subscriptions
Remember how many times we subscribed for RAII.
Definition: camerasource.h:80
VideoFrame::untrackFrames
static void untrackFrames(const IDType &sourceID, bool releaseFrames=false)
Untracks all the frames for the given VideoSource, releasing them if specified.
Definition: videoframe.cpp:236
CameraSource::_isNone
std::atomic_bool _isNone
Definition: camerasource.h:79
CameraDevice::open
static CameraDevice * open(QString devName, VideoMode mode=VideoMode())
Opens a device.
Definition: cameradevice.cpp:152
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