Add Double-Buffering Frame Uploader

This commit is contained in:
Florian Märkl 2019-08-05 13:56:59 +02:00
commit 3358759aab
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
5 changed files with 178 additions and 53 deletions

View file

@ -31,7 +31,9 @@ add_executable(chiaki
include/streamsession.h
src/streamsession.cpp
include/avopenglwidget.h
src/avopenglwidget.cpp)
src/avopenglwidget.cpp
include/avopenglframeuploader.h
src/avopenglframeuploader.cpp)
target_include_directories(chiaki PRIVATE include)
target_link_libraries(chiaki chiaki-lib)

View file

@ -0,0 +1,42 @@
/*
* This file is part of Chiaki.
*
* Chiaki is free 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.
*
* Chiaki 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chiaki. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef CHIAKI_AVOPENGLFRAMEUPLOADER_H
#define CHIAKI_AVOPENGLFRAMEUPLOADER_H
#include <QObject>
#include <QOpenGLWidget>
class AVOpenGLWidget;
class VideoDecoder;
class AVOpenGLFrameUploader: public QObject
{
Q_OBJECT
private:
VideoDecoder *decoder;
AVOpenGLWidget *widget;
private slots:
void UpdateFrame();
public:
AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget);
};
#endif // CHIAKI_AVOPENGLFRAMEUPLOADER_H

View file

@ -19,6 +19,7 @@
#define CHIAKI_AVOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QMutex>
extern "C"
{
@ -26,6 +27,16 @@ extern "C"
}
class VideoDecoder;
class AVOpenGLFrameUploader;
struct AVOpenGLFrame
{
GLuint tex[3];
unsigned int width;
unsigned int height;
bool Update(AVFrame *frame);
};
class AVOpenGLWidget: public QOpenGLWidget
{
@ -37,13 +48,18 @@ class AVOpenGLWidget: public QOpenGLWidget
GLuint program;
GLuint vbo;
GLuint vao;
GLuint tex[3];
unsigned int frame_width, frame_height;
void UpdateTextures(AVFrame *frame);
AVOpenGLFrame frames[2];
int frame_fg;
QMutex frames_mutex;
AVOpenGLFrameUploader *frame_uploader;
public:
explicit AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent = nullptr);
~AVOpenGLWidget() override;
void SwapFrames();
AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; }
protected:
void initializeGL() override;

View file

@ -0,0 +1,45 @@
/*
* This file is part of Chiaki.
*
* Chiaki is free 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.
*
* Chiaki 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chiaki. If not, see <https://www.gnu.org/licenses/>.
*/
#include <avopenglframeuploader.h>
#include <avopenglwidget.h>
#include <videodecoder.h>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
AVOpenGLFrameUploader::AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget)
: QObject(nullptr),
decoder(decoder),
widget(widget)
{
connect(decoder, SIGNAL(FramesAvailable()), this, SLOT(UpdateFrame()));
}
void AVOpenGLFrameUploader::UpdateFrame()
{
AVFrame *next_frame = decoder->PullFrame();
if(!next_frame)
return;
bool success = widget->GetBackgroundFrame()->Update(next_frame);
av_frame_free(&next_frame);
if(success)
widget->SwapFrames();
}

View file

@ -17,6 +17,7 @@
#include <avopenglwidget.h>
#include <videodecoder.h>
#include <avopenglframeuploader.h>
#include <QOpenGLContext>
#include <QOpenGLExtraFunctions>
@ -84,10 +85,51 @@ AVOpenGLWidget::AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent)
#endif
setFormat(format);
frame_width = 0;
frame_height = 0;
frame_uploader = nullptr;
}
connect(decoder, SIGNAL(FramesAvailable()), this, SLOT(update()));
AVOpenGLWidget::~AVOpenGLWidget()
{
delete frame_uploader;
}
void AVOpenGLWidget::SwapFrames()
{
QMutexLocker lock(&frames_mutex);
frame_fg = 1 - frame_fg;
QMetaObject::invokeMethod(this, "update");
}
bool AVOpenGLFrame::Update(AVFrame *frame)
{
auto f = QOpenGLContext::currentContext()->functions();
if(frame->format != AV_PIX_FMT_YUV420P)
{
// TODO: log to somewhere else
printf("Invalid Format\n");
return false;
}
width = frame->width;
height = frame->height;
for(int i=0; i<3; i++)
{
f->glBindTexture(GL_TEXTURE_2D, tex[i]);
int width = frame->width;
int height = frame->height;
if(i > 0)
{
width /= 2;
height /= 2;
}
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[i]);
}
f->glFinish(); // TODO: fence
return true;
}
void AVOpenGLWidget::initializeGL()
@ -131,16 +173,21 @@ void AVOpenGLWidget::initializeGL()
return;
}
f->glGenTextures(3, tex);
uint8_t uv_default = 127;
for(int i=0; i<3; i++)
for(int i=0; i<2; i++)
{
f->glBindTexture(GL_TEXTURE_2D, tex[i]);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, i > 0 ? &uv_default : nullptr);
f->glGenTextures(3, frames[i].tex);
uint8_t uv_default = 127;
for(int j=0; j<3; j++)
{
f->glBindTexture(GL_TEXTURE_2D, frames[i].tex[j]);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, j > 0 ? &uv_default : nullptr);
}
frames[i].width = 0;
frames[i].height = 0;
}
f->glUseProgram(program);
@ -162,62 +209,33 @@ void AVOpenGLWidget::initializeGL()
f->glCullFace(GL_BACK);
f->glEnable(GL_CULL_FACE);
f->glClearColor(0.0, 0.0, 0.0, 1.0);
frame_uploader = new AVOpenGLFrameUploader(decoder, this);
frame_fg = 0;
}
void AVOpenGLWidget::resizeGL(int w, int h)
{
}
void AVOpenGLWidget::UpdateTextures(AVFrame *frame)
{
auto f = QOpenGLContext::currentContext()->functions();
if(frame->format != AV_PIX_FMT_YUV420P)
{
// TODO: log to somewhere else
printf("Invalid Format\n");
return;
}
frame_width = frame->width;
frame_height = frame->height;
for(int i=0; i<3; i++)
{
f->glBindTexture(GL_TEXTURE_2D, tex[i]);
int width = frame->width;
int height = frame->height;
if(i > 0)
{
width /= 2;
height /= 2;
}
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[i]);
}
}
void AVOpenGLWidget::paintGL()
{
auto f = QOpenGLContext::currentContext()->extraFunctions();
f->glClear(GL_COLOR_BUFFER_BIT);
AVFrame *next_frame = decoder->PullFrame();
if(next_frame)
{
UpdateTextures(next_frame);
av_frame_free(&next_frame);
}
QMutexLocker lock(&frames_mutex);
AVOpenGLFrame *frame = &frames[frame_fg];
GLsizei vp_width, vp_height;
if(!frame_width || !frame_height)
if(!frame->width || !frame->height)
{
vp_width = width();
vp_height = height();
}
else
{
float aspect = (float)frame_width / (float)frame_height;
float aspect = (float)frame->width / (float)frame->height;
if(aspect < (float)width() / (float)height())
{
vp_height = height();
@ -235,8 +253,10 @@ void AVOpenGLWidget::paintGL()
for(int i=0; i<3; i++)
{
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, tex[i]);
f->glBindTexture(GL_TEXTURE_2D, frame->tex[i]);
}
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
f->glFinish(); // TODO: fence
}