从零开始用QT编写一个Android投屏、PC反控软件(六)--显示设备屏幕视频流

上一篇文章,我们学习了视频数据的处理流程。
简单回顾一下:video socket连接建立后,安照服务端和客户端的约定,首先读取codec_id,然后读取视频尺寸(宽、高),接下来循环读取AVPacket,解码AVPacket为YUV420格式的AVFrame,将AVFrame交给 screen sink做后续处理。
本篇我们学习screen sink拿到AVFrame数据后续的流程。
视频显示流程

我们逐步分析下视频显示的流程:

  1. screen sink在拿到AVFrame后,放入AVFrameBuffer中待用。
  2. AVFrameBuffer对新收到数据进行暂存,同时丢弃老数据,并且发送new frame 信号。
  3. VideoForm在创建是时候链接了new frame 信号,接到信号后调用updateFrame更新帧数据。
  4. VideoWidget是VideoForm上的视频显示组件,它继承了QOpenGLWidget和QOpenGLFunctions,用新收到的AVFrame数据更新纹理。
  5. 底层的OpenGL根据AVFrame中的YUV420数据绘图显示。

下图是VideoForm创建后显示的界面,
VideoForm
上面的工具栏分别是设备名称和一些快捷工具按钮,下面的工具栏模拟了Android手机常用的三个按键。
这个窗口继承自QWidget,通过设置

setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);

去掉了边框和自带的标题栏,然后进行了重新绘制。

下面视频部分相关的代码:

#ifndef VideoForm_H
#define VideoForm_H

#include <QWidget>
#include <QString>
#include <QLabel>
#include "video_widget.h"
extern "C"
{
#include "libavutil/frame.h"
}

class VideoForm : public QWidget
{
    Q_OBJECT
public:
    explicit VideoForm(QWidget *parent = nullptr);
    ~VideoForm();
    void setTitle(const std::string &title);
    void updateFrame();
    void setController(ControllerThread *handle);
    void setFrameBuffer(FrameBuffer *fb);
    void setVisible(bool visible) override;
private:
    void updateRender(int width, int height, uint8_t *data_y, uint8_t *data_u, uint8_t *data_v, int linesize_y, int linesize_u, int linesize_v);
    void updateShowSize(const QSize &newSize);
    QRect getScreenRect();
    void moveCenter();
    void showFPS(bool flag);
    void updateFPS(uint fps);
    void initShortcut();
    void switchFullScreen();
protected:
    void closeEvent(QCloseEvent *event) override;
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dropEvent(QDropEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseDoubleClickEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;
    void keyReleaseEvent(QKeyEvent *event) override;
    bool isShortcut(QKeyEvent *event);
    void resizeEvent(QResizeEvent *event) override;
    void changeEvent(QEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resize(int w, int h);
public slots:
    void showMaximized();
private:
    Ui::VideoForm *ui;
    VideoWidget *videoWidget;
    bool hasframe;
    QSize frameSize;
    float widthHeightRatio;
    QSize normalSize;
    QPoint fullScreenBeforePos;
    QPoint dragPosition;
    QLabel *fpsLabel;
    ControllerThread *controller;
    //设备屏幕是否点亮
    bool screenMode;
    VideoTitleBar *titleBar;
    VideoBottomBar *bottomBar;
    FrameBuffer *frameBuffer;
    AVFrame *lastFrame;
};

#endif // VideoForm_H

#include "VideoForm.h"
#include "ui_VideoForm.h"
#include <QDesktopWidget>
#include <QFileInfo>
#include <QLabel>
#include <QMessageBox>
#include <QMimeData>
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
#include <QShortcut>
#include <QStyle>
#include <QStyleOption>
#include <QTimer>
#include <QWindow>
#include <QLabel>
#include <QShortcut>
#include <QClipboard>
#include <QPushButton>
#include <QStyleOption>
#include <QPainter>
#include "videotitlebar.h"

#define TITLE_BAR_HEIGHT 36
#define BOTTOM_BAR_HEIGHT 50
#define LEFT_MARGIN 0
#define TOP_MARGIN 0
#define RIGHT_MARGIN 0
#define BOTTOM_MARGIN 0

VideoForm::VideoForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::VideoForm)
{
    ui->setupUi(this);
    videoWidget = new VideoWidget(this);
    videoWidget->setObjectName("VideoForm");
    hasframe = false;
    controller = NULL;
    lastFrame = NULL;

    setMouseTracking(true);
    this->setAcceptDrops(true);

    initShortcut();

#ifndef Q_OS_OSX
    setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);
#endif

    titleBar = new VideoTitleBar(this);
    connect(titleBar,&VideoTitleBar::barAction,this,[=](VideoTitleBarAction action){
        switch (action) {
        case VideoTitleBarAction::CLOSE:
            this->close();
            break;
        case VideoTitleBarAction::MIN:
            this->showMinimized();
            break;
        case VideoTitleBarAction::MAX:
            this->showMaximized();
            break;
        case VideoTitleBarAction::FULLSCREEN:
            this->switchFullScreen();
            break;
        default:
            break;
        }

    });
    bottomBar = new VideoBottomBar(this);
    connect(bottomBar,&VideoBottomBar::barAction,this,[=](VideoBottomBarAction action){
        if(!controller)
            return;
        switch(action)
        {
        case VideoBottomBarAction::HOME:
            this->controller->actionHome();
            break;
        case VideoBottomBarAction::BACK:
            this->controller->actionBack();
            break;
        case VideoBottomBarAction::MENU:
            this->controller->actionMenu();
            break;
        default:
            break;
        }
    });
}
void VideoForm::showFPS(bool flag)
{
    if (!fpsLabel) {
        return;
    }
    fpsLabel->setVisible(flag);
}
void VideoForm::updateFPS(uint fps)
{
    if (!fpsLabel) {
        return;
    }
    fpsLabel->setText(QString("FPS:%1").arg(fps));
}

void VideoForm::closeEvent(QCloseEvent *event)
{
    emit signal_close();
    event->accept();
}

VideoForm::~VideoForm()
{
    delete ui;
}

void VideoForm::setTitle(const std::string &title)
{
    this->titleBar->setDeviceName(title);
}
void VideoForm::setVisible(bool visible)
{
    QWidget::setVisible(visible);
}
void VideoForm::updateFrame()
{
    AVFrame *frame = av_frame_alloc();
    this->frameBuffer->consume(frame);
    QSize newSize(frame->width,frame->height);
    updateShowSize(newSize);
    videoWidget->updateTextures(frame->data[0],frame->data[1],frame->data[2],
                                frame->linesize[0],frame->linesize[1],frame->linesize[2]);
    av_frame_free(&frame);
}

void VideoForm::setController(ControllerThread *handle)
{
    if(!handle)
        return;
    this->controller = handle;
}

void VideoForm::setFrameBuffer(FrameBuffer *fb)
{
    frameBuffer = fb;
}

void VideoForm::dropEvent(QDropEvent *event)
{
    const QMimeData *qmd = event->mimeData();
    QList<QUrl> urls = qmd->urls();

    for (const QUrl &url : urls) {
        QString file = url.toLocalFile();
        QFileInfo fileInfo(file);

        if (!fileInfo.exists()) {
            //file does not exist
            continue;
        }

        if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
            //emit device->installApkRequest(file);
            continue;
        }
       // emit device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
    }

}
void VideoForm::dragEnterEvent(QDragEnterEvent *event)
{
    event->acceptProposedAction();
}
QRect VideoForm::getScreenRect()
{
    QRect screenRect;
    QWidget *win = window();
    if (!win) {
        return screenRect;
    }

    QWindow *winHandle = win->windowHandle();
    QScreen *screen = QGuiApplication::primaryScreen();
    if (winHandle) {
        screen = winHandle->screen();
    }
    if (!screen) {
        return screenRect;
    }

    screenRect = screen->availableGeometry();
    return screenRect;
}
void VideoForm::updateShowSize(const QSize &size)
{
    if (frameSize != size) {
        frameSize = size;
        this->videoWidget->setFrameSize(size);

        widthHeightRatio = 1.0f * size.width() / size.height();

        bool vertical = widthHeightRatio < 1.0f ? true : false;
        QSize showSize = size;
        QRect screenRect = getScreenRect();
        if (screenRect.isEmpty()) {
            qWarning() << "getScreenRect is empty";
            return;
        }
        if (vertical) {
            showSize.setHeight(qMin(size.height(), screenRect.height() - 160));
            showSize.setWidth(showSize.height() * widthHeightRatio);
        } else {
            showSize.setWidth(qMin(size.width(), screenRect.width() / 2));
            showSize.setHeight(showSize.width() / widthHeightRatio);
        }

        if (showSize != this->size()) {
            resize(showSize.width(),showSize.height()+TITLE_BAR_HEIGHT+BOTTOM_BAR_HEIGHT);

            moveCenter();
        }
    }
}
void VideoForm::moveCenter()
{
    QRect screenRect = getScreenRect();
    if (screenRect.isEmpty()) {
        qWarning() << "getScreenRect is empty";
    }
    else{
        move(screenRect.center() - QRect(0, 0, size().width(), size().height()).center());
    }
}
void VideoForm::mousePressEvent(QMouseEvent *event)
{
    if(videoWidget->geometry().contains(event->pos()))
    {
        if(!controller)
            return;

        if (event->button() == Qt::MiddleButton)
        {
            controller->actionHome();
        }
        else if(event->button() == Qt::XButton1)
        {
            controller->actionAppSwitch();
        }
        else if(event->button() == Qt::XButton2)
        {
            /*if (event->clicks() < 2) {
            expand_notification_panel(controller);
        } else {
            expand_settings_panel(controller);
        }*/
        }
        else if(event->button() == Qt::RightButton)
        {
            controller->pressBackOrTurnScreenOn();
        }
        else
        {
            QPoint pos = videoWidget->mapFromParent(event->pos());
            event->setLocalPos(pos);
            controller->mouseEvent(event,videoWidget->getFrameSize(),videoWidget->size());
        }
    }
    else
    {
        if(event->button() == Qt::LeftButton)
        {
            dragPosition = event->globalPos() - frameGeometry().topLeft();
            event->accept();
        }
    }
}

void VideoForm::switchFullScreen()
{
    if(isFullScreen())
    {
        showNormal();
        QSize size = this->size();
        this->titleBar->setVisible(true);
        this->bottomBar->setVisible(true);
        this->resize(size.width(),size.height());
    }
    else
    {
        QScreen *screen = QGuiApplication::primaryScreen();
        QSize screenSize = screen->size();
        showFullScreen();
        this->titleBar->setVisible(false);
        this->bottomBar->setVisible(false);
        int w,h,x,y;
        float whratio = 1.0f * frameSize.width()/frameSize.height();
        if(whratio<1.0f)//垂直
        {
            h = screenSize.height();
            w = screenSize.height()*whratio;
            x = (screenSize.width()-w)/2;
            y = 0;
            this->videoWidget->resize(w,h);
            this->videoWidget->move(x,y);
        }
        else
        {
            w = screenSize.width();
            h = screenSize.width()*whratio;
            y = (screenSize.height()-h)/2;
            x = 0;
            this->videoWidget->resize(w,h);
            this->videoWidget->move(x,y);
        }
    }
}

void VideoForm::resize(int w, int h)
{
    this->titleBar->setGeometry(0,0,w,TITLE_BAR_HEIGHT);
    this->videoWidget->setGeometry(LEFT_MARGIN,this->titleBar->height(),w,h-TITLE_BAR_HEIGHT-BOTTOM_BAR_HEIGHT);
    this->bottomBar->setGeometry(0,videoWidget->y()+videoWidget->height(),w,BOTTOM_BAR_HEIGHT);
    QWidget::resize(w,h);
}

void VideoForm::showMaximized()
{
    if(isMaximized())
    {
        showNormal();
        QSize size = this->size();
        this->resize(size.width(),size.height());
    }
    else
    {
        QWidget::showMaximized();
        QSize screenSize = this->size();
        int w,h,x,y;
        float whRatio = 1.0f * frameSize.width()/frameSize.height();
        if(whRatio<1.0f)//垂直
        {
            h = screenSize.height()-TITLE_BAR_HEIGHT-BOTTOM_BAR_HEIGHT;
            w = h*whRatio;
            x = (screenSize.width()-w)/2;

            this->titleBar->setGeometry(x,0,w,TITLE_BAR_HEIGHT);
            this->videoWidget->resize(w,h);
            this->videoWidget->move(x,TITLE_BAR_HEIGHT);
            y = videoWidget->y()+videoWidget->height();

            this->bottomBar->setGeometry(x,y,w,BOTTOM_BAR_HEIGHT);
        }
        else
        {
            w = screenSize.width();
            h = screenSize.width()*whRatio;
            y = (screenSize.height()-h)/2;
            x = 0;
            this->videoWidget->resize(w,h);
            this->videoWidget->move(x,y);
        }
    }

}
void VideoForm::initShortcut()
{
    QShortcut *shortcut = nullptr;

    shortcut = new QShortcut(QKeySequence("Ctrl+f"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        switchFullScreen();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+h"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        if(controller)
            controller->actionHome();
    });


    shortcut = new QShortcut(QKeySequence("Ctrl+s"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->actionAppSwitch();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+m"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->actionMenu();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+up"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->actionVolumeUp();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+down"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->actionVolumeDown();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+p"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->actionPower();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+o"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        controller->setScreenPowerMode(!screenMode);
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+n"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {

        controller->expandNotificationPanel();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+Shift+n"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {

        controller->collapsePanels();
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+r"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {

        controller->rotateDevice();
    });


    shortcut = new QShortcut(QKeySequence("Ctrl+c"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {

        controller->getDeviceClipboard(COPY_KEY_COPY);
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+v"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {
        QClipboard *board = QApplication::clipboard();
        QString text = board->text();

        controller->setDeviceClipboard(text.toStdString().c_str());
    });

    shortcut = new QShortcut(QKeySequence("Ctrl+x"), this);
    shortcut->setAutoRepeat(false);
    connect(shortcut, &QShortcut::activated, this, [this]() {

        controller->getDeviceClipboard(COPY_KEY_CUT);
    });

}
void VideoForm::mouseReleaseEvent(QMouseEvent *event)
{
    if (videoWidget->geometry().contains(event->pos()))
    {
        if(!controller)
            return;
        QPoint pos = videoWidget->mapFromParent(event->pos());
        event->setLocalPos(pos);
        controller->mouseEvent(event,videoWidget->getFrameSize(),videoWidget->size());
    }
    else{
        dragPosition = QPoint(0, 0);
    }
}
void VideoForm::mouseMoveEvent(QMouseEvent *event)
{
    if (videoWidget->geometry().contains(event->pos()))
    {
        if(event->buttons() & Qt::LeftButton){
            if(controller)
                controller->mouseMoveEvent(event,videoWidget->getFrameSize(),videoWidget->size());
        }
    }
    else
    {
        if(!dragPosition.isNull())
        {
            if (event->buttons() & Qt::LeftButton) {
                move(event->globalPos() - dragPosition);
                event->accept();
            }
        }
    }

}
void VideoForm::mouseDoubleClickEvent(QMouseEvent *event)
{

}
void VideoForm::wheelEvent(QWheelEvent *event)
{
    if (videoWidget->geometry().contains(event->position().toPoint()))
    {
        if(controller)
            controller->mouseWhellEvent(event,videoWidget->getFrameSize(),videoWidget->size());
    }
}
void VideoForm::keyPressEvent(QKeyEvent *event)
{

    if(event->key() == Qt::Key_Escape && !event->isAutoRepeat()){
        //switchFullScreen();
    }
    //this->isShortcut(event);
}
void VideoForm::keyReleaseEvent(QKeyEvent *event)
{

}

enum Qt::Key ShortcutKeys[] = {
    Qt::Key_C,
    Qt::Key_F,
    Qt::Key_H,
    Qt::Key_F,
    Qt::Key_S,
    Qt::Key_M,
    Qt::Key_Up,
    Qt::Key_Down,
    Qt::Key_P,
    Qt::Key_O,
    Qt::Key_N,
    Qt::Key_R,
    Qt::Key_V,
    Qt::Key_X
};
bool VideoForm::isShortcut(QKeyEvent *event)
{
    if(event->modifiers() ==(Qt::ControlModifier | Qt::ShiftModifier))
    {
        return true;
    }
    if(event->modifiers() == Qt::CTRL)
    {
        for(int i = 0;i<sizeof(ShortcutKeys)/sizeof(Qt::Key);i++)
        {
            if(event->key() == ShortcutKeys[i])
                return true;
        }
        return false;
    }
    return false;
}

void VideoForm::resizeEvent(QResizeEvent *event)
{
    QSize s = event->size();
    QSize old = event->oldSize();
    QWidget::resizeEvent(event);
}

void VideoForm::paintEvent(QPaintEvent *event)
{
    QStyleOption opt;
    opt.init(this);
    QPainter painter(this);
    style()->drawPrimitive(QStyle::PE_Widget,&opt,&painter,this);
}

OpenGL相关代码:

#ifndef VideoWidget_H
#define VideoWidget_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>

class VideoWidget
    : public QOpenGLWidget
    , protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit VideoWidget(QWidget *parent = nullptr);
    virtual ~VideoWidget() override;

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

    void setFrameSize(const QSize &frameSize);
    const QSize &getFrameSize();
    void updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV);

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;

private:
    void initShader();
    void initTextures();
    void deInitTextures();
    void updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride);

private:
    QSize frameSize;
    bool needUpdate;
    bool textureInited;
    QOpenGLBuffer vOpenGLBuffer;
    QOpenGLShaderProgram shaderProgram;
    GLuint texture[3] = { 0 };
};

#endif // VideoWidget_H

#include <QCoreApplication>
#include <QOpenGLTexture>
#include <QSurfaceFormat>

#include "video_widget.h"
static const GLfloat coordinate[] = {
    -1.0f,
    -1.0f,
    0.0f,
    1.0f,
    -1.0f,
    0.0f,
    -1.0f,
    1.0f,
    0.0f,
    1.0f,
    1.0f,
    0.0f,
    0.0f,
    1.0f,
    1.0f,
    1.0f,
    0.0f,
    0.0f,
    1.0f,
    0.0f
};

static const QString vertShader = R"(
    attribute vec3 vertexIn;    // xyz顶点坐标
    attribute vec2 textureIn;   // xy纹理坐标
    varying vec2 textureOut;    // 传递给片段着色器的纹理坐标
    void main(void)
    {
        gl_Position = vec4(vertexIn, 1.0);  // 1.0表示vertexIn是一个顶点位置
        textureOut = textureIn; // 纹理坐标直接传递给片段着色器
    }
)";

// 片段着色器
static QString fragShader = R"(
    varying vec2 textureOut;        // 由顶点着色器传递过来的纹理坐标
    uniform sampler2D textureY;     // uniform 纹理单元,利用纹理单元可以使用多个纹理
    uniform sampler2D textureU;     // sampler2D是2D采样器
    uniform sampler2D textureV;     // 声明yuv三个纹理单元
    void main(void)
    {
        vec3 yuv;
        vec3 rgb;

        // SDL2 BT709_SHADER_CONSTANTS
        // https://github.com/spurious/SDL-mirror/blob/4ddd4c445aa059bb127e101b74a8c5b59257fbe2/src/render/opengl/SDL_shaders_gl.c#L102
        const vec3 Rcoeff = vec3(1.1644,  0.000,  1.7927);
        const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329);
        const vec3 Bcoeff = vec3(1.1644,  2.1124,  0.000);

        // 根据指定的纹理textureY和坐标textureOut来采样
        yuv.x = texture2D(textureY, textureOut).r;
        yuv.y = texture2D(textureU, textureOut).r - 0.5;
        yuv.z = texture2D(textureV, textureOut).r - 0.5;

        // 采样完转为rgb
        // 减少一些亮度
        yuv.x = yuv.x - 0.0625;
        rgb.r = dot(yuv, Rcoeff);
        rgb.g = dot(yuv, Gcoeff);
        rgb.b = dot(yuv, Bcoeff);
        // 输出颜色值
        gl_FragColor = vec4(rgb, 1.0);
    }
)";

VideoWidget::VideoWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    frameSize = {-1,-1};
    needUpdate = false;
    textureInited = false;
}

VideoWidget::~VideoWidget()
{
    makeCurrent();
    vOpenGLBuffer.destroy();
    deInitTextures();
    doneCurrent();
}

QSize VideoWidget::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize VideoWidget::sizeHint() const
{
    return size();
}

void VideoWidget::setFrameSize(const QSize &size)
{
    if (this->frameSize != size) {
        this->frameSize = size;
        needUpdate = true;
        // inittexture immediately
        repaint();
    }
}

const QSize &VideoWidget::getFrameSize()
{
    return frameSize;
}

void VideoWidget::updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV)
{
    if (textureInited) {
        updateTexture(texture[0], 0, dataY, linesizeY);
        updateTexture(texture[1], 1, dataU, linesizeU);
        updateTexture(texture[2], 2, dataV, linesizeV);
        update();
    }
}

void VideoWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glDisable(GL_DEPTH_TEST);

    // 顶点缓冲对象初始化
    vOpenGLBuffer.create();
    vOpenGLBuffer.bind();
    vOpenGLBuffer.allocate(coordinate, sizeof(coordinate));
    initShader();
    // 设置背景清理色为黑色
    glClearColor(0.0, 0.0, 0.0, 0.0);
    // 清理颜色背景
    glClear(GL_COLOR_BUFFER_BIT);

}

void VideoWidget::paintGL()
{
    if (needUpdate) {
        deInitTextures();
        initTextures();
        needUpdate = false;
    }

    if (textureInited) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture[0]);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture[1]);

        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, texture[2]);

        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
}

void VideoWidget::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);
    repaint();
}

void VideoWidget::initShader()
{
    // opengles的float、int等要手动指定精度
    if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) {
        fragShader.prepend(R"(
                             precision mediump int;
                             precision mediump float;
                             )");
    }
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShader);
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShader);
    shaderProgram.link();
    shaderProgram.bind();

    // 指定顶点坐标在vbo中的访问方式
    // 参数解释:顶点坐标在shader中的参数名称,顶点坐标为float,起始偏移为0,顶点坐标类型为vec3,步幅为3个float
    shaderProgram.setAttributeBuffer("vertexIn", GL_FLOAT, 0, 3, 3 * sizeof(float));
    // 启用顶点属性
    shaderProgram.enableAttributeArray("vertexIn");

    // 指定纹理坐标在vbo中的访问方式
    // 参数解释:纹理坐标在shader中的参数名称,纹理坐标为float,起始偏移为12个float(跳过前面存储的12个顶点坐标),纹理坐标类型为vec2,步幅为2个float
    shaderProgram.setAttributeBuffer("textureIn", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float));
    shaderProgram.enableAttributeArray("textureIn");

    // 关联片段着色器中的纹理单元和opengl中的纹理单元(opengl一般提供16个纹理单元)
    shaderProgram.setUniformValue("textureY", 0);
    shaderProgram.setUniformValue("textureU", 1);
    shaderProgram.setUniformValue("textureV", 2);
}

void VideoWidget::initTextures()
{
    // 创建纹理
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    // 设置纹理缩放时的策略
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 设置st方向上纹理超出坐标时的显示策略
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frameSize.width(), frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    glGenTextures(1, &texture[1]);
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frameSize.width() / 2, frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    glGenTextures(1, &texture[2]);
    glBindTexture(GL_TEXTURE_2D, texture[2]);
    // 设置纹理缩放时的策略
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 设置st方向上纹理超出坐标时的显示策略
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frameSize.width() / 2, frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    textureInited = true;
}

void VideoWidget::deInitTextures()
{
    if (QOpenGLFunctions::isInitialized(QOpenGLFunctions::d_ptr)) {
        glDeleteTextures(3, texture);
    }

    memset(texture, 0, sizeof(texture));
    textureInited = false;
}

void VideoWidget::updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride)
{
    if (!pixels)
        return;

    QSize size = 0 == textureType ? frameSize : frameSize / 2;

    makeCurrent();
    glBindTexture(GL_TEXTURE_2D, texture);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
    doneCurrent();
}

下一篇继续讲解屏幕控制相关的内容。

GitHub: linkedbyte

QQ:617753820

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值