diff --git a/cameraworker.cpp b/cameraworker.cpp new file mode 100644 index 000000000..b0c964bc5 --- /dev/null +++ b/cameraworker.cpp @@ -0,0 +1,205 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include + +CameraWorker::CameraWorker(int index) + : clock(nullptr) + , camIndex(index) + , refCount(0) +{ +} + +void CameraWorker::onStart() +{ + clock = new QTimer(this); + clock->setSingleShot(false); + clock->setInterval(5); + + 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); + unsubscribe(); + qDebug() << "ASKED " << prop << " VAL " << props[prop]; + } + + return props.value(prop); +} + +void CameraWorker::probeResolutions() +{ + if (resolutions.isEmpty()) + { + subscribe(); + + // probe resolutions (TODO: add more) + QList propbeRes = { + QSize( 160, 120), // QQVGA + QSize( 320, 240), // HVGA + QSize(1024, 768), // XGA + QSize( 432, 240), // WQVGA + QSize( 640, 360), // nHD + }; + + 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 (!resolutions.contains(QSize(w,h))) + resolutions.append(QSize(w,h)); + } + + unsubscribe(); + } + + qDebug() << 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()) + { + cam.open(camIndex); + applyProps(); // restore props + } + } + + refCount++; +} + +void CameraWorker::unsubscribe() +{ + refCount--; + + if(refCount <= 0) + { + cam.release(); + } +} + +void CameraWorker::doWork() +{ + if (!cam.isOpened()) + return; + + if (queue.size() > 3) + { + queue.dequeue(); + return; + } + + cam >> frame; +//qDebug() << "Decoding frame"; + mutex.lock(); + + queue.enqueue(frame); + mutex.unlock(); + + emit newFrameAvailable(); +} + +bool CameraWorker::hasFrame() +{ + mutex.lock(); + bool b = !queue.empty(); + mutex.unlock(); + + return b; +} + +cv::Mat3b CameraWorker::dequeueFrame() +{ + mutex.lock(); + cv::Mat3b f = queue.dequeue(); + mutex.unlock(); + + return f; +} + +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)); +} + +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; +} diff --git a/cameraworker.h b/cameraworker.h new file mode 100644 index 000000000..47fb10f87 --- /dev/null +++ b/cameraworker.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include +#include +#include +#include + +#include "opencv2/opencv.hpp" + +class QTimer; + +class CameraWorker : public QObject +{ + Q_OBJECT +public: + CameraWorker(int index); + void doWork(); + bool hasFrame(); + cv::Mat3b dequeueFrame(); + + void suspend(); + void resume(); + void setProp(int prop, double val); + double getProp(int prop); // blocking call! + void probeResolutions(); + +public slots: + void onStart(); + +signals: + void started(); + void newFrameAvailable(); + void resProbingFinished(QList res); + +private slots: + void _suspend(); + void _resume(); + void _setProp(int prop, double val); + double _getProp(int prop); + +private: + void applyProps(); + void subscribe(); + void unsubscribe(); + +private: + QMutex mutex; + QQueue queue; + QTimer* clock; + cv::VideoCapture cam; + cv::Mat3b frame; + int camIndex; + QMap props; + QList resolutions; + int refCount; +}; + +#endif // CAMERAWORKER_H diff --git a/coreav.cpp b/coreav.cpp index fa4ce5cd8..b7aae0103 100644 --- a/coreav.cpp +++ b/coreav.cpp @@ -57,7 +57,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled if (calls[callId].videoEnabled) { calls[callId].sendVideoTimer->start(); - Camera::getInstance()->suscribe(); + Camera::getInstance()->subscribe(); } } @@ -73,12 +73,12 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) { calls[callId].videoEnabled = false; calls[callId].sendVideoTimer->stop(); - Camera::getInstance()->unsuscribe(); + Camera::getInstance()->unsubscribe(); emit ((Core*)core)->avMediaChange(friendId, callId, false); } else { - Camera::getInstance()->suscribe(); + Camera::getInstance()->subscribe(); calls[callId].videoEnabled = true; calls[callId].sendVideoTimer->start(); emit ((Core*)core)->avMediaChange(friendId, callId, true); @@ -161,7 +161,7 @@ void Core::cleanupCall(int callId) calls[callId].sendAudioTimer->stop(); calls[callId].sendVideoTimer->stop(); if (calls[callId].videoEnabled) - Camera::getInstance()->unsuscribe(); + Camera::getInstance()->unsubscribe(); alcCaptureStop(alInDev); } diff --git a/qtox.pro b/qtox.pro index 14e233ad1..d6f0e1969 100644 --- a/qtox.pro +++ b/qtox.pro @@ -20,7 +20,7 @@ # See the COPYING file for more details. -QT += core gui network xml +QT += core gui network xml opengl greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = qtox @@ -68,7 +68,7 @@ win32 { LIBS += -Wl,-Bdynamic -ltbb -lv4l1 -lv4l2 -lgnutls -lrtmp -lgnutls -lavformat -lavcodec -lavutil -lavfilter -lswscale -lusb-1.0 } else { - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc } contains(JENKINS, YES) { @@ -111,7 +111,6 @@ HEADERS += widget/form/addfriendform.h \ friendlist.h \ misc/cdata.h \ misc/cstring.h \ - widget/selfcamview.h \ widget/camera.h \ widget/netcamview.h \ misc/smileypack.h \ @@ -132,7 +131,10 @@ HEADERS += widget/form/addfriendform.h \ widget/tool/chatactions/filetransferaction.h \ widget/tool/chatactions/systemmessageaction.h \ widget/tool/chatactions/actionaction.h \ - widget/maskablepixmapwidget.h + widget/maskablepixmapwidget.h \ + videosource.h \ + cameraworker.h \ + widget/videosurface.h SOURCES += \ widget/form/addfriendform.cpp \ @@ -158,7 +160,6 @@ SOURCES += \ misc/settings.cpp \ misc/cdata.cpp \ misc/cstring.cpp \ - widget/selfcamview.cpp \ widget/camera.cpp \ widget/netcamview.cpp \ misc/smileypack.cpp \ @@ -178,4 +179,6 @@ SOURCES += \ widget/tool/chatactions/filetransferaction.cpp \ widget/tool/chatactions/systemmessageaction.cpp \ widget/tool/chatactions/actionaction.cpp \ - widget/maskablepixmapwidget.cpp + widget/maskablepixmapwidget.cpp \ + cameraworker.cpp \ + widget/videosurface.cpp diff --git a/videosource.h b/videosource.h new file mode 100644 index 000000000..f6c11592c --- /dev/null +++ b/videosource.h @@ -0,0 +1,27 @@ +#ifndef VIDEOSOURCE_H +#define VIDEOSOURCE_H + +#include +#include + +class VideoSource : public QObject +{ + Q_OBJECT +public: + 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; // resolution of a frame + + virtual void subscribe() = 0; + virtual void unsubscribe() = 0; + +signals: + void frameAvailable(); + +}; + +#endif // VIDEOSOURCE_H diff --git a/widget/camera.cpp b/widget/camera.cpp index dab371005..a92e3d256 100644 --- a/widget/camera.cpp +++ b/widget/camera.cpp @@ -16,68 +16,72 @@ #include "camera.h" #include "widget.h" +#include "cameraworker.h" +#include +#include -using namespace cv; +Camera* Camera::instance = nullptr; Camera::Camera() - : refcount{0} + : 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(workerThread, &QThread::deleteLater, worker, &CameraWorker::deleteLater); + connect(worker, &CameraWorker::started, this, &Camera::onWorkerStarted); + connect(worker, &CameraWorker::newFrameAvailable, this, &Camera::onNewFrameAvailable); + connect(worker, &CameraWorker::resProbingFinished, this, &Camera::onResProbingFinished); + workerThread->start(); } -void Camera::suscribe() +void Camera::onWorkerStarted() +{ + worker->probeResolutions(); +} + +Camera::~Camera() +{ + workerThread->exit(); + workerThread->deleteLater(); +} + +void Camera::subscribe() { if (refcount <= 0) - { - refcount = 1; - cam.open(0); - } - else - refcount++; + worker->resume(); + + refcount++; } -void Camera::unsuscribe() +void Camera::unsubscribe() { refcount--; if (refcount <= 0) { - cam.release(); + worker->suspend(); refcount = 0; } } -Mat Camera::getLastFrame() -{ - Mat frame; - cam >> frame; - return frame; -} - -QImage Camera::getLastImage() -{ - Mat3b src = getLastFrame(); - QImage dest(src.cols, src.rows, QImage::Format_ARGB32); - for (int y = 0; y < src.rows; ++y) - { - const cv::Vec3b *srcrow = src[y]; - QRgb *destrow = (QRgb*)dest.scanLine(y); - for (int x = 0; x < src.cols; ++x) - destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255); - } - return dest; -} - vpx_image Camera::getLastVPXImage() { - Mat3b frame = getLastFrame(); + lock(); vpx_image img; - int w = frame.size().width, h = frame.size().height; + int w = currFrame.size().width, h = currFrame.size().height; vpx_img_alloc(&img, VPX_IMG_FMT_I420, w, h, 1); // I420 == YUV420P, same as YV12 with U and V switched size_t i=0, j=0; for( int line = 0; line < h; ++line ) { - const cv::Vec3b *srcrow = frame[line]; + const cv::Vec3b *srcrow = currFrame[line]; if( !(line % 2) ) { for( int x = 0; x < w; x += 2 ) @@ -112,10 +116,123 @@ vpx_image Camera::getLastVPXImage() } } } + unlock(); return img; } +QList Camera::getSupportedResolutions() +{ + return resolutions; +} + +QSize Camera::getBestVideoMode() +{ + int bestScore = 0; + QSize bestRes; + + for (QSize res : getSupportedResolutions()) + { + int score = res.width() * res.height(); + + if (score > bestScore) + { + bestScore = score; + bestRes = res; + } + } + + return bestRes; +} + +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::getResolution() +{ + return QSize(worker->getProp(CV_CAP_PROP_FRAME_WIDTH), worker->getProp(CV_CAP_PROP_FRAME_HEIGHT)); +} + +void Camera::setProp(Camera::Prop prop, double val) +{ + switch (prop) + { + case BRIGHTNESS: + worker->setProp(CV_CAP_PROP_BRIGHTNESS, val); + break; + case SATURATION: + worker->setProp(CV_CAP_PROP_SATURATION, val); + break; + case CONTRAST: + worker->setProp(CV_CAP_PROP_CONTRAST, val); + break; + case HUE: + worker->setProp(CV_CAP_PROP_HUE, val); + break; + } +} + +double Camera::getProp(Camera::Prop prop) +{ + switch (prop) + { + case BRIGHTNESS: + return worker->getProp(CV_CAP_PROP_BRIGHTNESS); + case SATURATION: + return worker->getProp(CV_CAP_PROP_SATURATION); + case CONTRAST: + return worker->getProp(CV_CAP_PROP_CONTRAST); + case HUE: + return worker->getProp(CV_CAP_PROP_HUE); + } + + return 0.0; +} + +void Camera::onNewFrameAvailable() +{ + emit frameAvailable(); +} + +void Camera::onResProbingFinished(QList res) +{ + resolutions = res; +} + +void *Camera::getData() +{ + return currFrame.data; +} + +int Camera::getDataSize() +{ + return currFrame.total() * currFrame.channels(); +} + +void Camera::lock() +{ + mutex.lock(); + + if (worker->hasFrame()) + currFrame = worker->dequeueFrame(); +} + +void Camera::unlock() +{ + mutex.unlock(); +} + +QSize Camera::resolution() +{ + return QSize(currFrame.cols, currFrame.rows); +} + Camera* Camera::getInstance() { - return Widget::getInstance()->getCamera(); + if (!instance) + instance = new Camera(); + + return instance; } diff --git a/widget/camera.h b/widget/camera.h index e48307d41..5503f696c 100644 --- a/widget/camera.h +++ b/widget/camera.h @@ -18,8 +18,13 @@ #define CAMERA_H #include +#include +#include #include "vpx/vpx_image.h" #include "opencv2/opencv.hpp" +#include "videosource.h" + +class CameraWorker; /** * This class is a wrapper to share a camera's captured video frames @@ -27,20 +32,60 @@ * the camera only when needed, and giving access to the last frames **/ -class Camera +class Camera : public VideoSource { + Q_OBJECT public: - Camera(); + enum Prop { + BRIGHTNESS, + SATURATION, + CONTRAST, + HUE, + }; + + ~Camera(); + static Camera* getInstance(); ///< Returns the global widget's Camera instance - void suscribe(); ///< Call this once before trying to get frames - void unsuscribe(); ///< Call this once when you don't need frames anymore - cv::Mat getLastFrame(); ///< Get the last captured frame - QImage getLastImage(); ///< Convert the last frame to a QImage (can be expensive !) vpx_image getLastVPXImage(); ///< Convert the last frame to a vpx_image (can be expensive !) + QList getSupportedResolutions(); + QSize getBestVideoMode(); + + void setResolution(QSize res); + QSize getResolution(); + + void setProp(Prop prop, double val); + double getProp(Prop prop); + + // VideoSource interface + virtual void *getData(); + virtual int getDataSize(); + virtual void lock(); + virtual void unlock(); + virtual QSize resolution(); + 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; + + QThread* workerThread; + CameraWorker* worker; + + QList resolutions; + + static Camera* instance; + +private slots: + void onWorkerStarted(); + void onNewFrameAvailable(); + void onResProbingFinished(QList res); + }; #endif // CAMERA_H diff --git a/widget/form/settings/avform.cpp b/widget/form/settings/avform.cpp index 4b7afef25..4745bac2c 100644 --- a/widget/form/settings/avform.cpp +++ b/widget/form/settings/avform.cpp @@ -18,17 +18,16 @@ #include "widget/camera.h" #include "ui_avsettings.h" -AVForm::AVForm(Camera* cam) : +AVForm::AVForm() : GenericForm(tr("Audio/Video settings"), QPixmap(":/img/settings/av.png")) { bodyUI = new Ui::AVSettings; bodyUI->setupUi(this); - camView = new SelfCamView(cam, this); - bodyUI->videoGroup->layout()->addWidget(camView); - camView->hide(); // hide by default + //cam->setVideoMode(cam->getBestVideoMode()); + camView = new VideoSurface(Camera::getInstance(), this); - connect(bodyUI->testVideoBtn, &QPushButton::clicked, this, &AVForm::onTestVideoPressed); + bodyUI->CamViewLayout->addWidget(camView); } AVForm::~AVForm() @@ -36,22 +35,44 @@ AVForm::~AVForm() delete bodyUI; } -void AVForm::showTestVideo() +void AVForm::present() { - bodyUI->testVideoBtn->setText(tr("Hide video preview","On a button")); - camView->show(); + bodyUI->videoModescomboBox->clear(); + QList res = Camera::getInstance()->getSupportedResolutions(); + for (QSize r : res) + bodyUI->videoModescomboBox->addItem(QString("%1x%2").arg(QString::number(r.width()),QString::number(r.height()))); + + 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); } -void AVForm::closeTestVideo() +void AVForm::on_ContrastSlider_sliderMoved(int position) { - bodyUI->testVideoBtn->setText(tr("Show video preview","On a button")); - camView->close(); + Camera::getInstance()->setProp(Camera::CONTRAST, position / 100.0); } -void AVForm::onTestVideoPressed() +void AVForm::on_SaturationSlider_sliderMoved(int position) { - if (camView->isVisible()) - closeTestVideo(); - else - showTestVideo(); + Camera::getInstance()->setProp(Camera::SATURATION, position / 100.0); +} + +void AVForm::on_BrightnessSlider_sliderMoved(int position) +{ + Camera::getInstance()->setProp(Camera::BRIGHTNESS, position / 100.0); +} + +void AVForm::on_HueSlider_sliderMoved(int position) +{ + Camera::getInstance()->setProp(Camera::HUE, position / 100.0); +} + +void AVForm::on_videoModescomboBox_currentIndexChanged(const QString &arg1) +{ + QStringList resStr = arg1.split("x"); + int w = resStr[0].toInt(); + int h = resStr[0].toInt(); + + Camera::getInstance()->setResolution(QSize(w,h)); } diff --git a/widget/form/settings/avform.h b/widget/form/settings/avform.h index e75f742da..978506418 100644 --- a/widget/form/settings/avform.h +++ b/widget/form/settings/avform.h @@ -18,7 +18,7 @@ #define AVFORM_H #include "genericsettings.h" -#include "widget/selfcamview.h" +#include "widget/videosurface.h" #include #include #include @@ -33,20 +33,21 @@ class AVForm : public GenericForm { Q_OBJECT public: - AVForm(Camera* cam); + AVForm(); ~AVForm(); + virtual void present(); private slots: - void onTestVideoPressed(); + + 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; - - SelfCamView* camView; - - void showTestVideo(); - void closeTestVideo(); - + VideoSurface* camView; }; #endif diff --git a/widget/form/settings/avsettings.ui b/widget/form/settings/avsettings.ui index 77fbb18b4..20ebbf43e 100644 --- a/widget/form/settings/avsettings.ui +++ b/widget/form/settings/avsettings.ui @@ -6,27 +6,143 @@ 0 0 - 400 - 300 + 394 + 391 Form + + + + Volume Settings (Stubs) + + + + + + Playback + + + + + + + Qt::Horizontal + + + + + + + Microphone + + + + + + + Qt::Horizontal + + + + + + Video settings - - - + + + QFormLayout::ExpandingFieldsGrow + + + - Show video preview + Modes + + + + + 0 + 0 + + + + + + + + Hue + + + + + + + Qt::Horizontal + + + + + + + Brightness + + + + + + + Qt::Horizontal + + + + + + + Saturation + + + + + + + Qt::Horizontal + + + + + + + Contrast + + + + + + + Qt::Horizontal + + + + + + + Preview + + + + + + @@ -38,7 +154,7 @@ 20 - 40 + 75 diff --git a/widget/form/settings/genericsettings.h b/widget/form/settings/genericsettings.h index e79ec7b06..ba3791ada 100644 --- a/widget/form/settings/genericsettings.h +++ b/widget/form/settings/genericsettings.h @@ -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;} diff --git a/widget/form/settings/identityform.cpp b/widget/form/settings/identityform.cpp index 5aff0e513..041fbdc94 100644 --- a/widget/form/settings/identityform.cpp +++ b/widget/form/settings/identityform.cpp @@ -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()); } diff --git a/widget/form/settings/identityform.h b/widget/form/settings/identityform.h index 2a4b07a77..abbbf3a2c 100644 --- a/widget/form/settings/identityform.h +++ b/widget/form/settings/identityform.h @@ -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); diff --git a/widget/form/settingswidget.cpp b/widget/form/settingswidget.cpp index b7539a4c5..6ef030f53 100644 --- a/widget/form/settingswidget.cpp +++ b/widget/form/settingswidget.cpp @@ -25,7 +25,7 @@ #include #include -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(this->settingsWidgets->widget(index)); - currentWidget->updateContent(); + GenericForm* currentWidget = static_cast(this->settingsWidgets->widget(index)); + currentWidget->present(); nameLabel->setText(currentWidget->getFormName()); imgLabel->setPixmap(currentWidget->getFormIcon().scaledToHeight(40, Qt::SmoothTransformation)); } diff --git a/widget/form/settingswidget.h b/widget/form/settingswidget.h index 431e52c1c..6dca68b82 100644 --- a/widget/form/settingswidget.h +++ b/widget/form/settingswidget.h @@ -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); diff --git a/widget/selfcamview.cpp b/widget/selfcamview.cpp deleted file mode 100644 index 7e9c3d252..000000000 --- a/widget/selfcamview.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2014 by Project Tox - - 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 "selfcamview.h" -#include "camera.h" -#include -#include -#include -#include -#include -#include - -using namespace cv; - -SelfCamView::SelfCamView(Camera* Cam, QWidget* parent) - : QWidget(parent), displayLabel{new QLabel}, - mainLayout{new QHBoxLayout()}, cam(Cam), updateDisplayTimer{new QTimer} -{ - setLayout(mainLayout); - setWindowTitle(SelfCamView::tr("Tox video test","Title of the window to test the video/webcam")); - setMinimumSize(320,240); - - updateDisplayTimer->setInterval(5); - updateDisplayTimer->setSingleShot(false); - - displayLabel->setAlignment(Qt::AlignCenter); - - mainLayout->addWidget(displayLabel); - - connect(updateDisplayTimer, SIGNAL(timeout()), this, SLOT(updateDisplay())); -} - -void SelfCamView::closeEvent(QCloseEvent* event) -{ - cam->unsuscribe(); - updateDisplayTimer->stop(); - event->accept(); -} - -void SelfCamView::showEvent(QShowEvent* event) -{ - cam->suscribe(); - updateDisplayTimer->start(); - event->accept(); -} - -void SelfCamView::updateDisplay() -{ - displayLabel->setPixmap(QPixmap::fromImage(cam->getLastImage()).scaled(displayLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); -} - -void SelfCamView::resizeEvent(QResizeEvent *e) -{ - Q_UNUSED(e) - updateDisplay(); -} diff --git a/widget/videosurface.cpp b/widget/videosurface.cpp new file mode 100644 index 000000000..bd49b49c0 --- /dev/null +++ b/widget/videosurface.cpp @@ -0,0 +1,197 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 "videosurface.h" +#include "camera.h" +#include +#include +#include +#include +#include + +VideoSurface::VideoSurface(VideoSource *Source, QWidget* parent) + : QGLWidget(QGLFormat(QGL::SampleBuffers), parent) + , source(Source) + , pbo(nullptr) + , program(nullptr) + , textureId(0) + , pboAllocSize(0) + , uploadFrame(false) + , hasSubscribed(false) +{ + setFixedSize(source->resolution()); +} + +VideoSurface::~VideoSurface() +{ + if (pbo) + delete pbo; + + if (textureId != 0) + glDeleteTextures(1, &textureId); + + source->unsubscribe(); +} + +void VideoSurface::hideEvent(QHideEvent *ev) +{ + if (hasSubscribed) + { + source->unsubscribe(); + hasSubscribed = false; + disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL); + } + + QGLWidget::hideEvent(ev); +} + +void VideoSurface::showEvent(QShowEvent *ev) +{ + if (!hasSubscribed) + { + source->subscribe(); + hasSubscribed = true; + connect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL); + } + + QGLWidget::showEvent(ev); +} + +void VideoSurface::initializeGL() +{ + +} + +void VideoSurface::paintGL() +{ + if (!pbo) + { + qDebug() << "Creating pbo, program"; + + // pbo + pbo = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer); + pbo->setUsagePattern(QOpenGLBuffer::StreamDraw); + pbo->create(); + + // shaders + program = new QOpenGLShaderProgram; + program->addShaderFromSourceCode(QOpenGLShader::Vertex, + "attribute vec4 vertices;" + "varying vec2 coords;" + "void main() {" + " gl_Position = vec4(vertices.xy,0.0,1.0);" + " coords = vertices.xy*vec2(0.5,0.5)+vec2(0.5,0.5);" + "}"); + program->addShaderFromSourceCode(QOpenGLShader::Fragment, + "uniform sampler2D texture0;" + "varying vec2 coords;" + "void main() {" + " vec4 color = texture2D(texture0,coords*vec2(1.0, -1.0));" + " gl_FragColor = vec4(color.b, color.g, color.r, 1);" + "}"); + + program->bindAttributeLocation("vertices", 0); + program->link(); + } + + if (res != source->resolution()) + { + qDebug() << "Change resolution " << res << " to " << source->resolution(); + res = source->resolution(); + + // a texture used to render the pbo (has the match the pixelformat of the source) + glGenTextures(1,&textureId); + glBindTexture(GL_TEXTURE_2D, textureId); + glTexImage2D(GL_TEXTURE_2D,0, GL_RGB, res.width(), res.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + setFixedSize(res); + } + + + if (uploadFrame) + { + 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(); + + 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(); + + uploadFrame = false; + } + + // render pbo + float values[] = { + -1, -1, + 1, -1, + -1, 1, + 1, 1 + }; + + program->setAttributeArray(0, GL_FLOAT, values, 2); + + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, width(), height()); + + program->bind(); + program->enableAttributeArray(0); + + glBindTexture(GL_TEXTURE_2D, textureId); + + //draw fullscreen quad + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindTexture(GL_TEXTURE_2D, 0); + + program->disableAttributeArray(0); + program->release(); +} + +void VideoSurface::updateGL() +{ + uploadFrame = true; + QGLWidget::updateGL(); +} + + diff --git a/widget/selfcamview.h b/widget/videosurface.h similarity index 53% rename from widget/selfcamview.h rename to widget/videosurface.h index 26a8b315c..7de304682 100644 --- a/widget/selfcamview.h +++ b/widget/videosurface.h @@ -17,39 +17,42 @@ #ifndef SELFCAMVIEW_H #define SELFCAMVIEW_H -#include +#include -class QCloseEvent; -class QShowEvent; -class QPainter; -class Camera; -class QLabel; -class QHBoxLayout; +class QOpenGLBuffer; +class QOpenGLShaderProgram; class QTimer; +class VideoSource; -class SelfCamView : public QWidget +class VideoSurface : public QGLWidget { Q_OBJECT public: - SelfCamView(Camera* Cam, QWidget *parent=0); + VideoSurface(VideoSource* source, QWidget* parent=0); + ~VideoSurface(); -private slots: - void updateDisplay(); - -private: - void closeEvent(QCloseEvent*); - void showEvent(QShowEvent*); - void paint(QPainter *painter); + virtual void hideEvent(QHideEvent* ev); + virtual void showEvent(QShowEvent* ev); + // QGLWidget interface protected: - void resizeEvent(QResizeEvent *e); + virtual void initializeGL(); + virtual void paintGL(); + virtual void updateGL(); + + void update(); private: - QLabel *displayLabel; - QHBoxLayout* mainLayout; - Camera* cam; - QTimer* updateDisplayTimer; + VideoSource* source; + QOpenGLBuffer* pbo; + QOpenGLShaderProgram* program; + GLuint textureId; + int pboAllocSize; + QSize res; + bool uploadFrame; + bool hasSubscribed; + }; #endif // SELFCAMVIEW_H diff --git a/widget/widget.cpp b/widget/widget.cpp index 189b43f40..20efebaec 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -27,7 +27,6 @@ #include "widget/groupwidget.h" #include "widget/form/groupchatform.h" #include "misc/style.h" -#include "selfcamview.h" #include "widget/friendlistwidget.h" #include "camera.h" #include "widget/form/chatform.h" @@ -102,8 +101,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); @@ -119,7 +117,7 @@ Widget::Widget(QWidget *parent) qRegisterMetaType("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); @@ -218,11 +216,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()); diff --git a/widget/widget.h b/widget/widget.h index a973ca818..84423f26b 100644 --- a/widget/widget.h +++ b/widget/widget.h @@ -34,7 +34,7 @@ class GenericChatroomWidget; class Group; struct Friend; class QSplitter; -class SelfCamView; +class VideoSurface; class QMenu; class Core; class Camera; @@ -124,7 +124,6 @@ private: static Widget* instance; GenericChatroomWidget* activeChatroomWidget; FriendListWidget* contactListWidget; - Camera* camera; MaskablePixmapWidget* profilePicture; bool notify(QObject *receiver, QEvent *event); };