c++ - 使用 updatePaintNode 在 QT/OpenGL 中渲染视频:新帧仅在我调整屏幕大小时出现
问题描述
我正在用 Qt 编写一个 OpenGL 视频渲染器,它几乎可以工作了。如果我调整窗口大小,它只会渲染视频。所以每次我调整窗口大小时,都会在屏幕上绘制一个新框架。我想我在更新框架后忘记调用一些函数。
#include <QtQuick/QQuickItem>
#include <qguiapplication.h>
#include <qsgmaterial.h>
#include <qsgnode.h>
#include <qquickitem.h>
#include <qquickview.h>
#include <qsgsimplerectnode.h>
#include <qsgsimplematerial.h>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include "OpenGlMaterialQQuickItem.h"
#include <iostream>
#define GET_STR(x) #x
#define VERTEX_ATTRIBUTE 3
struct State
{
public:
int frameWidth = 1920;
int frameHeight = 1080;
unsigned char *datas[3] = {0};
private:
bool firstRender = true;
public:
void updateData(unsigned char**data, int frameWidth, int frameHeight)
{
this->frameWidth = frameWidth;
this->frameHeight = frameHeight;
if (firstRender) {
datas[0] = new unsigned char[frameWidth*frameHeight]; //Y
datas[1] = new unsigned char[frameWidth*frameHeight/4];//U
datas[2] = new unsigned char[frameWidth*frameHeight/4];//V
firstRender = false;
}
memcpy(datas[0], data[0], frameWidth*frameHeight);
memcpy(datas[1], data[1], frameWidth*frameHeight/4);
memcpy(datas[2], data[2], frameWidth*frameHeight/4);
};
class Shader : public QSGSimpleMaterialShader<State>
{
QSG_DECLARE_SIMPLE_SHADER(Shader, State);
private:
QOpenGLFunctions *glFuncs = nullptr;
GLuint unis[3] = {0};
GLuint texs[3] = {0};
QSize m_viewportSize;
bool firstRender = true;
public:
const char *vertexShader() const override {
return GET_STR(
uniform highp mat4 qt_Matrix;
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
gl_Position = qt_Matrix * vertexIn;
textureOut = textureIn;
}
);
}
const char *fragmentShader() const override {
return GET_STR(
varying vec2 textureOut;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
uniform lowp float qt_Opacity;
void main(void)
{
vec3 yuv;
vec3 rgb;
yuv.x = texture2D(tex_y, textureOut).r;
yuv.y = texture2D(tex_u, textureOut).r - 0.5;
yuv.z = texture2D(tex_v, textureOut).r - 0.5;
rgb = mat3(1.0, 1.0, 1.0,
0.0, -0.39465, 2.03211,
1.13983, -0.58060, 0.0) * yuv;
gl_FragColor = vec4(rgb, 1.0) * qt_Opacity;
}
);
}
QList<QByteArray> attributes() const override
{
return {QByteArrayLiteral("vertexIn"), QByteArrayLiteral("textureIn")};
}
void initialize()
{
if (!program()->isLinked()) {
return;
}
QSGSimpleMaterialShader<State>::initialize();
glFuncs = QOpenGLContext::currentContext()->functions();
program()->bind();
glFuncs->glGenTextures(3, texs);
}
void updateState(const State *state, const State *) override
{
//Y
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[0]);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFuncs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, state->frameWidth, state->frameHeight, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
//U
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[1]);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFuncs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, state->frameWidth/2, state->frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
//V
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[2]);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFuncs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFuncs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, state->frameWidth / 2, state->frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
glFuncs->glActiveTexture(GL_TEXTURE0);
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[0]);
glFuncs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state->frameWidth, state->frameHeight, GL_RED, GL_UNSIGNED_BYTE, state->datas[0]);
glFuncs->glUniform1i(unis[0], 0);
glFuncs->glActiveTexture(GL_TEXTURE0+1);
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[1]);
glFuncs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state->frameWidth/2, state->frameHeight / 2, GL_RED, GL_UNSIGNED_BYTE, state->datas[1]);
glFuncs->glUniform1i(unis[1],1);
glFuncs->glActiveTexture(GL_TEXTURE0+2);
glFuncs->glBindTexture(GL_TEXTURE_2D, texs[2]);
glFuncs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state->frameWidth / 2, state->frameHeight / 2, GL_RED, GL_UNSIGNED_BYTE, state->datas[2]);
glFuncs->glUniform1i(unis[2], 2);
glFuncs->glDrawArrays(GL_TRIANGLE_STRIP,0,4);
}
void resolveUniforms() override
{
unis[0] = program()->uniformLocation("tex_y");
unis[1] = program()->uniformLocation("tex_u");
unis[2] = program()->uniformLocation("tex_v");
}
};
class Node: public QSGGeometryNode, public FrameUpdater
{
public:
State state;
//QQuickItem* item;
Node()
{
material = Shader::createMaterial();
setMaterial(material);
setFlag(OwnsMaterial, true);
QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
QSGGeometry::updateTexturedRectGeometry(g, QRect(), QRect());
setGeometry(g);
setFlag(QSGNode::OwnsGeometry, true);
stream = new MediaStream("rtsp://admin:19929394@192.168.1.178:10554/tcp/av0_0");
stream->setFrameUpdater((FrameUpdater *) this);
boost::thread mediaThread(&MediaStream::run, stream);
}
void updateData(unsigned char**data, int frameWidth, int frameHeight)
{
material->state()->updateData(data, frameWidth, frameHeight);
markDirty(QSGNode::DirtyMaterial);
//item->update();
}
private:
QSGSimpleMaterial<State> *material;
MediaStream* stream;
};
QSGNode * OpenGlMaterialQQuickItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) //override
{
std::cout << "updatePaintNode called " << std::endl;
Node *n = static_cast<Node *>(node);
if (!node)
n = new Node();
//n->item = this;
QSGGeometry::updateTexturedRectGeometry(n->geometry(), boundingRect(), QRectF(0, 0, 1, 1));
n->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial);
return n;
}
要了解代码如何更新视频帧:boost::thread mediaThread(&MediaStream::run, stream);
创建一个线程,该线程将通过void Node::updateData(unsigned char**data, int frameWidth, int frameHeight)
. 此数据写入State
结构内部。
唯一缺少的是一种告诉 Qt 它需要绘制另一个框架的方法。什么不见了?
PS:这是QQuickItem
:
class OpenGlMaterialQQuickItem: public ReactItem
{
//Q_OBJECT
Q_PROPERTY(QString uri WRITE setUri)// NOTIFY uriChanged)
public:
std::string uri;
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
OpenGlMaterialQQuickItem()
{
setFlag(ItemHasContents, true);
}
void setUri(const QString &a) {
uri = a.toStdString();
}
};
解决方案
根据 Qt 文档,这是通过调用QQuickItem::update()
. 请注意,您必须先QQuickItem::ItemHasContents
在项目上设置标志。
从评论更新
当您取消注释时,item->update();
您会得到QObject::startTimer: Timers cannot be started from another thread
,因为有两件事:
将
QQuickItem::update()
请求转发给窗口,然后转发给平台适配。在某些(所有?)情况下,实现使用 QTimer 来安排重绘。您
item->update();
不是从 GUI 线程调用的。您可能直接从用于阅读视频的 boost 线程中调用它。
所以最后,item->update();
无法启动计时器,什么也没有发生。
一个解决方案是确保您QQuickItem::update()
从 GUI 线程调用。
也许这应该工作:
QMetaObject::invokeMethod(item, &QQuickItem::update, Qt::QueuedConnection);
这应该QQuickItem::update()
从item->thread()
.
推荐阅读
- selenium - 如何使用硒完全导航谷歌表格菜单
- python - 来自多个模块的 Flask IIS wfastcgi 日志记录
- c - 使用 do while 循环读取 .txt 文件
- jquery - 如何在 Yii2 2amigos CKEditor 中禁用工具提示
- javascript - 如何在 DyanmoDb API 的 FilterExpression 中使用 equals
- html - 如何根据文本框中的文本量自动调整一个元素的高度
- sql - 具有 > 14 个表的 Oracle SQL XOR 条件
- ms-access - 我想用关系设置初始化 ACCESS 表的自动编号
- c++ - C++:如何正确地将 deque::front() 返回的变量从函数中传递出去?
- html - 垂直对齐容器中的一行文本