1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
This commit is contained in:
krepa098 2014-10-08 14:25:32 +02:00
parent 7f839b473b
commit 3e234bd076
15 changed files with 326 additions and 114 deletions

View File

@ -1,19 +1,27 @@
#ifndef VIDEOSOURCE_H
#define VIDEOSOURCE_H
#include <QObject>
#include <QSize>
class VideoSource
class VideoSource : public QObject
{
Q_OBJECT
public:
virtual void* getData() = 0;
virtual int getDataSize() = 0;
virtual void lock() = 0;
virtual void* getData() = 0; // a pointer to a frame
virtual int getDataSize() = 0; // size of a frame in bytes
virtual void lock() = 0; // locks a frame so that it can't change
virtual void unlock() = 0;
virtual QSize resolution() = 0;
virtual double fps() = 0;
virtual QSize resolution() = 0; // resolution of a frame
virtual void subscribe() = 0;
virtual void unsubscribe() = 0;
signals:
void frameAvailable();
};
#endif // VIDEOSOURCE_H

View File

@ -16,13 +16,35 @@
#include "camera.h"
#include "widget.h"
#include <QtConcurrent/QtConcurrentRun>
#include <QDebug>
#include <QThread>
#include <QTimer>
using namespace cv;
Camera* Camera::instance = nullptr;
Camera::Camera()
: refcount{0}
: refcount(0)
, workerThread(nullptr)
, worker(nullptr)
{
qDebug() << "New Worker";
worker = new SelfCamWorker(0);
workerThread = new QThread();
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, worker, &SelfCamWorker::onStart);
connect(workerThread, &QThread::finished, worker, &SelfCamWorker::deleteLater);
connect(workerThread, &QThread::deleteLater, worker, &SelfCamWorker::deleteLater);
connect(worker, &SelfCamWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable);
workerThread->start();
}
Camera::~Camera()
{
workerThread->exit();
workerThread->deleteLater();
}
void Camera::subscribe()
@ -30,7 +52,11 @@ void Camera::subscribe()
if (refcount <= 0)
{
refcount = 1;
cam.open(0);
//mode.res.setWidth(cam.get(CV_CAP_PROP_FRAME_WIDTH));
//mode.res.setHeight(cam.get(CV_CAP_PROP_FRAME_HEIGHT));
worker->resume();
}
else
refcount++;
@ -42,26 +68,22 @@ void Camera::unsubscribe()
if (refcount <= 0)
{
//cam.release();
worker->suspend();
refcount = 0;
}
}
Mat Camera::getLastFrame()
cv::Mat Camera::getLastFrame()
{
Mat frame;
cv::Mat frame;
cam >> frame;
Mat out;
if (!frame.empty())
cv::cvtColor(frame, out, CV_BGR2RGB);
return out;
return frame;
}
vpx_image Camera::getLastVPXImage()
{
Mat3b frame = getLastFrame();
cv::Mat3b frame = getLastFrame();
vpx_image img;
int w = frame.size().width, h = frame.size().height;
vpx_img_alloc(&img, VPX_IMG_FMT_I420, w, h, 1); // I420 == YUV420P, same as YV12 with U and V switched
@ -122,6 +144,7 @@ QList<Camera::VideoMode> Camera::getVideoModes()
for (QSize res : resolutions)
{
mutex.lock();
cam.set(CV_CAP_PROP_FRAME_WIDTH, res.width());
cam.set(CV_CAP_PROP_FRAME_HEIGHT, res.height());
@ -134,6 +157,7 @@ QList<Camera::VideoMode> Camera::getVideoModes()
{
modes.append({res, 60}); // assume 60fps for now
}
mutex.unlock();
}
return modes;
@ -162,10 +186,14 @@ void Camera::setVideoMode(Camera::VideoMode mode)
{
if (cam.isOpened())
{
mutex.lock();
cam.set(CV_CAP_PROP_FRAME_WIDTH, mode.res.width());
cam.set(CV_CAP_PROP_FRAME_HEIGHT, mode.res.height());
//cam.set(CV_CAP_PROP_SATURATION, 0.5);
mode.res.setWidth(cam.get(CV_CAP_PROP_FRAME_WIDTH));
mode.res.setHeight(cam.get(CV_CAP_PROP_FRAME_HEIGHT));
mutex.unlock();
qDebug() << "VIDEO MODE" << mode.res;
}
}
@ -210,6 +238,11 @@ double Camera::getProp(Camera::Prop prop)
return 0.0;
}
void Camera::onNewFrameAvailable()
{
emit frameAvailable();
}
void *Camera::getData()
{
return currFrame.data;
@ -223,8 +256,12 @@ int Camera::getDataSize()
void Camera::lock()
{
mutex.lock();
currFrame = getLastFrame();
//getLastFrame().copyTo(currFrame);
mode.res.setWidth(currFrame.cols);
mode.res.setHeight(currFrame.rows);
if (worker->hasFrame())
currFrame = worker->deqeueFrame();
}
void Camera::unlock()
@ -234,15 +271,94 @@ void Camera::unlock()
QSize Camera::resolution()
{
return getVideoMode().res;
}
double Camera::fps()
{
return getVideoMode().fps;
return QSize(currFrame.cols, currFrame.rows);
}
Camera* Camera::getInstance()
{
return Widget::getInstance()->getCamera();
if (!instance)
instance = new Camera();
return instance;
}
// ====================
// WORKER
// ====================
SelfCamWorker::SelfCamWorker(int index)
: clock(nullptr)
, camIndex(index)
{
}
void SelfCamWorker::onStart()
{
clock = new QTimer(this);
clock->setSingleShot(false);
clock->setInterval(1);
connect(clock, &QTimer::timeout, this, &SelfCamWorker::doWork);
clock->start();
cam.open(camIndex);
}
void SelfCamWorker::_suspend()
{
qDebug() << "Suspend";
if (cam.isOpened())
cam.release();
}
void SelfCamWorker::_resume()
{
qDebug() << "Resume";
if (!cam.isOpened())
cam.open(camIndex);
}
void SelfCamWorker::doWork()
{
if (!cam.isOpened())
return;
cam >> frame;
//qDebug() << "Decoding frame";
mutex.lock();
while (qeue.size() > 3)
qeue.dequeue();
qeue.enqueue(frame);
mutex.unlock();
emit newFrameAvailable();
}
bool SelfCamWorker::hasFrame()
{
mutex.lock();
bool b = !qeue.empty();
mutex.unlock();
return b;
}
cv::Mat3b SelfCamWorker::deqeueFrame()
{
mutex.lock();
cv::Mat3b f = qeue.dequeue();
mutex.unlock();
return f;
}
void SelfCamWorker::suspend()
{
QMetaObject::invokeMethod(this, "_suspend");
}
void SelfCamWorker::resume()
{
QMetaObject::invokeMethod(this, "_resume");
}

View File

@ -19,6 +19,7 @@
#include <QImage>
#include <QList>
#include <QQueue>
#include <QMutex>
#include "vpx/vpx_image.h"
#include "opencv2/opencv.hpp"
@ -30,8 +31,40 @@
* the camera only when needed, and giving access to the last frames
**/
class SelfCamWorker : public QObject
{
Q_OBJECT
public:
SelfCamWorker(int index);
void doWork();
bool hasFrame();
cv::Mat3b deqeueFrame();
void suspend();
void resume();
public slots:
void onStart();
signals:
void newFrameAvailable();
private slots:
void _suspend();
void _resume();
private:
QMutex mutex;
QQueue<cv::Mat3b> qeue;
QTimer* clock;
cv::VideoCapture cam;
cv::Mat3b frame;
int camIndex;
};
class Camera : public VideoSource
{
Q_OBJECT
public:
struct VideoMode {
QSize res;
@ -45,10 +78,10 @@ public:
HUE,
};
Camera();
~Camera();
static Camera* getInstance(); ///< Returns the global widget's Camera instance
virtual void subscribe(); ///< Call this once before trying to get frames
virtual void unsubscribe(); ///< Call this once when you don't need frames anymore
cv::Mat getLastFrame(); ///< Get the last captured frame
vpx_image getLastVPXImage(); ///< Convert the last frame to a vpx_image (can be expensive !)
@ -61,20 +94,33 @@ public:
void setProp(Prop prop, double val);
double getProp(Prop prop);
private:
int refcount; ///< Number of users suscribed to the camera
cv::VideoCapture cam; ///< OpenCV camera capture opbject
cv::Mat3b currFrame;
QMutex mutex;
// VideoSource interface
public:
virtual void *getData();
virtual int getDataSize();
virtual void lock();
virtual void unlock();
virtual QSize resolution();
virtual double fps();
virtual void subscribe();
virtual void unsubscribe();
protected:
Camera();
private:
int refcount; ///< Number of users suscribed to the camera
cv::VideoCapture cam; ///< OpenCV camera capture opbject
cv::Mat3b currFrame;
QMutex mutex;
VideoMode mode;
QThread* workerThread;
SelfCamWorker* worker;
static Camera* instance;
private slots:
void onNewFrameAvailable();
};
#endif // CAMERA_H

View File

@ -18,54 +18,59 @@
#include "widget/camera.h"
#include "ui_avsettings.h"
AVForm::AVForm(Camera* Cam) :
GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png")), cam(Cam)
AVForm::AVForm() :
GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png"))
{
bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this);
cam->subscribe();
cam->setVideoMode(cam->getBestVideoMode());
camView = new SelfCamView(cam, this);
//cam->setVideoMode(cam->getBestVideoMode());
camView = new SelfCamView(Camera::getInstance(), this);
bodyUI->videoGroup->layout()->addWidget(camView);
bodyUI->CamViewLayout->addWidget(camView);
auto modes = cam->getVideoModes();
auto modes = Camera::getInstance()->getVideoModes();
for (Camera::VideoMode m : modes)
{
bodyUI->videoModescomboBox->addItem(QString("%1x%2").arg(QString::number(m.res.width())
,QString::number(m.res.height())));
}
bodyUI->ContrastSlider->setValue(cam->getProp(Camera::CONTRAST)*100);
bodyUI->BrightnessSlider->setValue(cam->getProp(Camera::BRIGHTNESS)*100);
bodyUI->SaturationSlider->setValue(cam->getProp(Camera::SATURATION)*100);
bodyUI->HueSlider->setValue(cam->getProp(Camera::HUE)*100);
bodyUI->ContrastSlider->setValue(Camera::getInstance()->getProp(Camera::CONTRAST)*100);
bodyUI->BrightnessSlider->setValue(Camera::getInstance()->getProp(Camera::BRIGHTNESS)*100);
bodyUI->SaturationSlider->setValue(Camera::getInstance()->getProp(Camera::SATURATION)*100);
bodyUI->HueSlider->setValue(Camera::getInstance()->getProp(Camera::HUE)*100);
}
AVForm::~AVForm()
{
delete bodyUI;
}
#include <QDebug>
void AVForm::show()
{
qDebug() << "SHADADASDJASLKDJAKLD";
camView->show();
}
void AVForm::on_ContrastSlider_sliderMoved(int position)
{
cam->setProp(Camera::CONTRAST, position / 100.0);
Camera::getInstance()->setProp(Camera::CONTRAST, position / 100.0);
}
void AVForm::on_SaturationSlider_sliderMoved(int position)
{
cam->setProp(Camera::SATURATION, position / 100.0);
Camera::getInstance()->setProp(Camera::SATURATION, position / 100.0);
}
void AVForm::on_BrightnessSlider_sliderMoved(int position)
{
cam->setProp(Camera::BRIGHTNESS, position / 100.0);
Camera::getInstance()->setProp(Camera::BRIGHTNESS, position / 100.0);
}
void AVForm::on_HueSlider_sliderMoved(int position)
{
cam->setProp(Camera::HUE, position / 100.0);
Camera::getInstance()->setProp(Camera::HUE, position / 100.0);
}
void AVForm::on_videoModescomboBox_currentIndexChanged(const QString &arg1)
@ -74,5 +79,5 @@ void AVForm::on_videoModescomboBox_currentIndexChanged(const QString &arg1)
int w = resStr[0].toInt();
int h = resStr[0].toInt();
cam->setVideoMode(Camera::VideoMode{QSize(w,h),60});
Camera::getInstance()->setVideoMode(Camera::VideoMode{QSize(w,h),60});
}

View File

@ -33,24 +33,22 @@ class AVForm : public GenericForm
{
Q_OBJECT
public:
AVForm(Camera* cam);
AVForm();
~AVForm();
virtual void show();
private slots:
void on_ContrastSlider_sliderMoved(int position);
void on_SaturationSlider_sliderMoved(int position);
void on_BrightnessSlider_sliderMoved(int position);
void on_HueSlider_sliderMoved(int position);
void on_videoModescomboBox_currentIndexChanged(const QString &arg1);
private:
Ui::AVSettings *bodyUI;
Camera* cam;
SelfCamView* camView;
};
#endif

View File

@ -95,6 +95,16 @@
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QVBoxLayout" name="CamViewLayout"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Preview</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -27,7 +27,7 @@ public:
GenericForm(const QString &name, const QPixmap &icon) : formName(name), formIcon(icon) {;}
~GenericForm() {;}
virtual void updateContent() {;}
virtual void present() {}
QString getFormName() {return formName;}
QPixmap getFormIcon() {return formIcon;}

View File

@ -74,7 +74,7 @@ void IdentityForm::onStatusMessageEdited()
Core::getInstance()->setStatusMessage(bodyUI->statusMessage->text());
}
void IdentityForm::updateContent()
void IdentityForm::present()
{
toxId->setText(Core::getInstance()->getSelfId().toString());
}

View File

@ -50,7 +50,7 @@ public:
void setUserName(const QString &name);
void setStatusMessage(const QString &msg);
virtual void updateContent();
virtual void present();
signals:
void userNameChanged(QString);

View File

@ -25,7 +25,7 @@
#include <QTabBar>
#include <QStackedWidget>
SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent)
SettingsWidget::SettingsWidget(QWidget* parent)
: QWidget(parent)
{
body = new QWidget(this);
@ -55,7 +55,7 @@ SettingsWidget::SettingsWidget(Camera* cam, QWidget* parent)
GeneralForm *gfrm = new GeneralForm;
ifrm = new IdentityForm;
PrivacyForm *pfrm = new PrivacyForm;
AVForm *avfrm = new AVForm(cam);
AVForm *avfrm = new AVForm;
GenericForm *cfgForms[] = {gfrm, ifrm, pfrm, avfrm};
for (auto cfgForm : cfgForms)
@ -85,8 +85,8 @@ void SettingsWidget::show(Ui::MainWindow& ui)
void SettingsWidget::onTabChanged(int index)
{
this->settingsWidgets->setCurrentIndex(index);
GenericForm *currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index));
currentWidget->updateContent();
GenericForm* currentWidget = static_cast<GenericForm*>(this->settingsWidgets->widget(index));
currentWidget->present();
nameLabel->setText(currentWidget->getFormName());
imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation));
}

View File

@ -35,7 +35,7 @@ class SettingsWidget : public QWidget
{
Q_OBJECT
public:
SettingsWidget(Camera* cam, QWidget* parent = nullptr);
SettingsWidget(QWidget* parent = nullptr);
~SettingsWidget();
void show(Ui::MainWindow &ui);

View File

@ -29,13 +29,11 @@ SelfCamView::SelfCamView(VideoSource *Source, QWidget* parent)
, program(nullptr)
, textureId(0)
, pboAllocSize(0)
, useNewFrame(false)
, hasSubscribed(false)
{
source->subscribe();
qDebug()<<"NEW:"<<source->resolution();
qDebug()<<"NEW VideoSurface:"<<source->resolution();
setFixedSize(source->resolution());
}
SelfCamView::~SelfCamView()
@ -49,21 +47,38 @@ SelfCamView::~SelfCamView()
source->unsubscribe();
}
void SelfCamView::hideEvent(QHideEvent *ev)
{
if (hasSubscribed)
{
source->unsubscribe();
hasSubscribed = false;
disconnect(source, &VideoSource::frameAvailable, this, &SelfCamView::updateGL);
}
QGLWidget::hideEvent(ev);
}
void SelfCamView::showEvent(QShowEvent *ev)
{
if (!hasSubscribed)
{
source->subscribe();
hasSubscribed = true;
connect(source, &VideoSource::frameAvailable, this, &SelfCamView::updateGL);
}
QGLWidget::showEvent(ev);
}
void SelfCamView::initializeGL()
{
updateTimer = new QTimer(this);
updateTimer->setSingleShot(false);
updateTimer->setInterval(1000.0 / source->fps());
connect(updateTimer, &QTimer::timeout, this, &SelfCamView::update);
updateTimer->start();
}
void SelfCamView::paintGL()
{
source->lock();
void* frame = source->getData();
int frameBytes = source->getDataSize();
//qDebug() << "PainterThread" << QThread::currentThreadId();
if (!pbo)
{
@ -87,7 +102,8 @@ void SelfCamView::paintGL()
"uniform sampler2D texture0;"
"varying vec2 coords;"
"void main() {"
" gl_FragColor = texture2D(texture0,coords*vec2(1.0, -1.0));"
" vec4 color = texture2D(texture0,coords*vec2(1.0, -1.0));"
" gl_FragColor = vec4(color.b, color.g, color.r, 1);"
"}");
program->bindAttributeLocation("vertices", 0);
@ -108,30 +124,42 @@ void SelfCamView::paintGL()
setFixedSize(res);
}
if (pboAllocSize != frameBytes)
if (useNewFrame)
{
qDebug() << "Resize pbo " << frameBytes << "bytes (was" << pboAllocSize << ") res " << source->resolution();
source->lock();
void* frame = source->getData();
int frameBytes = source->getDataSize();
if (pboAllocSize != frameBytes && frameBytes > 0)
{
qDebug() << "Resize pbo " << frameBytes << "bytes (was" << pboAllocSize << ") res " << source->resolution();
pbo->bind();
pbo->allocate(frameBytes);
pbo->release();
pboAllocSize = frameBytes;
}
// transfer data
pbo->bind();
pbo->allocate(frameBytes);
void* ptr = pbo->map(QOpenGLBuffer::WriteOnly);
if (ptr && frame)
memcpy(ptr, frame, frameBytes);
pbo->unmap();
source->unlock();
//transfer pbo data to texture
glBindTexture(GL_TEXTURE_2D, textureId);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0, res.width(), res.height(), GL_RGB, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
pbo->release();
pboAllocSize = frameBytes;
}
// transfer data
pbo->bind();
void* ptr = pbo->map(QOpenGLBuffer::WriteOnly);
if (ptr)
memcpy(ptr, frame, frameBytes);
pbo->unmap();
//transfer pbo data to texture
glBindTexture(GL_TEXTURE_2D, textureId);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0, res.width(), res.height(), GL_RGB, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
// render pbo
float values[] = {
-1, -1,
@ -155,18 +183,20 @@ void SelfCamView::paintGL()
//draw fullscreen quad
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_2D, 0);
program->disableAttributeArray(0);
program->release();
pbo->release();
//glFlush();
source->unlock();
useNewFrame = false;
}
void SelfCamView::update()
void SelfCamView::updateGL()
{
QGLWidget::update();
useNewFrame = true;
QGLWidget::updateGL();
}

View File

@ -32,10 +32,14 @@ public:
SelfCamView(VideoSource* source, QWidget* parent=0);
~SelfCamView();
virtual void hideEvent(QHideEvent* ev);
virtual void showEvent(QShowEvent* ev);
// QGLWidget interface
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void updateGL();
void update();
@ -43,10 +47,12 @@ private:
VideoSource* source;
QOpenGLBuffer* pbo;
QOpenGLShaderProgram* program;
QTimer* updateTimer;
GLuint textureId;
int pboAllocSize;
QSize res;
bool useNewFrame;
bool hasSubscribed;
};
#endif // SELFCAMVIEW_H

View File

@ -100,8 +100,7 @@ Widget::Widget(QWidget *parent)
ui->statusButton->setProperty("status", "offline");
Style::repolish(ui->statusButton);
camera = new Camera;
settingsWidget = new SettingsWidget(camera);
settingsWidget = new SettingsWidget();
// Disable some widgets until we're connected to the DHT
ui->statusButton->setEnabled(false);
@ -117,7 +116,7 @@ Widget::Widget(QWidget *parent)
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
coreThread = new QThread(this);
core = new Core(camera, coreThread);
core = new Core(Camera::getInstance(), coreThread);
core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start);
@ -215,11 +214,6 @@ QString Widget::getUsername()
return core->getUsername();
}
Camera* Widget::getCamera()
{
return camera;
}
void Widget::onAvatarClicked()
{
QString filename = QFileDialog::getOpenFileName(this, tr("Choose a profile picture"), QDir::homePath());

View File

@ -122,7 +122,6 @@ private:
static Widget* instance;
GenericChatroomWidget* activeChatroomWidget;
FriendListWidget* contactListWidget;
Camera* camera;
MaskablePixmapWidget* profilePicture;
bool notify(QObject *receiver, QEvent *event);
};