使用updatePaintNode在QT/OpenGL中渲染视频:仅当我调整屏幕大小时才会出现新帧 [英] Rendering video in QT/OpenGL using updatePaintNode: new frame only appears when I resize screen

查看:328
本文介绍了使用updatePaintNode在QT/OpenGL中渲染视频:仅当我调整屏幕大小时才会出现新帧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Qt中编写OpenGL视频渲染器,并且几乎可以正常工作.仅当我调整窗口大小时,它才会渲染视频.因此,每次我调整窗口大小时,屏幕上都会绘制一个新框架.我想我忘了在更新框架后调用某些函数.

I'm writing an OpenGL video renderer in Qt and it's almost working. It'll only render the video if I resize the window. So every time I resize the window, a new frame is drawn on the screen. I think I'm forgetting to call some function after updating the frame.

这是第一帧,没有视频数据时:

Here's the first frame, when there's no video data:

调整屏幕大小后,将绘制一个框架:

After I resize the screen, a frame is drawn:

#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结构中.

To understand how the code updates a video frame: boost::thread mediaThread(&MediaStream::run, stream); creates a thread that will insert video data through void Node::updateData(unsigned char**data, int frameWidth, int frameHeight). This data is written inside the State struct.

唯一缺少的一种方法是告诉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.

According to Qt documentation this is done by calling QQuickItem::update(). Note that you must set the flag QQuickItem::ItemHasContents on the item first.

取消注释item->update();时得到QObject::startTimer: Timers cannot be started from another thread的原因有两点:

When you uncomment item->update(); you get QObject::startTimer: Timers cannot be started from another thread, because of 2 things:

  1. QQuickItem::update()将请求转发到窗口,然后转发到平台适配.在某些(全部?)情况下,该实现使用QTimer安排重新绘制.

  1. The QQuickItem::update() forwards the request to the window and then to the platform adaptation. In some (all ?) cases, the implementation uses a QTimer to schedule the repaint.

您不是从GUI线程调用item->update();.您可能直接从用于阅读视频的加速线程中调用它.

You call item->update(); not from the GUI thread. Likely you call it directly from the boost thread you use to read the video.

所以最后,item->update();无法启动计时器,什么也没发生.

So in the end, item->update(); fails to start the timer, and nothing happens.

一种解决方案是确保您从GUI线程调用QQuickItem::update().

A solution can be to make sure you call QQuickItem::update() from the GUI thread.

也许这应该起作用:

QMetaObject::invokeMethod(item, &QQuickItem::update, Qt::QueuedConnection);

这应该从item->thread()调用QQuickItem::update().

这篇关于使用updatePaintNode在QT/OpenGL中渲染视频:仅当我调整屏幕大小时才会出现新帧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆