2014-07-07 00:19:45 +08:00
|
|
|
/*
|
|
|
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2014-10-08 22:42:09 +08:00
|
|
|
#include "videosurface.h"
|
2014-10-24 04:07:44 +08:00
|
|
|
#include "src/video/camera.h"
|
2014-09-11 21:44:34 +08:00
|
|
|
#include <QTimer>
|
2014-10-07 20:17:35 +08:00
|
|
|
#include <QOpenGLBuffer>
|
|
|
|
#include <QOpenGLShaderProgram>
|
|
|
|
#include <QDebug>
|
2014-08-29 19:40:37 +08:00
|
|
|
|
2014-10-12 02:53:40 +08:00
|
|
|
VideoSurface::VideoSurface(QWidget* parent)
|
2015-01-14 22:26:02 +08:00
|
|
|
: QGLWidget(QGLFormat(QGL::SingleBuffer), parent)
|
2015-01-30 04:23:33 +08:00
|
|
|
, source{nullptr}
|
2014-10-14 16:57:45 +08:00
|
|
|
, pbo{nullptr, nullptr}
|
2015-01-30 04:23:33 +08:00
|
|
|
, bgrProgramm{nullptr}
|
|
|
|
, yuvProgramm{nullptr}
|
|
|
|
, textureId{0}
|
|
|
|
, pboAllocSize{0}
|
|
|
|
, hasSubscribed{false}
|
|
|
|
, pboIndex{0}
|
2014-10-12 02:53:40 +08:00
|
|
|
{
|
2015-01-14 22:26:02 +08:00
|
|
|
|
2014-10-12 02:53:40 +08:00
|
|
|
}
|
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
VideoSurface::VideoSurface(VideoSource *source, QWidget* parent)
|
2014-10-12 02:53:40 +08:00
|
|
|
: VideoSurface(parent)
|
2014-06-29 04:24:26 +08:00
|
|
|
{
|
2014-10-15 20:46:01 +08:00
|
|
|
setSource(source);
|
2014-06-29 04:24:26 +08:00
|
|
|
}
|
|
|
|
|
2014-10-08 22:42:09 +08:00
|
|
|
VideoSurface::~VideoSurface()
|
2014-06-29 04:24:26 +08:00
|
|
|
{
|
2014-10-14 16:57:45 +08:00
|
|
|
if (pbo[0])
|
|
|
|
{
|
|
|
|
delete pbo[0];
|
|
|
|
delete pbo[1];
|
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
|
|
|
if (textureId != 0)
|
|
|
|
glDeleteTextures(1, &textureId);
|
2014-10-07 22:59:21 +08:00
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
unsubscribe();
|
2014-10-12 02:53:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void VideoSurface::setSource(VideoSource *src)
|
|
|
|
{
|
2014-10-15 20:46:01 +08:00
|
|
|
if (source == src)
|
|
|
|
return;
|
2014-06-29 04:24:26 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
unsubscribe();
|
2014-10-15 20:46:01 +08:00
|
|
|
source = src;
|
2014-10-14 16:57:45 +08:00
|
|
|
subscribe();
|
2014-10-12 02:53:40 +08:00
|
|
|
}
|
|
|
|
|
2014-10-08 22:42:09 +08:00
|
|
|
void VideoSurface::initializeGL()
|
2014-06-29 04:24:26 +08:00
|
|
|
{
|
2015-01-14 22:26:02 +08:00
|
|
|
QGLWidget::initializeGL();
|
2014-10-14 16:57:45 +08:00
|
|
|
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() {"
|
2014-10-27 00:08:35 +08:00
|
|
|
" gl_Position = vec4(vertices.xy, 0.0, 1.0);"
|
|
|
|
" coords = vertices.xy*vec2(0.5, 0.5) + vec2(0.5, 0.5);"
|
2014-10-14 16:57:45 +08:00
|
|
|
"}");
|
|
|
|
|
|
|
|
// 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));"
|
2014-10-27 00:08:35 +08:00
|
|
|
" gl_FragColor = vec4(color.bgr, 1.0);"
|
2014-10-14 16:57:45 +08:00
|
|
|
"}");
|
|
|
|
|
|
|
|
bgrProgramm->bindAttributeLocation("vertices", 0);
|
|
|
|
bgrProgramm->link();
|
2014-10-15 20:46:01 +08:00
|
|
|
|
|
|
|
// shaders
|
|
|
|
yuvProgramm = new QOpenGLShaderProgram;
|
|
|
|
yuvProgramm->addShaderFromSourceCode(QOpenGLShader::Vertex,
|
|
|
|
"attribute vec4 vertices;"
|
|
|
|
"varying vec2 coords;"
|
|
|
|
"void main() {"
|
2014-10-27 00:08:35 +08:00
|
|
|
" gl_Position = vec4(vertices.xy, 0.0, 1.0);"
|
|
|
|
" coords = vertices.xy*vec2(0.5, 0.5) + vec2(0.5, 0.5);"
|
2014-10-15 20:46:01 +08:00
|
|
|
"}");
|
|
|
|
|
2014-10-15 22:37:43 +08:00
|
|
|
// yuv frag-shader
|
2014-10-15 20:46:01 +08:00
|
|
|
yuvProgramm->addShaderFromSourceCode(QOpenGLShader::Fragment,
|
|
|
|
"uniform sampler2D texture0;"
|
|
|
|
"varying vec2 coords;"
|
|
|
|
"void main() {"
|
2014-10-27 00:08:35 +08:00
|
|
|
" vec3 yuv = texture2D(texture0,coords*vec2(1.0, -1.0)).rgb - vec3(0.0, 0.5, 0.5);"
|
|
|
|
" vec3 rgb = mat3(1.0, 1.0, 1.0, 0.0, -0.21482, 2.12798, 1.28033, -0.38059, 0.0)*yuv;"
|
|
|
|
" gl_FragColor = vec4(rgb, 1.0);"
|
2014-10-15 20:46:01 +08:00
|
|
|
"}");
|
|
|
|
|
|
|
|
yuvProgramm->bindAttributeLocation("vertices", 0);
|
|
|
|
yuvProgramm->link();
|
2014-06-29 04:24:26 +08:00
|
|
|
}
|
2014-06-30 05:41:47 +08:00
|
|
|
|
2014-10-08 22:42:09 +08:00
|
|
|
void VideoSurface::paintGL()
|
2014-06-30 05:41:47 +08:00
|
|
|
{
|
2014-10-14 16:57:45 +08:00
|
|
|
mutex.lock();
|
|
|
|
VideoFrame currFrame = frame;
|
2014-10-24 04:31:39 +08:00
|
|
|
frame.invalidate();
|
2014-10-14 16:57:45 +08:00
|
|
|
mutex.unlock();
|
2014-10-07 22:59:21 +08:00
|
|
|
|
2014-10-24 04:31:39 +08:00
|
|
|
if (currFrame.isValid() && res != currFrame.resolution)
|
2014-10-07 22:59:21 +08:00
|
|
|
{
|
2014-10-14 16:57:45 +08:00
|
|
|
res = currFrame.resolution;
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-22 18:59:48 +08:00
|
|
|
// delete old texture
|
|
|
|
if (textureId != 0)
|
|
|
|
glDeleteTextures(1, &textureId);
|
|
|
|
|
2014-10-07 20:17:35 +08:00
|
|
|
// a texture used to render the pbo (has the match the pixelformat of the source)
|
2014-10-07 22:59:21 +08:00
|
|
|
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);
|
2014-10-07 20:17:35 +08:00
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
}
|
|
|
|
|
2014-10-24 04:31:39 +08:00
|
|
|
if (currFrame.isValid())
|
2014-10-07 20:17:35 +08:00
|
|
|
{
|
2014-10-14 16:57:45 +08:00
|
|
|
pboIndex = (pboIndex + 1) % 2;
|
|
|
|
int nextPboIndex = (pboIndex + 1) % 2;
|
2014-10-08 20:25:32 +08:00
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
if (pboAllocSize != currFrame.frameData.size())
|
2014-10-08 20:25:32 +08:00
|
|
|
{
|
2014-10-22 18:59:48 +08:00
|
|
|
qDebug() << "VideoSurface: Resize pbo " << currFrame.frameData.size() << "(" << currFrame.resolution << ")" << "bytes (before" << pboAllocSize << ")";
|
2014-10-08 20:25:32 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[0]->bind();
|
2014-10-15 20:46:01 +08:00
|
|
|
pbo[0]->allocate(currFrame.frameData.size());
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[0]->release();
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[1]->bind();
|
2014-10-15 20:46:01 +08:00
|
|
|
pbo[1]->allocate(currFrame.frameData.size());
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[1]->release();
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
pboAllocSize = currFrame.frameData.size();
|
2014-10-14 16:57:45 +08:00
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[pboIndex]->bind();
|
2014-10-08 20:25:32 +08:00
|
|
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D,0,0,0, res.width(), res.height(), GL_RGB, GL_UNSIGNED_BYTE, 0);
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[pboIndex]->unmap();
|
|
|
|
pbo[pboIndex]->release();
|
2014-10-08 22:19:42 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
// transfer data
|
|
|
|
pbo[nextPboIndex]->bind();
|
|
|
|
void* ptr = pbo[nextPboIndex]->map(QOpenGLBuffer::WriteOnly);
|
|
|
|
if (ptr)
|
2014-10-15 20:46:01 +08:00
|
|
|
memcpy(ptr, currFrame.frameData.data(), currFrame.frameData.size());
|
2014-10-14 16:57:45 +08:00
|
|
|
pbo[nextPboIndex]->unmap();
|
|
|
|
pbo[nextPboIndex]->release();
|
2014-10-08 20:25:32 +08:00
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
// background
|
2014-10-07 20:17:35 +08:00
|
|
|
glClearColor(0, 0, 0, 1);
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
2014-10-12 02:53:40 +08:00
|
|
|
// keep aspect ratio
|
|
|
|
float aspectRatio = float(res.width()) / float(res.height());
|
|
|
|
if (width() < float(height()) * aspectRatio)
|
|
|
|
{
|
|
|
|
float h = float(width()) / aspectRatio;
|
|
|
|
glViewport(0, (height() - h)*0.5f, width(), h);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
float w = float(height()) * float(aspectRatio);
|
|
|
|
glViewport((width() - w)*0.5f, 0, w, height());
|
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
QOpenGLShaderProgram* programm = nullptr;
|
|
|
|
switch (frame.format)
|
|
|
|
{
|
|
|
|
case VideoFrame::YUV:
|
|
|
|
programm = yuvProgramm;
|
|
|
|
break;
|
|
|
|
case VideoFrame::BGR:
|
|
|
|
programm = bgrProgramm;
|
|
|
|
break;
|
2014-10-15 22:54:48 +08:00
|
|
|
default:
|
|
|
|
break;
|
2014-10-15 20:46:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (programm)
|
|
|
|
{
|
2015-01-30 04:23:33 +08:00
|
|
|
// render pbo
|
|
|
|
static float values[] = {
|
|
|
|
-1, -1,
|
|
|
|
1, -1,
|
|
|
|
-1, 1,
|
|
|
|
1, 1
|
|
|
|
};
|
|
|
|
|
2014-10-15 20:46:01 +08:00
|
|
|
programm->bind();
|
|
|
|
programm->setAttributeArray(0, GL_FLOAT, values, 2);
|
|
|
|
programm->enableAttributeArray(0);
|
|
|
|
}
|
|
|
|
|
2014-10-07 20:17:35 +08:00
|
|
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
|
|
|
|
|
|
|
//draw fullscreen quad
|
|
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
2014-10-14 16:57:45 +08:00
|
|
|
|
2014-10-08 20:25:32 +08:00
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
2014-10-15 20:46:01 +08:00
|
|
|
|
|
|
|
if (programm)
|
|
|
|
{
|
|
|
|
programm->disableAttributeArray(0);
|
|
|
|
programm->release();
|
|
|
|
}
|
2014-10-14 16:57:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void VideoSurface::subscribe()
|
|
|
|
{
|
|
|
|
if (source && !hasSubscribed)
|
|
|
|
{
|
|
|
|
source->subscribe();
|
|
|
|
hasSubscribed = true;
|
|
|
|
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
|
|
|
|
}
|
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
2014-10-14 16:57:45 +08:00
|
|
|
void VideoSurface::unsubscribe()
|
|
|
|
{
|
|
|
|
if (source && hasSubscribed)
|
|
|
|
{
|
|
|
|
source->unsubscribe();
|
|
|
|
hasSubscribed = false;
|
|
|
|
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
|
|
|
|
}
|
2014-06-30 05:41:47 +08:00
|
|
|
}
|
|
|
|
|
2015-01-30 04:23:33 +08:00
|
|
|
void VideoSurface::onNewFrameAvailable(const VideoFrame& newFrame)
|
2014-09-16 01:51:25 +08:00
|
|
|
{
|
2014-10-14 16:57:45 +08:00
|
|
|
mutex.lock();
|
|
|
|
frame = newFrame;
|
|
|
|
mutex.unlock();
|
|
|
|
|
|
|
|
updateGL();
|
2014-09-16 01:51:25 +08:00
|
|
|
}
|
2014-10-07 20:17:35 +08:00
|
|
|
|
|
|
|
|
2014-10-12 02:53:40 +08:00
|
|
|
|