2015-05-14 10:46:28 +08:00
|
|
|
/*
|
2016-11-28 21:33:38 +08:00
|
|
|
Copyright © 2015 by The qTox Project Contributors
|
2015-06-06 09:40:08 +08:00
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
qTox is libre software: you can redistribute it and/or modify
|
2015-05-14 10:46:28 +08:00
|
|
|
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.
|
2015-06-06 09:40:08 +08:00
|
|
|
|
|
|
|
qTox is distributed in the hope that it will be useful,
|
2015-05-14 10:46:28 +08:00
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-06-06 09:40:08 +08:00
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
2015-05-14 10:46:28 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavdevice/avdevice.h>
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
}
|
2017-02-26 19:52:45 +08:00
|
|
|
#include "cameradevice.h"
|
|
|
|
#include "camerasource.h"
|
|
|
|
#include "videoframe.h"
|
|
|
|
#include "src/persistence/settings.h"
|
2015-05-14 10:46:28 +08:00
|
|
|
#include <QDebug>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QReadLocker>
|
|
|
|
#include <QWriteLocker>
|
2015-05-14 10:46:28 +08:00
|
|
|
#include <QtConcurrent/QtConcurrentRun>
|
|
|
|
#include <functional>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <memory>
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @class CameraSource
|
|
|
|
* @brief This class is a wrapper to share a camera's captured video frames
|
|
|
|
*
|
|
|
|
* It allows objects to suscribe and unsuscribe to the stream, starting
|
|
|
|
* the camera and streaming new video frames only when needed.
|
|
|
|
* This is a singleton, since we can only capture from one
|
|
|
|
* camera at the same time without thread-safety issues.
|
|
|
|
* The source is lazy in the sense that it will only keep the video
|
|
|
|
* device open as long as there are subscribers, the source can be
|
|
|
|
* open but the device closed if there are zero subscribers.
|
|
|
|
*/
|
2016-07-27 06:18:57 +08:00
|
|
|
|
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @var QVector<std::weak_ptr<VideoFrame>> CameraSource::freelist
|
|
|
|
* @brief Frames that need freeing before we can safely close the device
|
|
|
|
*
|
|
|
|
* @var QFuture<void> CameraSource::streamFuture
|
|
|
|
* @brief Future of the streaming thread
|
|
|
|
*
|
|
|
|
* @var QString CameraSource::deviceName
|
|
|
|
* @brief Short name of the device for CameraDevice's open(QString)
|
|
|
|
*
|
|
|
|
* @var CameraDevice* CameraSource::device
|
2017-02-26 19:52:45 +08:00
|
|
|
* @brief Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences
|
|
|
|
* when becomes null.
|
2016-08-01 16:22:17 +08:00
|
|
|
*
|
|
|
|
* @var VideoMode CameraSource::mode
|
|
|
|
* @brief What mode we tried to open the device in, all zeros means default mode
|
|
|
|
*
|
|
|
|
* @var AVCodecContext* CameraSource::cctx
|
|
|
|
* @brief Codec context of the camera's selected video stream
|
|
|
|
*
|
|
|
|
* @var AVCodecContext* CameraSource::cctxOrig
|
|
|
|
* @brief Codec context of the camera's selected video stream
|
2017-02-16 07:38:14 +08:00
|
|
|
* @deprecated
|
2016-08-01 16:22:17 +08:00
|
|
|
*
|
|
|
|
* @var int CameraSource::videoStreamIndex
|
|
|
|
* @brief A camera can have multiple streams, this is the one we're decoding
|
|
|
|
*
|
|
|
|
* @var QMutex CameraSource::biglock
|
|
|
|
* @brief True when locked. Faster than mutexes for video decoding.
|
|
|
|
*
|
|
|
|
* @var QMutex CameraSource::freelistLock
|
|
|
|
* @brief True when locked. Faster than mutexes for video decoding.
|
|
|
|
*
|
|
|
|
* @var std::atomic_bool CameraSource::streamBlocker
|
|
|
|
* @brief Holds the streaming thread still when true
|
|
|
|
*
|
|
|
|
* @var std::atomic_int CameraSource::subscriptions
|
|
|
|
* @brief Remember how many times we subscribed for RAII
|
|
|
|
*/
|
2016-07-27 06:18:57 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
CameraSource* CameraSource::instance{nullptr};
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
CameraSource::CameraSource()
|
2017-06-26 18:58:22 +08:00
|
|
|
: deviceThread{new QThread}
|
|
|
|
, deviceName{"none"}
|
2017-02-26 19:52:45 +08:00
|
|
|
, device{nullptr}
|
|
|
|
, mode(VideoMode())
|
2017-06-01 15:50:59 +08:00
|
|
|
// clang-format off
|
2017-02-26 19:52:45 +08:00
|
|
|
, cctx{nullptr}
|
2017-02-16 07:38:14 +08:00
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
2017-02-26 19:52:45 +08:00
|
|
|
, cctxOrig{nullptr}
|
2017-02-16 07:38:14 +08:00
|
|
|
#endif
|
2017-02-26 19:52:45 +08:00
|
|
|
, videoStreamIndex{-1}
|
2017-06-26 17:57:46 +08:00
|
|
|
, _isNone{true}
|
2017-02-26 19:52:45 +08:00
|
|
|
, subscriptions{0}
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2017-06-26 19:05:26 +08:00
|
|
|
qRegisterMetaType<VideoMode>("VideoMode");
|
2017-06-26 18:58:22 +08:00
|
|
|
deviceThread->setObjectName("Device thread");
|
|
|
|
deviceThread->start();
|
|
|
|
moveToThread(deviceThread);
|
|
|
|
|
2015-10-11 14:38:44 +08:00
|
|
|
subscriptions = 0;
|
2018-04-24 01:31:58 +08:00
|
|
|
|
|
|
|
// TODO(sudden6): remove code when minimum ffmpeg version >= 4.0
|
|
|
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
2015-06-26 23:38:53 +08:00
|
|
|
av_register_all();
|
2018-04-24 01:31:58 +08:00
|
|
|
#endif
|
2015-06-26 23:38:53 +08:00
|
|
|
avdevice_register_all();
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2017-06-01 15:41:47 +08:00
|
|
|
// clang-format on
|
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @brief Returns the singleton instance.
|
|
|
|
*/
|
2015-06-26 23:38:53 +08:00
|
|
|
CameraSource& CameraSource::getInstance()
|
2015-05-16 10:01:38 +08:00
|
|
|
{
|
2015-06-26 23:38:53 +08:00
|
|
|
if (!instance)
|
|
|
|
instance = new CameraSource();
|
|
|
|
return *instance;
|
2015-05-16 10:01:38 +08:00
|
|
|
}
|
|
|
|
|
2015-06-27 01:04:53 +08:00
|
|
|
void CameraSource::destroyInstance()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (instance) {
|
2015-06-27 01:04:53 +08:00
|
|
|
delete instance;
|
|
|
|
instance = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2017-06-26 17:57:46 +08:00
|
|
|
* @brief Setup default device
|
2016-08-01 16:22:17 +08:00
|
|
|
* @note If a device is already open, the source will seamlessly switch to the new device.
|
|
|
|
*/
|
2017-06-26 17:57:46 +08:00
|
|
|
void CameraSource::setupDefault()
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2017-06-26 18:27:06 +08:00
|
|
|
QString deviceName = CameraDevice::getDefaultDeviceName();
|
2016-06-23 07:11:02 +08:00
|
|
|
bool isScreen = CameraDevice::isScreen(deviceName);
|
|
|
|
VideoMode mode = VideoMode(Settings::getInstance().getScreenRegion());
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isScreen) {
|
2016-06-23 07:11:02 +08:00
|
|
|
mode = VideoMode(Settings::getInstance().getCamVideoRes());
|
|
|
|
mode.FPS = Settings::getInstance().getCamVideoFPS();
|
|
|
|
}
|
|
|
|
|
2017-06-26 17:57:46 +08:00
|
|
|
setupDevice(deviceName, mode);
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
|
|
|
|
2017-06-26 17:57:46 +08:00
|
|
|
/**
|
|
|
|
* @brief Change the device and mode.
|
|
|
|
* @note If a device is already open, the source will seamlessly switch to the new device.
|
|
|
|
*/
|
|
|
|
void CameraSource::setupDevice(const QString& DeviceName, const VideoMode& Mode)
|
2015-06-26 23:38:53 +08:00
|
|
|
{
|
2017-06-26 19:05:26 +08:00
|
|
|
if (QThread::currentThread() != deviceThread) {
|
|
|
|
QMetaObject::invokeMethod(this, "setupDevice", Q_ARG(const QString&, DeviceName),
|
|
|
|
Q_ARG(const VideoMode&, Mode));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker{&deviceMutex};
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (DeviceName == deviceName && Mode == mode) {
|
2015-06-26 23:38:53 +08:00
|
|
|
return;
|
2015-10-24 11:19:23 +08:00
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2017-06-27 05:32:27 +08:00
|
|
|
if (subscriptions) {
|
|
|
|
// To force close, ignoring optimization
|
|
|
|
int subs = subscriptions;
|
|
|
|
subscriptions = 0;
|
2015-06-26 23:38:53 +08:00
|
|
|
closeDevice();
|
2017-06-27 05:32:27 +08:00
|
|
|
subscriptions = subs;
|
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
|
|
|
|
deviceName = DeviceName;
|
|
|
|
mode = Mode;
|
2017-06-26 17:57:46 +08:00
|
|
|
_isNone = (deviceName == "none");
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2017-06-27 05:32:27 +08:00
|
|
|
if (subscriptions && !_isNone) {
|
2015-06-26 23:38:53 +08:00
|
|
|
openDevice();
|
2017-06-27 05:32:27 +08:00
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
2015-06-22 20:59:55 +08:00
|
|
|
|
2017-06-26 17:57:46 +08:00
|
|
|
bool CameraSource::isNone() const
|
2015-09-28 07:04:39 +08:00
|
|
|
{
|
2017-06-26 17:57:46 +08:00
|
|
|
return _isNone;
|
2015-09-28 07:04:39 +08:00
|
|
|
}
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
CameraSource::~CameraSource()
|
|
|
|
{
|
2016-08-04 23:18:37 +08:00
|
|
|
QWriteLocker locker{&streamMutex};
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker2{&deviceMutex};
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-09-27 13:42:48 +08:00
|
|
|
// Stop the device thread
|
|
|
|
deviceThread->exit(0);
|
|
|
|
deviceThread->wait();
|
|
|
|
delete deviceThread;
|
|
|
|
|
2017-06-26 17:57:46 +08:00
|
|
|
if (_isNone) {
|
2015-06-22 20:59:55 +08:00
|
|
|
return;
|
2016-08-04 23:18:37 +08:00
|
|
|
}
|
2015-06-22 20:59:55 +08:00
|
|
|
|
2015-05-16 10:01:38 +08:00
|
|
|
// Free all remaining VideoFrame
|
2016-05-02 16:22:08 +08:00
|
|
|
VideoFrame::untrackFrames(id, true);
|
2015-05-16 10:01:38 +08:00
|
|
|
|
2017-02-16 07:38:14 +08:00
|
|
|
if (cctx) {
|
2015-05-14 10:46:28 +08:00
|
|
|
avcodec_free_context(&cctx);
|
2017-02-16 07:38:14 +08:00
|
|
|
}
|
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
|
|
|
if (cctxOrig) {
|
2015-05-16 10:01:38 +08:00
|
|
|
avcodec_close(cctxOrig);
|
2017-02-16 07:38:14 +08:00
|
|
|
}
|
|
|
|
#endif
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (device) {
|
2016-10-30 06:48:03 +08:00
|
|
|
for (int i = 0; i < subscriptions; ++i)
|
2015-10-24 02:30:22 +08:00
|
|
|
device->close();
|
2016-07-07 17:58:28 +08:00
|
|
|
|
2015-10-24 02:30:22 +08:00
|
|
|
device = nullptr;
|
|
|
|
}
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2016-08-04 23:18:37 +08:00
|
|
|
locker.unlock();
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// Synchronize with our stream thread
|
|
|
|
while (streamFuture.isRunning())
|
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
}
|
|
|
|
|
2017-06-26 18:51:44 +08:00
|
|
|
void CameraSource::subscribe()
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker{&deviceMutex};
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-06-26 18:53:52 +08:00
|
|
|
++subscriptions;
|
2017-06-26 18:51:44 +08:00
|
|
|
openDevice();
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::unsubscribe()
|
|
|
|
{
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker{&deviceMutex};
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2017-06-26 18:53:52 +08:00
|
|
|
--subscriptions;
|
|
|
|
if (subscriptions == 0) {
|
2015-06-26 23:38:53 +08:00
|
|
|
closeDevice();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @brief Opens the video device and starts streaming.
|
|
|
|
* @note Callers must own the biglock.
|
|
|
|
*/
|
2017-06-26 18:51:44 +08:00
|
|
|
void CameraSource::openDevice()
|
2015-06-26 23:38:53 +08:00
|
|
|
{
|
2017-06-26 19:05:26 +08:00
|
|
|
if (QThread::currentThread() != deviceThread) {
|
|
|
|
QMetaObject::invokeMethod(this, "openDevice");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker{&streamMutex};
|
2017-06-27 05:32:27 +08:00
|
|
|
if (subscriptions == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-19 03:44:31 +08:00
|
|
|
qDebug() << "Opening device " << deviceName;
|
2015-10-10 07:24:04 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (device) {
|
2015-06-26 23:38:53 +08:00
|
|
|
device->open();
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// We need to create a new CameraDevice
|
|
|
|
AVCodec* codec;
|
2016-06-19 03:44:31 +08:00
|
|
|
device = CameraDevice::open(deviceName, mode);
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!device) {
|
2015-06-22 20:59:55 +08:00
|
|
|
qWarning() << "Failed to open device!";
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// We need to open the device as many time as we already have subscribers,
|
|
|
|
// otherwise the device could get closed while we still have subscribers
|
2017-12-22 06:35:39 +08:00
|
|
|
for (int i = 0; i < subscriptions; ++i) {
|
2015-06-26 23:38:53 +08:00
|
|
|
device->open();
|
2017-12-22 06:35:39 +08:00
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2017-02-16 07:38:14 +08:00
|
|
|
// Find the first video stream, if any
|
2017-02-26 19:52:45 +08:00
|
|
|
for (unsigned i = 0; i < device->context->nb_streams; ++i) {
|
2017-02-16 07:38:14 +08:00
|
|
|
AVMediaType type;
|
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
|
|
|
type = device->context->streams[i]->codec->codec_type;
|
|
|
|
#else
|
|
|
|
type = device->context->streams[i]->codecpar->codec_type;
|
|
|
|
#endif
|
|
|
|
if (type == AVMEDIA_TYPE_VIDEO) {
|
2015-10-11 14:38:44 +08:00
|
|
|
videoStreamIndex = i;
|
2015-05-14 10:46:28 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-06-19 03:44:31 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (videoStreamIndex == -1) {
|
2016-06-19 03:44:31 +08:00
|
|
|
qWarning() << "Video stream not found";
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2016-06-19 03:44:31 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-02-16 07:38:14 +08:00
|
|
|
AVCodecID codecId;
|
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
2015-10-11 14:38:44 +08:00
|
|
|
cctxOrig = device->context->streams[videoStreamIndex]->codec;
|
2017-02-16 07:38:14 +08:00
|
|
|
codecId = cctxOrig->codec_id;
|
|
|
|
#else
|
|
|
|
// Get the stream's codec's parameters and find a matching decoder
|
|
|
|
AVCodecParameters* cparams = device->context->streams[videoStreamIndex]->codecpar;
|
|
|
|
codecId = cparams->codec_id;
|
|
|
|
#endif
|
|
|
|
codec = avcodec_find_decoder(codecId);
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!codec) {
|
2016-06-19 03:44:31 +08:00
|
|
|
qWarning() << "Codec not found";
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2016-06-19 03:44:31 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-02-16 07:38:14 +08:00
|
|
|
|
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
2015-05-14 10:46:28 +08:00
|
|
|
// Copy context, since we apparently aren't allowed to use the original
|
|
|
|
cctx = avcodec_alloc_context3(codec);
|
2017-02-26 19:52:45 +08:00
|
|
|
if (avcodec_copy_context(cctx, cctxOrig) != 0) {
|
2016-06-19 03:44:31 +08:00
|
|
|
qWarning() << "Can't copy context";
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2016-06-19 03:44:31 +08:00
|
|
|
}
|
2017-10-19 06:37:08 +08:00
|
|
|
|
|
|
|
cctx->refcounted_frames = 1;
|
2017-02-16 07:38:14 +08:00
|
|
|
#else
|
|
|
|
// Create a context for our codec, using the existing parameters
|
|
|
|
cctx = avcodec_alloc_context3(codec);
|
|
|
|
if (avcodec_parameters_to_context(cctx, cparams) < 0) {
|
|
|
|
qWarning() << "Can't create AV context from parameters";
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2017-02-16 07:38:14 +08:00
|
|
|
}
|
|
|
|
#endif
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
// Open codec
|
2017-02-26 19:52:45 +08:00
|
|
|
if (avcodec_open2(cctx, codec, nullptr) < 0) {
|
2016-06-19 03:44:31 +08:00
|
|
|
qWarning() << "Can't open codec";
|
2015-05-14 10:46:28 +08:00
|
|
|
avcodec_free_context(&cctx);
|
2017-06-26 18:51:44 +08:00
|
|
|
emit openFailed();
|
|
|
|
return;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (streamFuture.isRunning())
|
2015-06-26 23:38:53 +08:00
|
|
|
qDebug() << "The stream thread is already running! Keeping the current one open.";
|
2015-05-14 10:46:28 +08:00
|
|
|
else
|
|
|
|
streamFuture = QtConcurrent::run(std::bind(&CameraSource::stream, this));
|
|
|
|
|
|
|
|
// Synchronize with our stream thread
|
|
|
|
while (!streamFuture.isRunning())
|
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
|
2015-07-22 02:38:43 +08:00
|
|
|
emit deviceOpened();
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @brief Closes the video device and stops streaming.
|
|
|
|
* @note Callers must own the biglock.
|
|
|
|
*/
|
2015-06-26 23:38:53 +08:00
|
|
|
void CameraSource::closeDevice()
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2017-06-26 19:05:26 +08:00
|
|
|
if (QThread::currentThread() != deviceThread) {
|
|
|
|
QMetaObject::invokeMethod(this, "closeDevice");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-26 19:00:29 +08:00
|
|
|
QWriteLocker locker{&streamMutex};
|
2017-06-27 05:32:27 +08:00
|
|
|
if (subscriptions != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-04 23:18:37 +08:00
|
|
|
qDebug() << "Closing device " << deviceName;
|
2015-10-10 07:24:04 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// Free all remaining VideoFrame
|
2016-05-02 16:22:08 +08:00
|
|
|
VideoFrame::untrackFrames(id, true);
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// Free our resources and close the device
|
|
|
|
videoStreamIndex = -1;
|
|
|
|
avcodec_free_context(&cctx);
|
2017-02-16 07:38:14 +08:00
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
2015-06-26 23:38:53 +08:00
|
|
|
avcodec_close(cctxOrig);
|
|
|
|
cctxOrig = nullptr;
|
2017-02-16 07:38:14 +08:00
|
|
|
#endif
|
2017-02-26 19:52:45 +08:00
|
|
|
while (device && !device->close()) {
|
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
device = nullptr;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:18:57 +08:00
|
|
|
/**
|
2016-08-01 16:22:17 +08:00
|
|
|
* @brief Blocking. Decodes video stream and emits new frames.
|
|
|
|
* @note Designed to run in its own thread.
|
|
|
|
*/
|
2015-05-14 10:46:28 +08:00
|
|
|
void CameraSource::stream()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
auto streamLoop = [=]() {
|
2017-02-16 10:52:37 +08:00
|
|
|
AVPacket packet;
|
|
|
|
if (av_read_frame(device->context, &packet) != 0) {
|
2015-05-14 10:46:28 +08:00
|
|
|
return;
|
2017-02-16 10:52:37 +08:00
|
|
|
}
|
2016-06-19 03:44:31 +08:00
|
|
|
|
2017-02-16 10:52:37 +08:00
|
|
|
#if LIBAVCODEC_VERSION_INT < 3747941
|
|
|
|
AVFrame* frame = av_frame_alloc();
|
|
|
|
if (!frame) {
|
2015-05-14 10:46:28 +08:00
|
|
|
return;
|
2017-02-16 10:52:37 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// Only keep packets from the right stream;
|
2017-02-26 19:52:45 +08:00
|
|
|
if (packet.stream_index == videoStreamIndex) {
|
2015-05-14 10:46:28 +08:00
|
|
|
// Decode video frame
|
|
|
|
int frameFinished;
|
|
|
|
avcodec_decode_video2(cctx, frame, &frameFinished, &packet);
|
2017-02-16 10:52:37 +08:00
|
|
|
if (!frameFinished) {
|
2015-05-14 10:46:28 +08:00
|
|
|
return;
|
2017-02-16 10:52:37 +08:00
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2016-05-02 16:22:08 +08:00
|
|
|
VideoFrame* vframe = new VideoFrame(id, frame);
|
|
|
|
emit frameAvailable(vframe->trackFrame());
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
2017-02-16 10:52:37 +08:00
|
|
|
#else
|
|
|
|
|
|
|
|
// Forward packets to the decoder and grab the decoded frame
|
|
|
|
bool isVideo = packet.stream_index == videoStreamIndex;
|
|
|
|
bool readyToRecive = isVideo && !avcodec_send_packet(cctx, &packet);
|
|
|
|
|
|
|
|
if (readyToRecive) {
|
|
|
|
AVFrame* frame = av_frame_alloc();
|
|
|
|
if (frame && !avcodec_receive_frame(cctx, frame)) {
|
|
|
|
VideoFrame* vframe = new VideoFrame(id, frame);
|
|
|
|
emit frameAvailable(vframe->trackFrame());
|
|
|
|
} else {
|
|
|
|
av_frame_free(&frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
av_packet_unref(&packet);
|
2015-05-14 10:46:28 +08:00
|
|
|
};
|
|
|
|
|
2016-06-19 03:44:31 +08:00
|
|
|
forever
|
|
|
|
{
|
2016-08-04 23:18:37 +08:00
|
|
|
QReadLocker locker{&streamMutex};
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2016-08-04 23:18:37 +08:00
|
|
|
// Exit if device is no longer valid
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!device) {
|
2016-08-04 23:18:37 +08:00
|
|
|
break;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
streamLoop();
|
|
|
|
}
|
|
|
|
}
|