mirror of https://github.com/qTox/qTox.git
35 changed files with 1270 additions and 1201 deletions
@ -1,113 +0,0 @@
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#include "camera.h" |
||||
#include "src/video/cameraworker.h" |
||||
#include <QDebug> |
||||
#include <QThread> |
||||
#include <QMutexLocker> |
||||
|
||||
Camera* Camera::getInstance() |
||||
{ |
||||
static Camera instance; |
||||
|
||||
return &instance; |
||||
} |
||||
|
||||
Camera::Camera() |
||||
: refcount(0) |
||||
, workerThread(nullptr) |
||||
, worker(nullptr) |
||||
{ |
||||
worker = new CameraWorker(0); |
||||
workerThread = new QThread(); |
||||
|
||||
worker->moveToThread(workerThread); |
||||
|
||||
connect(workerThread, &QThread::started, worker, &CameraWorker::onStart); |
||||
connect(workerThread, &QThread::finished, worker, &CameraWorker::deleteLater); |
||||
connect(worker, &CameraWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable); |
||||
|
||||
connect(worker, &CameraWorker::resProbingFinished, this, &Camera::resolutionProbingFinished); |
||||
connect(worker, &CameraWorker::propProbingFinished, this, [=](int prop, double val) { emit propProbingFinished(Prop(prop), val); } ); |
||||
|
||||
workerThread->start(); |
||||
} |
||||
|
||||
Camera::~Camera() |
||||
{ |
||||
workerThread->exit(); |
||||
workerThread->deleteLater(); |
||||
} |
||||
|
||||
void Camera::subscribe() |
||||
{ |
||||
if (refcount++ <= 0) |
||||
worker->resume(); |
||||
} |
||||
|
||||
void Camera::unsubscribe() |
||||
{ |
||||
if (--refcount <= 0) |
||||
{ |
||||
worker->suspend(); |
||||
refcount = 0; |
||||
} |
||||
} |
||||
|
||||
void Camera::probeProp(Camera::Prop prop) |
||||
{ |
||||
worker->probeProp(int(prop)); |
||||
} |
||||
|
||||
void Camera::probeResolutions() |
||||
{ |
||||
worker->probeResolutions(); |
||||
} |
||||
|
||||
void Camera::setResolution(QSize res) |
||||
{ |
||||
worker->setProp(CV_CAP_PROP_FRAME_WIDTH, res.width()); |
||||
worker->setProp(CV_CAP_PROP_FRAME_HEIGHT, res.height()); |
||||
} |
||||
|
||||
QSize Camera::getCurrentResolution() |
||||
{ |
||||
return QSize(worker->getProp(CV_CAP_PROP_FRAME_WIDTH), worker->getProp(CV_CAP_PROP_FRAME_HEIGHT)); |
||||
} |
||||
|
||||
void Camera::setProp(Camera::Prop prop, double val) |
||||
{ |
||||
worker->setProp(int(prop), val); |
||||
} |
||||
|
||||
double Camera::getProp(Camera::Prop prop) |
||||
{ |
||||
return worker->getProp(int(prop)); |
||||
} |
||||
|
||||
void Camera::onNewFrameAvailable(const VideoFrame &frame) |
||||
{ |
||||
emit frameAvailable(frame); |
||||
|
||||
mutex.lock(); |
||||
currFrame = frame; |
||||
mutex.unlock(); |
||||
} |
||||
|
||||
VideoFrame Camera::getLastFrame() |
||||
{ |
||||
QMutexLocker lock(&mutex); |
||||
return currFrame; |
||||
} |
@ -1,84 +0,0 @@
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#ifndef CAMERA_H |
||||
#define CAMERA_H |
||||
|
||||
#include <QImage> |
||||
#include <QList> |
||||
#include <QMutex> |
||||
#include "vpx/vpx_image.h" |
||||
#include "opencv2/highgui/highgui.hpp" |
||||
#include "src/video/videosource.h" |
||||
|
||||
class CameraWorker; |
||||
|
||||
/**
|
||||
* 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 only when needed, and giving access to the last frames |
||||
**/ |
||||
|
||||
class Camera : public VideoSource |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
enum Prop : int { |
||||
BRIGHTNESS = CV_CAP_PROP_BRIGHTNESS, |
||||
SATURATION = CV_CAP_PROP_SATURATION, |
||||
CONTRAST = CV_CAP_PROP_CONTRAST, |
||||
HUE = CV_CAP_PROP_HUE, |
||||
WIDTH = CV_CAP_PROP_FRAME_WIDTH, |
||||
HEIGHT = CV_CAP_PROP_FRAME_HEIGHT, |
||||
}; |
||||
|
||||
~Camera(); |
||||
|
||||
static Camera* getInstance(); ///< Returns the global widget's Camera instance
|
||||
VideoFrame getLastFrame(); |
||||
|
||||
void setResolution(QSize res); |
||||
QSize getCurrentResolution(); |
||||
|
||||
void setProp(Prop prop, double val); |
||||
double getProp(Prop prop); |
||||
|
||||
void probeProp(Prop prop); |
||||
void probeResolutions(); |
||||
|
||||
// VideoSource interface
|
||||
virtual void subscribe(); |
||||
virtual void unsubscribe(); |
||||
|
||||
signals: |
||||
void resolutionProbingFinished(QList<QSize> res); |
||||
void propProbingFinished(Prop prop, double val); |
||||
|
||||
protected: |
||||
Camera(); |
||||
|
||||
private: |
||||
int refcount; ///< Number of users suscribed to the camera
|
||||
VideoFrame currFrame; |
||||
QMutex mutex; |
||||
|
||||
QThread* workerThread; |
||||
CameraWorker* worker; |
||||
|
||||
private slots: |
||||
void onNewFrameAvailable(const VideoFrame& frame); |
||||
|
||||
}; |
||||
|
||||
#endif // CAMERA_H
|
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
#include <QDebug> |
||||
extern "C" { |
||||
#include <libavformat/avformat.h> |
||||
#include <libavdevice/avdevice.h> |
||||
} |
||||
#include "cameradevice.h" |
||||
#include "src/misc/settings.h" |
||||
|
||||
QHash<QString, CameraDevice*> CameraDevice::openDevices; |
||||
QMutex CameraDevice::openDeviceLock, CameraDevice::iformatLock; |
||||
AVInputFormat* CameraDevice::iformat{nullptr}; |
||||
|
||||
CameraDevice::CameraDevice(const QString devName, AVFormatContext *context) |
||||
: devName{devName}, context{context}, refcount{1} |
||||
{ |
||||
|
||||
} |
||||
|
||||
CameraDevice* CameraDevice::open(QString devName) |
||||
{ |
||||
openDeviceLock.lock(); |
||||
AVFormatContext* fctx = nullptr; |
||||
CameraDevice* dev = openDevices.value(devName); |
||||
if (dev) |
||||
goto out; |
||||
|
||||
if (avformat_open_input(&fctx, devName.toStdString().c_str(), nullptr, nullptr)<0) |
||||
goto out; |
||||
|
||||
if (avformat_find_stream_info(fctx, NULL) < 0) |
||||
{ |
||||
avformat_close_input(&fctx); |
||||
goto out; |
||||
} |
||||
|
||||
dev = new CameraDevice{devName, fctx}; |
||||
openDevices[devName] = dev; |
||||
|
||||
out: |
||||
openDeviceLock.unlock(); |
||||
return dev; |
||||
} |
||||
|
||||
void CameraDevice::open() |
||||
{ |
||||
++refcount; |
||||
} |
||||
|
||||
bool CameraDevice::close() |
||||
{ |
||||
if (--refcount <= 0) |
||||
{ |
||||
openDeviceLock.lock(); |
||||
openDevices.remove(devName); |
||||
openDeviceLock.unlock(); |
||||
avformat_close_input(&context); |
||||
delete this; |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
QVector<QPair<QString, QString>> CameraDevice::getDeviceList() |
||||
{ |
||||
QVector<QPair<QString, QString>> devices; |
||||
|
||||
if (!getDefaultInputFormat()) |
||||
return devices; |
||||
|
||||
AVDeviceInfoList* devlist; |
||||
avdevice_list_input_sources(iformat, nullptr, nullptr, &devlist); |
||||
|
||||
devices.resize(devlist->nb_devices); |
||||
for (int i=0; i<devlist->nb_devices; i++) |
||||
{ |
||||
AVDeviceInfo* dev = devlist->devices[i]; |
||||
devices[i].first = dev->device_name; |
||||
devices[i].second = dev->device_description; |
||||
} |
||||
|
||||
return devices; |
||||
} |
||||
|
||||
QString CameraDevice::getDefaultDeviceName() |
||||
{ |
||||
QString device = Settings::getInstance().getVideoDev(); |
||||
|
||||
if (!getDefaultInputFormat()) |
||||
return device; |
||||
|
||||
bool actuallyExists = false; |
||||
AVDeviceInfoList* devlist; |
||||
avdevice_list_input_sources(iformat, nullptr, nullptr, &devlist); |
||||
|
||||
for (int i=0; i<devlist->nb_devices; i++) |
||||
{ |
||||
if (device == devlist->devices[i]->device_name) |
||||
{ |
||||
actuallyExists = true; |
||||
break; |
||||
} |
||||
} |
||||
if (actuallyExists) |
||||
return device; |
||||
|
||||
if (!devlist->nb_devices) |
||||
return QString(); |
||||
|
||||
int defaultDev = devlist->default_device == -1 ? 0 : devlist->default_device; |
||||
return QString(devlist->devices[defaultDev]->device_name); |
||||
} |
||||
|
||||
bool CameraDevice::getDefaultInputFormat() |
||||
{ |
||||
QMutexLocker locker(&iformatLock); |
||||
if (iformat) |
||||
return true; |
||||
|
||||
avdevice_register_all(); |
||||
|
||||
// Linux
|
||||
if ((iformat = av_find_input_format("v4l2"))) |
||||
return true; |
||||
|
||||
// Windows
|
||||
if ((iformat = av_find_input_format("dshow"))) |
||||
return true; |
||||
|
||||
// Mac
|
||||
if ((iformat = av_find_input_format("avfoundation"))) |
||||
return true; |
||||
if ((iformat = av_find_input_format("qtkit"))) |
||||
return true; |
||||
|
||||
qWarning() << "No valid input format found"; |
||||
return false; |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
#ifndef CAMERADEVICE_H |
||||
#define CAMERADEVICE_H |
||||
|
||||
#include <QHash> |
||||
#include <QString> |
||||
#include <QMutex> |
||||
#include <QVector> |
||||
#include <atomic> |
||||
|
||||
struct AVFormatContext; |
||||
struct AVInputFormat; |
||||
|
||||
/// Maintains an FFmpeg context for open camera devices,
|
||||
/// takes care of sharing the context accross users
|
||||
/// and closing the camera device when not in use.
|
||||
/// The device can be opened recursively,
|
||||
/// and must then be closed recursively
|
||||
class CameraDevice |
||||
{ |
||||
public: |
||||
/// Opens a device, creating a new one if needed
|
||||
/// Returns a nullptr if the device couldn't be opened
|
||||
static CameraDevice* open(QString devName); |
||||
void open(); ///< Opens the device again. Never fails
|
||||
bool close(); ///< Closes the device. Never fails. If returns true, "this" becomes invalid
|
||||
|
||||
/// Returns a list of device names and descriptions
|
||||
/// The names are the first part of the pair and can be passed to open(QString)
|
||||
static QVector<QPair<QString, QString> > getDeviceList(); |
||||
|
||||
/// Returns the short name of the default defice
|
||||
/// This is either the device in the settings
|
||||
/// or the system default.
|
||||
static QString getDefaultDeviceName(); |
||||
|
||||
private: |
||||
CameraDevice(const QString devName, AVFormatContext *context); |
||||
static bool getDefaultInputFormat(); ///< Sets CameraDevice::iformat, returns success/failure
|
||||
|
||||
public: |
||||
const QString devName; ///< Short name of the device
|
||||
AVFormatContext* context; ///< Context of the open device, must always be valid
|
||||
|
||||
private: |
||||
std::atomic_int refcount; ///< Number of times the device was opened
|
||||
static QHash<QString, CameraDevice*> openDevices; |
||||
static QMutex openDeviceLock, iformatLock; |
||||
static AVInputFormat* iformat; |
||||
}; |
||||
|
||||
#endif // CAMERADEVICE_H
|
@ -0,0 +1,273 @@
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
extern "C" { |
||||
#include <libavcodec/avcodec.h> |
||||
#include <libavdevice/avdevice.h> |
||||
#include <libavformat/avformat.h> |
||||
#include <libswscale/swscale.h> |
||||
} |
||||
#include <QMutexLocker> |
||||
#include <QDebug> |
||||
#include <QtConcurrent/QtConcurrentRun> |
||||
#include <memory> |
||||
#include <functional> |
||||
#include "camerasource.h" |
||||
#include "cameradevice.h" |
||||
#include "videoframe.h" |
||||
|
||||
CameraSource::CameraSource() |
||||
: CameraSource{CameraDevice::getDefaultDeviceName()} |
||||
{ |
||||
} |
||||
|
||||
CameraSource::CameraSource(const QString deviceName) |
||||
: deviceName{deviceName}, device{nullptr}, |
||||
cctx{nullptr}, videoStreamIndex{-1}, |
||||
biglock{false}, freelistLock{false}, subscriptions{0} |
||||
{ |
||||
av_register_all(); |
||||
avdevice_register_all(); |
||||
} |
||||
|
||||
CameraSource::~CameraSource() |
||||
{ |
||||
// Fast lock, in case our stream thread is running
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
|
||||
if (cctx) |
||||
avcodec_free_context(&cctx); |
||||
|
||||
for (int i=subscriptions; i; --i) |
||||
device->close(); |
||||
device = nullptr; |
||||
biglock=false; |
||||
|
||||
// Synchronize with our stream thread
|
||||
while (streamFuture.isRunning()) |
||||
QThread::yieldCurrentThread(); |
||||
} |
||||
|
||||
bool CameraSource::subscribe() |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
|
||||
if (device) |
||||
{ |
||||
device->open(); |
||||
++subscriptions; |
||||
biglock = false; |
||||
return true; |
||||
} |
||||
|
||||
// We need to create a new CameraDevice
|
||||
AVCodec* codec; |
||||
device = CameraDevice::open(deviceName); |
||||
if (!device) |
||||
{ |
||||
biglock = false; |
||||
return false; |
||||
} |
||||
|
||||
// Find the first video stream
|
||||
for (unsigned i=0; i<device->context->nb_streams; i++) |
||||
{ |
||||
if(device->context->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) |
||||
{ |
||||
videoStreamIndex=i; |
||||
break; |
||||
} |
||||
} |
||||
if (videoStreamIndex == -1) |
||||
goto fail; |
||||
|
||||
// Get a pointer to the codec context for the video stream
|
||||
cctxOrig=device->context->streams[videoStreamIndex]->codec; |
||||
codec=avcodec_find_decoder(cctxOrig->codec_id); |
||||
if(!codec) |
||||
goto fail; |
||||
|
||||
// Copy context, since we apparently aren't allowed to use the original
|
||||
cctx = avcodec_alloc_context3(codec); |
||||
if(avcodec_copy_context(cctx, cctxOrig) != 0) |
||||
goto fail; |
||||
cctx->refcounted_frames = 1; |
||||
|
||||
// Open codec
|
||||
if(avcodec_open2(cctx, codec, nullptr)<0) |
||||
{ |
||||
avcodec_free_context(&cctx); |
||||
goto fail; |
||||
} |
||||
|
||||
if (streamFuture.isRunning()) |
||||
qCritical() << "The stream thread is already running! Keeping the current one open."; |
||||
else |
||||
streamFuture = QtConcurrent::run(std::bind(&CameraSource::stream, this)); |
||||
|
||||
// Synchronize with our stream thread
|
||||
while (!streamFuture.isRunning()) |
||||
QThread::yieldCurrentThread(); |
||||
|
||||
++subscriptions; |
||||
biglock = false; |
||||
return true; |
||||
|
||||
fail: |
||||
while (!device->close()) {} |
||||
biglock = false; |
||||
return false; |
||||
} |
||||
|
||||
void CameraSource::unsubscribe() |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
|
||||
if (!device) |
||||
{ |
||||
qWarning() << "Unsubscribing with zero subscriber"; |
||||
biglock = false; |
||||
return; |
||||
} |
||||
|
||||
if (--subscriptions == 0) |
||||
{ |
||||
// Free all remaining VideoFrame
|
||||
// Locking must be done precisely this way to avoid races
|
||||
for (int i=0; i<freelist.size(); i++) |
||||
{ |
||||
std::shared_ptr<VideoFrame> vframe = freelist[i].lock(); |
||||
if (!vframe) |
||||
continue; |
||||
vframe->releaseFrame(); |
||||
} |
||||
|
||||
// Free our resources and close the device
|
||||
videoStreamIndex = -1; |
||||
avcodec_free_context(&cctx); |
||||
avcodec_close(cctxOrig); |
||||
cctxOrig = nullptr; |
||||
device->close(); |
||||
device = nullptr; |
||||
|
||||
biglock = false; |
||||
|
||||
// Synchronize with our stream thread
|
||||
while (streamFuture.isRunning()) |
||||
QThread::yieldCurrentThread(); |
||||
} |
||||
else |
||||
{ |
||||
device->close(); |
||||
biglock = false; |
||||
} |
||||
} |
||||
|
||||
void CameraSource::stream() |
||||
{ |
||||
auto streamLoop = [=]() |
||||
{ |
||||
AVFrame* frame = av_frame_alloc(); |
||||
if (!frame) |
||||
return; |
||||
frame->opaque = nullptr; |
||||
|
||||
AVPacket packet; |
||||
if (av_read_frame(device->context, &packet)<0) |
||||
return; |
||||
|
||||
// Only keep packets from the right stream;
|
||||
if (packet.stream_index==videoStreamIndex) |
||||
{ |
||||
// Decode video frame
|
||||
int frameFinished; |
||||
avcodec_decode_video2(cctx, frame, &frameFinished, &packet); |
||||
if (!frameFinished) |
||||
return; |
||||
|
||||
// Broadcast a new VideoFrame, it takes ownership of the AVFrame
|
||||
{ |
||||
bool expected = false; |
||||
while (!freelistLock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
int freeFreelistSlot = getFreelistSlotLockless(); |
||||
auto frameFreeCb = std::bind(&CameraSource::freelistCallback, this, freeFreelistSlot); |
||||
std::shared_ptr<VideoFrame> vframe = std::make_shared<VideoFrame>(frame, frameFreeCb); |
||||
freelist.append(vframe); |
||||
freelistLock = false; |
||||
emit frameAvailable(vframe); |
||||
} |
||||
|
||||
// Free the packet that was allocated by av_read_frame
|
||||
av_free_packet(&packet); |
||||
}; |
||||
|
||||
forever { |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
|
||||
if (!device) |
||||
{ |
||||
biglock = false; |
||||
return; |
||||
} |
||||
|
||||
streamLoop(); |
||||
|
||||
// Give a chance to other functions to pick up the lock if needed
|
||||
biglock = false; |
||||
QThread::yieldCurrentThread(); |
||||
} |
||||
} |
||||
|
||||
void CameraSource::freelistCallback(int freelistIndex) |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!freelistLock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
freelist[freelistIndex].reset(); |
||||
freelistLock = false; |
||||
} |
||||
|
||||
int CameraSource::getFreelistSlotLockless() |
||||
{ |
||||
int size = freelist.size(); |
||||
for (int i=0; i<size; ++i) |
||||
if (freelist[i].expired()) |
||||
return i; |
||||
freelist.resize(size+(size>>1)+4); // Arbitrary growth strategy, should work well
|
||||
return size; |
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#ifndef CAMERA_H |
||||
#define CAMERA_H |
||||
|
||||
#include <QHash> |
||||
#include <QString> |
||||
#include <QFuture> |
||||
#include <QVector> |
||||
#include <atomic> |
||||
#include "src/video/videosource.h" |
||||
|
||||
class CameraDevice; |
||||
struct AVCodecContext; |
||||
|
||||
/**
|
||||
* 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. |
||||
**/ |
||||
|
||||
class CameraSource : public VideoSource |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
CameraSource(); ///< Opens the camera device in the settings, or the system default
|
||||
CameraSource(const QString deviceName); |
||||
~CameraSource(); |
||||
|
||||
// VideoSource interface
|
||||
virtual bool subscribe() override; |
||||
virtual void unsubscribe() override; |
||||
|
||||
private: |
||||
/// Blocking. Decodes video stream and emits new frames.
|
||||
/// Designed to run in its own thread.
|
||||
void stream(); |
||||
/// All VideoFrames must be deleted or released before we can close the device
|
||||
/// or the device will forcibly free them, and then ~VideoFrame() will double free.
|
||||
/// In theory very careful coding from our users could ensure all VideoFrames
|
||||
/// die before unsubscribing, even the ones currently in flight in the metatype system.
|
||||
/// But that's just asking for trouble and mysterious crashes, so we'll just
|
||||
/// maintain a freelist and have all VideoFrames tell us when they die so we can forget them.
|
||||
void freelistCallback(int freelistIndex); |
||||
/// Get the index of a free slot in the freelist
|
||||
/// Callers must hold the freelistLock
|
||||
int getFreelistSlotLockless(); |
||||
|
||||
private: |
||||
QVector<std::weak_ptr<VideoFrame>> freelist; ///< Frames that need freeing before we can safely close the device
|
||||
QFuture<void> streamFuture; ///< Future of the streaming thread
|
||||
const QString deviceName; ///< Short name of the device for CameraDevice's open(QString)
|
||||
CameraDevice* device; ///< Non-owning pointer to an open CameraDevice, or nullptr
|
||||
AVCodecContext* cctx, *cctxOrig; ///< Codec context of the camera's selected video stream
|
||||
int videoStreamIndex; ///< A camera can have multiple streams, this is the one we're decoding
|
||||
std::atomic_bool biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
|
||||
std::atomic_int subscriptions; ///< Remember how many times we subscribed for RAII
|
||||
}; |
||||
|
||||
#endif // CAMERA_H
|
@ -1,240 +0,0 @@
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#include "cameraworker.h" |
||||
|
||||
#include <QTimer> |
||||
#include <QDebug> |
||||
#include <QThread> |
||||
|
||||
CameraWorker::CameraWorker(int index) |
||||
: clock(nullptr) |
||||
, camIndex(index) |
||||
, refCount(0) |
||||
{ |
||||
qRegisterMetaType<VideoFrame>(); |
||||
qRegisterMetaType<QList<QSize>>(); |
||||
} |
||||
|
||||
CameraWorker::~CameraWorker() |
||||
{ |
||||
if (clock) |
||||
delete clock; |
||||
} |
||||
|
||||
void CameraWorker::onStart() |
||||
{ |
||||
if (!clock) |
||||
{ |
||||
clock = new QTimer(this); |
||||
clock->setSingleShot(false); |
||||
clock->setInterval(1000/60); |
||||
|
||||
connect(clock, &QTimer::timeout, this, &CameraWorker::doWork); |
||||
} |
||||
emit started(); |
||||
} |
||||
|
||||
void CameraWorker::_suspend() |
||||
{ |
||||
qDebug() << "Suspend"; |
||||
clock->stop(); |
||||
unsubscribe(); |
||||
} |
||||
|
||||
void CameraWorker::_resume() |
||||
{ |
||||
qDebug() << "Resume"; |
||||
subscribe(); |
||||
clock->start(); |
||||
} |
||||
|
||||
void CameraWorker::_setProp(int prop, double val) |
||||
{ |
||||
props[prop] = val; |
||||
|
||||
if (cam.isOpened()) |
||||
cam.set(prop, val); |
||||
} |
||||
|
||||
double CameraWorker::_getProp(int prop) |
||||
{ |
||||
if (!props.contains(prop)) |
||||
{ |
||||
subscribe(); |
||||
props[prop] = cam.get(prop); |
||||
emit propProbingFinished(prop, props[prop]); |
||||
unsubscribe(); |
||||
} |
||||
|
||||
return props.value(prop); |
||||
} |
||||
|
||||
void CameraWorker::_probeResolutions() |
||||
{ |
||||
if (resolutions.isEmpty()) |
||||
{ |
||||
subscribe(); |
||||
|
||||
// probe resolutions
|
||||
QList<QSize> propbeRes = { |
||||
QSize( 160, 120), // QQVGA
|
||||
QSize( 320, 240), // HVGA
|
||||
QSize( 432, 240), // WQVGA
|
||||
QSize( 640, 360), // nHD
|
||||
QSize( 640, 480), |
||||
QSize( 800, 600), |
||||
QSize( 960, 640), |
||||
QSize(1024, 768), // XGA
|
||||
QSize(1280, 720), |
||||
QSize(1280, 1024), |
||||
QSize(1360, 768), |
||||
QSize(1366, 768), |
||||
QSize(1400, 1050), |
||||
QSize(1440, 900), |
||||
QSize(1600, 1200), |
||||
QSize(1680, 1050), |
||||
QSize(1920, 1200), |
||||
}; |
||||
|
||||
for (QSize res : propbeRes) |
||||
{ |
||||
cam.set(CV_CAP_PROP_FRAME_WIDTH, res.width()); |
||||
cam.set(CV_CAP_PROP_FRAME_HEIGHT, res.height()); |
||||
|
||||
double w = cam.get(CV_CAP_PROP_FRAME_WIDTH); |
||||
double h = cam.get(CV_CAP_PROP_FRAME_HEIGHT); |
||||
|
||||
//qDebug() << "PROBING:" << res << " got " << w << h;
|
||||
|
||||
if (w>0 && h>0 && !resolutions.contains(QSize(w,h))) |
||||
resolutions.append(QSize(w,h)); |
||||
} |
||||
|
||||
unsubscribe(); |
||||
|
||||
qDebug() << "Resolutions" <<resolutions; |
||||
} |
||||
|
||||
emit resProbingFinished(resolutions); |
||||
} |
||||
|
||||
void CameraWorker::applyProps() |
||||
{ |
||||
if (!cam.isOpened()) |
||||
return; |
||||
|
||||
for (int prop : props.keys()) |
||||
cam.set(prop, props.value(prop)); |
||||
} |
||||
|
||||
void CameraWorker::subscribe() |
||||
{ |
||||
if (refCount++ == 0) |
||||
{ |
||||
if (!cam.isOpened()) |
||||
{ |
||||
queue.clear(); |
||||
bool bSuccess = false; |
||||
|
||||
try |
||||
{ |
||||
bSuccess = cam.open(camIndex); |
||||
} |
||||
catch( cv::Exception& e ) |
||||
{ |
||||
qDebug() << "OpenCV exception caught: " << e.what(); |
||||
} |
||||
|
||||
if (!bSuccess) |
||||
{ |
||||
qDebug() << "Could not open camera"; |
||||
} |
||||
applyProps(); // restore props
|
||||
} |
||||
} |
||||
} |
||||
|
||||
void CameraWorker::unsubscribe() |
||||
{ |
||||
if (--refCount <= 0) |
||||
{ |
||||
cam.release(); |
||||
frame = cv::Mat3b(); |
||||
queue.clear(); |
||||
refCount = 0; |
||||
} |
||||
} |
||||
|
||||
void CameraWorker::doWork() |
||||
{ |
||||
if (!cam.isOpened()) |
||||
return; |
||||
|
||||
bool bSuccess = false; |
||||
|
||||
try |
||||
{ |
||||
bSuccess = cam.read(frame); |
||||
} |
||||
catch( cv::Exception& e ) |
||||
{ |
||||
qDebug() << "OpenCV exception caught: " << e.what();; |
||||
this->clock->stop(); // prevent log spamming
|
||||
qDebug() << "stopped clock"; |
||||
} |
||||
|
||||
if (!bSuccess) |
||||
{ |
||||
qDebug() << "Cannot read frame"; |
||||
return; |
||||
} |
||||
|
||||
QByteArray frameData = QByteArray::fromRawData(reinterpret_cast<char*>(frame.data), frame.total() * frame.channels()); |
||||
|
||||
emit newFrameAvailable(VideoFrame{frameData, QSize(frame.cols, frame.rows), VideoFrame::BGR}); |
||||
} |
||||
|
||||
void CameraWorker::suspend() |
||||
{ |
||||
QMetaObject::invokeMethod(this, "_suspend"); |
||||
} |
||||
|
||||
void CameraWorker::resume() |
||||
{ |
||||
QMetaObject::invokeMethod(this, "_resume"); |
||||
} |
||||
|
||||
void CameraWorker::setProp(int prop, double val) |
||||
{ |
||||
QMetaObject::invokeMethod(this, "_setProp", Q_ARG(int, prop), Q_ARG(double, val)); |
||||
} |
||||
|
||||
void CameraWorker::probeProp(int prop) |
||||
{ |
||||
QMetaObject::invokeMethod(this, "_getProp", Q_ARG(int, prop)); |
||||
} |
||||
|
||||
void CameraWorker::probeResolutions() |
||||
{ |
||||
QMetaObject::invokeMethod(this, "_probeResolutions"); |
||||
} |
||||
|
||||
double CameraWorker::getProp(int prop) |
||||
{ |
||||
double ret = 0.0; |
||||
QMetaObject::invokeMethod(this, "_getProp", Qt::BlockingQueuedConnection, Q_RETURN_ARG(double, ret), Q_ARG(int, prop)); |
||||
|
||||
return ret; |
||||
} |
@ -1,78 +0,0 @@
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#ifndef CAMERAWORKER_H |
||||
#define CAMERAWORKER_H |
||||
|
||||
#include <QObject> |
||||
#include <QList> |
||||
#include <QMap> |
||||
#include <QMutex> |
||||
#include <QQueue> |
||||
#include <QSize> |
||||
|
||||
#include "opencv2/highgui/highgui.hpp" |
||||
#include "videosource.h" |
||||
|
||||
class QTimer; |
||||
|
||||
class CameraWorker : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
CameraWorker(int index); |
||||
~CameraWorker(); |
||||
void doWork(); |
||||
|
||||
void suspend(); |
||||
void resume(); |
||||
void setProp(int prop, double val); |
||||
double getProp(int prop); // blocking call!
|
||||
|
||||
public slots: |
||||
void onStart(); |
||||
void probeProp(int prop); |
||||
void probeResolutions(); |
||||
|
||||
signals: |
||||
void started(); |
||||
void newFrameAvailable(const VideoFrame& frame); |
||||
void resProbingFinished(QList<QSize> res); |
||||
void propProbingFinished(int prop, double val); |
||||
|
||||
private slots: |
||||
void _suspend(); |
||||
void _resume(); |
||||
void _setProp(int prop, double val); |
||||
double _getProp(int prop); |
||||
void _probeResolutions(); |
||||
|
||||
private: |
||||
void applyProps(); |
||||
void subscribe(); |
||||
void unsubscribe(); |
||||
|
||||
private: |
||||
QMutex mutex; |
||||
QQueue<cv::Mat3b> queue; |
||||
QTimer* clock; |
||||
cv::VideoCapture cam; |
||||
cv::Mat3b frame; |
||||
int camIndex; |
||||
QMap<int, double> props; |
||||
QList<QSize> resolutions; |
||||
int refCount; |
||||
}; |
||||
|
||||
#endif // CAMERAWORKER_H
|
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
extern "C" { |
||||
#include <libavcodec/avcodec.h> |
||||
} |
||||
#include "corevideosource.h" |
||||
#include "videoframe.h" |
||||
|
||||
CoreVideoSource::CoreVideoSource() |
||||
: subscribers{0}, deleteOnClose{false}, |
||||
biglock{false} |
||||
{ |
||||
} |
||||
|
||||
void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe) |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
|
||||
std::shared_ptr<VideoFrame> vframe; |
||||
AVFrame* avframe; |
||||
uint8_t* buf; |
||||
int width = vpxframe->d_w, height = vpxframe->d_h; |
||||
int dstStride, srcStride, minStride; |
||||
|
||||
if (subscribers <= 0) |
||||
goto end; |
||||
|
||||
avframe = av_frame_alloc(); |
||||
if (!avframe) |
||||
goto end; |
||||
avframe->width = width; |
||||
avframe->height = height; |
||||
avframe->format = AV_PIX_FMT_YUV420P; |
||||
|
||||
buf = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, width, height)); |
||||
if (!buf) |
||||
{ |
||||
av_frame_free(&avframe); |
||||
goto end; |
||||
} |
||||
avframe->opaque = buf; |
||||
|
||||
avpicture_fill((AVPicture*)avframe, buf, AV_PIX_FMT_YUV420P, width, height); |
||||
|
||||
dstStride=avframe->linesize[0], srcStride=vpxframe->stride[0], minStride=std::min(dstStride, srcStride); |
||||
for (int i=0; i<height; i++) |
||||
memcpy(avframe->data[0]+dstStride*i, vpxframe->planes[0]+srcStride*i, minStride); |
||||
dstStride=avframe->linesize[1], srcStride=vpxframe->stride[1], minStride=std::min(dstStride, srcStride); |
||||
for (int i=0; i<height/2; i++) |
||||
memcpy(avframe->data[1]+dstStride*i, vpxframe->planes[1]+srcStride*i, minStride); |
||||
dstStride=avframe->linesize[2], srcStride=vpxframe->stride[2], minStride=std::min(dstStride, srcStride); |
||||
for (int i=0; i<height/2; i++) |
||||
memcpy(avframe->data[2]+dstStride*i, vpxframe->planes[2]+srcStride*i, minStride); |
||||
|
||||
vframe = std::make_shared<VideoFrame>(avframe); |
||||
emit frameAvailable(vframe); |
||||
|
||||
end: |
||||
biglock = false; |
||||
} |
||||
|
||||
bool CoreVideoSource::subscribe() |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
++subscribers; |
||||
biglock = false; |
||||
return true; |
||||
} |
||||
|
||||
void CoreVideoSource::unsubscribe() |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
if (--subscribers == 0) |
||||
{ |
||||
if (deleteOnClose) |
||||
{ |
||||
biglock = false; |
||||
delete this; |
||||
return; |
||||
} |
||||
} |
||||
biglock = false; |
||||
} |
||||
|
||||
void CoreVideoSource::setDeleteOnClose(bool newstate) |
||||
{ |
||||
// Fast lock
|
||||
{ |
||||
bool expected = false; |
||||
while (!biglock.compare_exchange_weak(expected, true)) |
||||
expected = false; |
||||
} |
||||
deleteOnClose = newstate; |
||||
biglock = false; |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
#ifndef COREVIDEOSOURCE_H |
||||
#define COREVIDEOSOURCE_H |
||||
|
||||
#include <vpx/vpx_image.h> |
||||
#include <atomic> |
||||
#include "videosource.h" |
||||
|
||||
/// A VideoSource that emits frames received by Core
|
||||
class CoreVideoSource : public VideoSource |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
// VideoSource interface
|
||||
virtual bool subscribe() override; |
||||
virtual void unsubscribe() override; |
||||
|
||||
private: |
||||
// Only Core should create a CoreVideoSource since
|
||||
// only Core can push images to it
|
||||
CoreVideoSource(); |
||||
|
||||
/// Makes a copy of the vpx_image_t and emits it as a new VideoFrame
|
||||
void pushFrame(const vpx_image_t *frame); |
||||
/// If true, self-delete after the last suscriber is gone
|
||||
void setDeleteOnClose(bool newstate); |
||||
|
||||
private: |
||||
std::atomic_int subscribers; ///< Number of suscribers
|
||||
std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone
|
||||
std::atomic_bool biglock; ///< Fast lock
|
||||
|
||||
friend class Core; |
||||
}; |
||||
|
||||
#endif // COREVIDEOSOURCE_H
|
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#include "netvideosource.h" |
||||
|
||||
#include <QDebug> |
||||
#include <vpx/vpx_image.h> |
||||
|
||||
NetVideoSource::NetVideoSource() |
||||
{ |
||||
} |
||||
|
||||
void NetVideoSource::pushFrame(VideoFrame frame) |
||||
{ |
||||
emit frameAvailable(frame); |
||||
} |
||||
|
||||
void NetVideoSource::pushVPXFrame(const vpx_image *image) |
||||
{ |
||||
const int dw = image->d_w; |
||||
const int dh = image->d_h; |
||||
|
||||
const int bpl = image->stride[VPX_PLANE_Y]; |
||||
const int cxbpl = image->stride[VPX_PLANE_V]; |
||||
|
||||
VideoFrame frame; |
||||
frame.frameData.resize(dw * dh * 3); //YUV 24bit
|
||||
frame.resolution = QSize(dw, dh); |
||||
frame.format = VideoFrame::YUV; |
||||
|
||||
const uint8_t* yData = image->planes[VPX_PLANE_Y]; |
||||
const uint8_t* uData = image->planes[VPX_PLANE_U]; |
||||
const uint8_t* vData = image->planes[VPX_PLANE_V]; |
||||
|
||||
// convert from planar to packed
|
||||
for (int y = 0; y < dh; ++y) |
||||
{ |
||||
for (int x = 0; x < dw; ++x) |
||||
{ |
||||
uint8_t Y = yData[x + y * bpl]; |
||||
uint8_t U = uData[x/(1 << image->x_chroma_shift) + y/(1 << image->y_chroma_shift)*cxbpl]; |
||||
uint8_t V = vData[x/(1 << image->x_chroma_shift) + y/(1 << image->y_chroma_shift)*cxbpl]; |
||||
|
||||
frame.frameData.data()[dw * 3 * y + x * 3 + 0] = Y; |
||||
frame.frameData.data()[dw * 3 * y + x * 3 + 1] = U; |
||||
frame.frameData.data()[dw * 3 * y + x * 3 + 2] = V; |
||||
} |
||||
} |
||||
|
||||
pushFrame(frame); |
||||
} |
@ -1,34 +0,0 @@
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
This program is libre software: you can redistribute it and/or modify |
||||
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. |
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
|
||||
See the COPYING file for more details. |
||||
*/ |
||||
|
||||
#ifndef NETVIDEOSOURCE_H |
||||
#define NETVIDEOSOURCE_H |
||||
|
||||
#include "videosource.h" |
||||
|
||||
struct vpx_image; |
||||
|
||||
class NetVideoSource : public VideoSource |
||||
{ |
||||
public: |
||||
NetVideoSource(); |
||||
|
||||
void pushFrame(VideoFrame frame); |
||||
void pushVPXFrame(const vpx_image *image); |
||||
|
||||
virtual void subscribe() {} |
||||
virtual void unsubscribe() {} |
||||
}; |
||||
|
||||
#endif // NETVIDEOSOURCE_H
|
Loading…
Reference in new issue