1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

simplified VideoSource, interface, BRG->YUV conversion changes + more

This commit is contained in:
krepa098 2014-10-14 10:57:45 +02:00
parent b43d7197ed
commit 8b8a541826
8 changed files with 226 additions and 217 deletions

View File

@ -24,6 +24,7 @@ CameraWorker::CameraWorker(int index)
, camIndex(index)
, refCount(0)
{
qRegisterMetaType<VideoFrame>();
}
void CameraWorker::onStart()
@ -147,42 +148,15 @@ void CameraWorker::doWork()
if (!cam.isOpened())
return;
if (queue.size() > 3)
return;
if (!cam.read(frame))
{
cam.release();
qDebug() << "CameraWorker: received empty frame -> closing";
qDebug() << "CameraWorker: Cannot read frame";
return;
}
mutex.lock();
queue.enqueue(frame);
mutex.unlock();
QByteArray frameData(reinterpret_cast<char*>(frame.data), frame.total() * frame.channels());
emit newFrameAvailable();
}
bool CameraWorker::hasFrame()
{
mutex.lock();
bool b = !queue.empty();
mutex.unlock();
return b;
}
cv::Mat3b CameraWorker::dequeueFrame()
{
if (queue.isEmpty())
return cv::Mat3b();
mutex.lock();
cv::Mat3b f = queue.dequeue();
mutex.unlock();
return f;
emit newFrameAvailable(VideoFrame{frameData, QSize(frame.cols, frame.rows), VideoFrame::BGR});
}
void CameraWorker::suspend()

View File

@ -25,6 +25,7 @@
#include <QSize>
#include "opencv2/opencv.hpp"
#include "videosource.h"
class QTimer;
@ -34,8 +35,6 @@ class CameraWorker : public QObject
public:
CameraWorker(int index);
void doWork();
bool hasFrame();
cv::Mat3b dequeueFrame();
void suspend();
void resume();
@ -48,7 +47,7 @@ public slots:
signals:
void started();
void newFrameAvailable();
void newFrameAvailable(const VideoFrame frame);
void resProbingFinished(QList<QSize> res);
private slots:

View File

@ -3,24 +3,54 @@
#include <QObject>
#include <QSize>
#include <QRgb>
struct VideoFrame
{
enum ColorFormat
{
BGR,
YUV,
};
QByteArray data;
QSize resolution;
ColorFormat format;
void setNull()
{
data = QByteArray();
}
bool isNull()
{
return data.isEmpty();
}
// assumes format is BGR
QRgb getPixel(int x, int y)
{
char b = data.data()[resolution.width() * 3 * y + x * 3 + 0];
char g = data.data()[resolution.width() * 3 * y + x * 3 + 1];
char r = data.data()[resolution.width() * 3 * y + x * 3 + 2];
return qRgb(r, g, b);
}
};
Q_DECLARE_METATYPE(VideoFrame)
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;
virtual VideoFrame::ColorFormat getColorFormat() = 0;
signals:
void frameAvailable();
void frameAvailable(const VideoFrame frame);
};

View File

@ -19,6 +19,7 @@
#include "src/cameraworker.h"
#include <QDebug>
#include <QThread>
#include <QMutexLocker>
Camera* Camera::instance = nullptr;
@ -70,52 +71,58 @@ void Camera::unsubscribe()
}
}
VideoFrame::ColorFormat Camera::getColorFormat()
{
return VideoFrame::BGR;
}
vpx_image Camera::getLastVPXImage()
{
lock();
QMutexLocker lock(&mutex);
vpx_image img;
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 )
if (currFrame.isNull())
{
const cv::Vec3b *srcrow = currFrame[line];
if( !(line % 2) )
img.w = 0;
img.h = 0;
return img;
}
int w = currFrame.resolution.width();
int h = currFrame.resolution.height();
// I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes."
// http://fourcc.org/yuv.php#IYUV
vpx_img_alloc(&img, VPX_IMG_FMT_VPXI420, w, h, 1);
for (int x = 0; x < w; x += 2)
{
for (int y = 0; y < h; y += 2)
{
for( int x = 0; x < w; x += 2 )
QRgb p1 = currFrame.getPixel(x, y);
QRgb p2 = currFrame.getPixel(x + 1, y);
QRgb p3 = currFrame.getPixel(x, y + 1);
QRgb p4 = currFrame.getPixel(x + 1, y + 1);
img.planes[VPX_PLANE_Y][x + y * w] = ((66 * qRed(p1) + 129 * qGreen(p1) + 25 * qBlue(p1)) >> 8) + 16;
img.planes[VPX_PLANE_Y][x + 1 + y * w] = ((66 * qRed(p2) + 129 * qGreen(p2) + 25 * qBlue(p2)) >> 8) + 16;
img.planes[VPX_PLANE_Y][x + (y + 1) * w] = ((66 * qRed(p3) + 129 * qGreen(p3) + 25 * qBlue(p3)) >> 8) + 16;
img.planes[VPX_PLANE_Y][x + 1 + (y + 1) * w] = ((66 * qRed(p4) + 129 * qGreen(p4) + 25 * qBlue(p4)) >> 8) + 16;
if (!(x % 2) && !(y % 2))
{
uint8_t r = srcrow[x][2];
uint8_t g = srcrow[x][1];
uint8_t b = srcrow[x][0];
// TODO: consider p1 to p4?
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
img.planes[VPX_PLANE_V][j] = ((-38*r + -74*g + 112*b) >> 8) + 128;
img.planes[VPX_PLANE_U][j] = ((112*r + -94*g + -18*b) >> 8) + 128;
i++;
j++;
int i = x / 2;
int j = y / 2;
r = srcrow[x+1][2];
g = srcrow[x+1][1];
b = srcrow[x+1][0];
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
i++;
}
}
else
{
for( int x = 0; x < w; x += 1 )
{
uint8_t r = srcrow[x][2];
uint8_t g = srcrow[x][1];
uint8_t b = srcrow[x][0];
img.planes[VPX_PLANE_Y][i] = ((66*r + 129*g + 25*b) >> 8) + 16;
i++;
img.planes[VPX_PLANE_U][i + j * w / 2] = ((112 * qRed(p1) + -94 * qGreen(p1) + -18 * qBlue(p1)) >> 8) + 128;
img.planes[VPX_PLANE_V][i + j * w / 2] = ((-38 * qRed(p1) + -74 * qGreen(p1) + 112 * qBlue(p1)) >> 8) + 128;
}
}
}
unlock();
return img;
}
@ -190,9 +197,13 @@ double Camera::getProp(Camera::Prop prop)
return 0.0;
}
void Camera::onNewFrameAvailable()
void Camera::onNewFrameAvailable(const VideoFrame frame)
{
emit frameAvailable();
emit frameAvailable(frame);
mutex.lock();
currFrame = frame;
mutex.unlock();
}
void Camera::onResProbingFinished(QList<QSize> res)
@ -200,34 +211,6 @@ void Camera::onResProbingFinished(QList<QSize> 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()
{
if (!instance)

View File

@ -58,20 +58,16 @@ public:
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();
virtual VideoFrame::ColorFormat getColorFormat();
protected:
Camera();
private:
int refcount; ///< Number of users suscribed to the camera
cv::Mat3b currFrame;
VideoFrame currFrame;
QMutex mutex;
QThread* workerThread;
@ -83,7 +79,7 @@ private:
private slots:
void onWorkerStarted();
void onNewFrameAvailable();
void onNewFrameAvailable(const VideoFrame frame);
void onResProbingFinished(QList<QSize> res);
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>387</height>
<width>382</width>
<height>379</height>
</rect>
</property>
<property name="windowTitle">
@ -54,7 +54,7 @@
<item>
<widget class="QGroupBox" name="videoGroup">
<property name="title">
<string>Video settings</string>
<string>Video Settings</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -90,7 +90,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Modes</string>
<string>Resolution</string>
</property>
</widget>
</item>

View File

@ -21,19 +21,19 @@
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QDebug>
#include <QElapsedTimer>
VideoSurface::VideoSurface(QWidget* parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers | QGL::SingleBuffer), parent)
, source(nullptr)
, pbo(nullptr)
, program(nullptr)
, pbo{nullptr, nullptr}
, bgrProgramm(nullptr)
, textureId(0)
, pboAllocSize(0)
, uploadFrame(false)
, hasSubscribed(false)
, lastWidth(0)
, pboIndex(0)
{
setAutoBufferSwap(false);
}
VideoSurface::VideoSurface(VideoSource *Source, QWidget* parent)
@ -44,8 +44,11 @@ VideoSurface::VideoSurface(VideoSource *Source, QWidget* parent)
VideoSurface::~VideoSurface()
{
if (pbo)
delete pbo;
if (pbo[0])
{
delete pbo[0];
delete pbo[1];
}
if (textureId != 0)
glDeleteTextures(1, &textureId);
@ -61,25 +64,13 @@ void VideoSurface::setSource(VideoSource *src)
void VideoSurface::hideEvent(QHideEvent *ev)
{
if (source && hasSubscribed)
{
source->unsubscribe();
hasSubscribed = false;
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL);
}
unsubscribe();
QGLWidget::hideEvent(ev);
}
void VideoSurface::showEvent(QShowEvent *ev)
{
if (source && !hasSubscribed)
{
source->subscribe();
hasSubscribed = true;
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::updateGL);
}
subscribe();
QGLWidget::showEvent(ev);
}
@ -90,7 +81,38 @@ QSize VideoSurface::sizeHint() const
void VideoSurface::initializeGL()
{
qDebug() << "VideoSurface: Init";
// pbo
pbo[0] = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer);
pbo[0]->setUsagePattern(QOpenGLBuffer::StreamDraw);
pbo[0]->create();
pbo[1] = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer);
pbo[1]->setUsagePattern(QOpenGLBuffer::StreamDraw);
pbo[1]->create();
// shaders
bgrProgramm = new QOpenGLShaderProgram;
bgrProgramm->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);"
"}");
// brg frag-shader
bgrProgramm->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);"
"}");
bgrProgramm->bindAttributeLocation("vertices", 0);
bgrProgramm->link();
}
void VideoSurface::paintGL()
@ -98,42 +120,13 @@ void VideoSurface::paintGL()
if (!source)
return;
if (!pbo)
mutex.lock();
VideoFrame currFrame = frame;
mutex.unlock();
if (res != currFrame.resolution)
{
qDebug() << "VideoSurface: Init";
// 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);"
"}");
// brg frag-shader
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() << "VideoSurface: Change resolution from " << res << " to " << source->resolution();
res = source->resolution();
res = currFrame.resolution;
// a texture used to render the pbo (has the match the pixelformat of the source)
glGenTextures(1,&textureId);
@ -145,53 +138,58 @@ void VideoSurface::paintGL()
}
if (uploadFrame)
if (!currFrame.isNull())
{
source->lock();
void* frame = source->getData();
int frameBytes = source->getDataSize();
QElapsedTimer timer;
timer.start();
if (pboAllocSize != frameBytes && frameBytes > 0)
pboIndex = (pboIndex + 1) % 2;
int nextPboIndex = (pboIndex + 1) % 2;
if (pboAllocSize != currFrame.data.size())
{
qDebug() << "VideoSurface: Resize pbo " << frameBytes << "bytes (before" << pboAllocSize << ")";
qDebug() << "VideoSurface: Resize pbo " << currFrame.data.size() << "bytes (before" << pboAllocSize << ")";
pbo->bind();
pbo->allocate(frameBytes);
pbo->release();
pbo[0]->bind();
pbo[0]->allocate(currFrame.data.size());
pbo[0]->release();
pboAllocSize = frameBytes;
pbo[1]->bind();
pbo[1]->allocate(currFrame.data.size());
pbo[1]->release();
pboAllocSize = currFrame.data.size();
}
// 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
pbo[pboIndex]->bind();
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[pboIndex]->unmap();
pbo[pboIndex]->release();
pbo->release();
// transfer data
pbo[nextPboIndex]->bind();
void* ptr = pbo[nextPboIndex]->map(QOpenGLBuffer::WriteOnly);
if (ptr)
memcpy(ptr, currFrame.data.data(), currFrame.data.size());
pbo[nextPboIndex]->unmap();
pbo[nextPboIndex]->release();
uploadFrame = false;
mutex.lock();
frame.setNull();
mutex.unlock();
}
// render pbo
float values[] = {
static float values[] = {
-1, -1,
1, -1,
-1, 1,
1, 1
};
program->setAttributeArray(0, GL_FLOAT, values, 2);
// background
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
@ -208,23 +206,46 @@ void VideoSurface::paintGL()
glViewport((width() - w)*0.5f, 0, w, height());
}
program->bind();
program->enableAttributeArray(0);
bgrProgramm->bind();
bgrProgramm->setAttributeArray(0, GL_FLOAT, values, 2);
bgrProgramm->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();
bgrProgramm->disableAttributeArray(0);
bgrProgramm->release();
}
void VideoSurface::updateGL()
void VideoSurface::subscribe()
{
uploadFrame = true;
QGLWidget::updateGL();
if (source && !hasSubscribed)
{
source->subscribe();
hasSubscribed = true;
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
}
}
void VideoSurface::unsubscribe()
{
if (source && hasSubscribed)
{
source->unsubscribe();
hasSubscribed = false;
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
}
}
void VideoSurface::onNewFrameAvailable(const VideoFrame newFrame)
{
mutex.lock();
frame = newFrame;
mutex.unlock();
updateGL();
}

View File

@ -18,11 +18,11 @@
#define SELFCAMVIEW_H
#include <QGLWidget>
#include <QMutex>
#include "videosource.h"
class QOpenGLBuffer;
class QOpenGLShaderProgram;
class QTimer;
class VideoSource;
class VideoSurface : public QGLWidget
{
@ -42,20 +42,26 @@ public:
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void updateGL();
void update();
void subscribe();
void unsubscribe();
private slots:
void onNewFrameAvailable(const VideoFrame newFrame);
private:
VideoSource* source;
QOpenGLBuffer* pbo;
QOpenGLShaderProgram* program;
QOpenGLBuffer* pbo[2];
QOpenGLShaderProgram* bgrProgramm;
GLuint textureId;
int pboAllocSize;
QSize res;
bool uploadFrame;
bool hasSubscribed;
mutable int lastWidth;
QMutex mutex;
VideoFrame frame;
int pboIndex;
};
#endif // SELFCAMVIEW_H