在上一篇文章,我们学习了视频数据的处理流程。
简单回顾一下:video socket连接建立后,安照服务端和客户端的约定,首先读取codec_id,然后读取视频尺寸(宽、高),接下来循环读取AVPacket,解码AVPacket为YUV420格式的AVFrame,将AVFrame交给 screen sink做后续处理。
本篇我们学习screen sink拿到AVFrame数据后续的流程。
我们逐步分析下视频显示的流程:
- screen sink在拿到AVFrame后,放入AVFrameBuffer中待用。
- AVFrameBuffer对新收到数据进行暂存,同时丢弃老数据,并且发送new frame 信号。
- VideoForm在创建是时候链接了new frame 信号,接到信号后调用updateFrame更新帧数据。
- VideoWidget是VideoForm上的视频显示组件,它继承了QOpenGLWidget和QOpenGLFunctions,用新收到的AVFrame数据更新纹理。
- 底层的OpenGL根据AVFrame中的YUV420数据绘图显示。
下图是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