收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
3、QT视频绘制 QT openGL编程
因为视频显示存在像素格式的转换问题,要从解码出来的YUV格式转化为显示需要的RGB格式,视频的每一帧图像这么多像素点都需要转换,这是一个很大的开销,如果这一部分效率不高,那么对整体的视频播放性能都是十分有影响的,而QTopenGL也是效率很高的,直接操作显卡。
Shader有点类似于一个程序的编译器,
1)编写Vertex Shader和Fragment Shader源码。GLSL语言编写类似与C语言的一样的代码,它可以放在GPU里面被并行运行。
Vertex Shader(如果要存放到文件中就在.vsh)负责搞定像素位置,填写gl_Posizion;
Fragment Shader(Fragment Shader fsh)负责搞定像素外观,填写 gl_FragColor;
Program有点类似于一个程序的链接器。
program对象提供了把需要做的事连接在一起的机制,在一个program中,shader对象可以连接在一起。
QTopenGL显示的几个关键点
QOpenGLWidget (与界面如何交互)
Program GLSL 顶点与片元(如何与显卡交互的,GLSL 是跑在显卡上面的)
材质纹理Texture (如何写入ffmpeg数据的)
顶点和纹理材质
下面有相关介绍
为什么要采用QT的OpenGL三维的来绘制,直接使用QopenWidget、QWight也是可以的,但是如果使用后面的那么其实图像显示和界面的按钮是一套东西,当点击按钮刷新的时候就会存在闪屏的情况。因此采用三维的,QTOpenGL这种就是提供三维绘制,就可以自动在界面上叠加了。
使用的时候需要重载这些函数
void paintGL();//具体绘制在这里面实现
void initializeGL();//初始化
void resizeGL(int width,int height)//当窗口发生变化的时候调用,这个函数
还提供了很大的便利,如如果需要视频放缩的时候就可以直接调用OpenGL里面的函数,不然就需要在ffmpeg进行像素尺寸的转换效率就比较低了并且采用不同的算法效果可能也不一定好。而openGL中就是采用差值的算法来放缩,整体界面就减少失真的情况。
封装了操作OpenGL对象的函数类QOpenGLFunctions
QOpenGL相关的函数是单独有一个类来封装的,是QOpenGLFunctions
类,这里面就有很多与openGL相关的函数了,因此也可以进行QOpenGLFunctions继承来获取操作openGL的相关函数。
QGLShaderProgram编译运行shader和shader交互
shader 着色器
这个程序最终是在显卡上运行的,是通过QGLShaderProgram 来与显卡进行交互的。
QGLShaderProgram 主要封装的函数有
编译运行shader
addShaderFromSourceCode 把shader的源代码加载进来,有两部分代码,是顶点shader和片元shader(又称为像素shader)
bindAttributeLocation 设置传入的属性,只要是设置顶点坐标的属性和材质纹理Texture坐标的属性,后面可以传入顶点坐标或材质坐标然后对应坐标设置什么变量是通过他来设置的。
uniformLocation 获取shader变量 就是将显卡当中变量的一块地址取出来
总结;就是编译运行shader、传入shader程序,设置变量,获取变量
GLSL着色器语言 基于openGL设计的,给显卡用的语言
OpenGL Shader Language,简称GLSL。它是一种类似于C语言的专门为GPU设计的语言,它可以放在GPU里面被并行运行。
顶点着色器,就是针对每个顶点执行一次,用于确定顶点的位置,因为在三维空间中要将所有顶点的参数都获取。一般顶点着色器都是画三角形,如计算显卡的能力都是看他能画出多少个三角形,这里图片的显示我们采用画矩形,也就是两个三角形组合。因此我们这里顶点着色器的代码就是画两个三角形组成矩形。
代码填充就是
float \*vertexData = new float[12]{
-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这里不传入。再传入的时候告诉他填充的是二维的要他在内部填充
片元着色器,就是针对一个平面的,针对每个片元(可以理解为每个像素)执行一次,用于确定每个片元(像素)的颜色 。传入YUV的数据过来,至于至于转换可以到显卡自己那边转换。片元着色器他有一个内置变量gl_FragColor,就是在遍历像素点的时候将这个值进行改变,那么他的颜色一会跟着变化的。
流程就是通过顶点着色器获取顶点的位置,再通过片元着色器对这些顶点构成的片元像素进行填充颜色。
GLSL基本语法与C基本相同,这里只是做简单图像显示,真正的GLSL是用来做特效的,涉及算法的,
GLSL 完美支持向量和矩阵操作,因此在像素格式的转换方面十分适合选用,并且提供了大量的内置函数来提供丰富的扩展功能的,
他是通过限定符操作来管理输入输出类型的,例如传入顶点坐标,传出材质纹理等。
材质坐标信息
与顶点不同的是他是二维的并且坐标都是正数。
代码设置;
float \*textureVertexData = new float[8]{
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
}
//也可以第三维的0这里不传入。再传入的时候告诉他填充的是二维的要他在内部填充
三种GLSL变量类型
varying顶点与片元共享,就是有两部分代码顶点着色器代码计算出来的顶点坐标通过这个变量传给片元着色器那段代码,然后就可以获取到材质位置。可见代码理解。
atrribute 顶点使用 由bindAttributeLocation传入,就是我们创建的数组通过他传入过去进行计算
uniform 程序传入 uniformLocation获取地址,就是我们ffmpeg解码出来的YUV数据要传入进去变成uniform。
最后再通过glUniform1i(textureUniformY,0)传入与材质层进行绑定
//自动加双引号 定义常量串都可以使用
#define GET\_STR(x) #x
//顶点着色器的shader代码
const char \*vString = GET\_STR(
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
gl_Position = vertexIn;//传入的顶点坐标记录
textureOut = textureIn;//传入的材质坐标保存到textureOut
}
);
//自动加双引号 定义常量串都可以使用
#define GET\_STR(x) #x
//片元着色器的shader代码
const char \*tString = GET\_STR(
varying vec2 textureOut;//刚刚顶点着色器算出来的坐标
uniform sampler2D tex_y;//uniform是外部传入的变量
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main(void)
{
//420P是平面存储的,最后都是要转换为每个像素都是有yuv再方便转换
//用的是灰度图的形式存储的
//如安装那边硬解码出来的都是yuv420sp,uv是打包存在一起的,则不能使用这套shader代码了,要增加透明度存储方式。
vec3 yuv;
vec3 rgb;
//根据那个坐标把材质坐标计算出来
//传入材质,和坐标 返回材质当中的颜色rgb,但是用灰度图存储的,都是一样的。
//三个材质就可以拼出一个yuv的数据
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);//转换成显示的颜色,再设置一下
}
);
创建材质
shader其实就是绘制某一个材质,那么就要看创建材质了。qt也有创建材质的函数,但是openGL这边要简单一点。
glGenTextures(1,t);创建几个材质,传入二维数组存放创建的材质地址
glBindTexture(GL_TEXTURE_2D \*t)进行绑定 绑定为2d的图像
glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)设置属性 放大的属性
glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)设置属性 缩小的属性
GL_TEXTURE_2D操作的是2D纹理
GL_TEXTURE_MAG_FILTER 放大过滤
GL_TEXTURE_MIN_FILTER 缩小过滤
GL_LINEAR用线性差值的方式进行缩放 使用距离当前渲染像素中心最近的四个纹素加权平均值,则最后的图像变化就比较荣和
写入和绘制材质
glActiveTexture(GL_TEXTURE0);激活材质 序号0
glBindTexture(GL_TEXTURE_2D,id_y);绑定之前创建的材质
在显存当中创建材质
//将openGL里面的创建的材质设置传入到内存当中,就是显存到内存的操作
//GL\_LUMINANCE,pixel\_w,pixel\_h 这个就表示已灰度图的形式存放的,传入宽高
glTexImage2D(GL_TEXTURE_2D,//创建为了
0, //细节显示 0默认 是拉远拉近摄像机相关的显示
GL_LUMINANCE,//gpu显卡内部的格式,就是创建的材质是存放到显卡当中,这是灰度图
pixel_w,pixel_h,
0,
GL_LUMINANCE, 数据格式,内存到显存的格式,但是无法转换因此要一致
GL_UNSIGNED_BYTE,//单个像素的存放格式
plane[0])
//因为创建纹理有很大的开销,因此提供了一个修改纹理的函数
glTexSubImage2D() 修改纹理 存在偏移值的变量就是表示从当前的纹理当中只取一部分
glUniform1i(textureUniformY, 0);材质设完之后与shaeder相关联,通过uniform变量出入到材质0层
glDrawArrays(GL_TRINGLE_STRIP,0,4)//绘制矩形,四个顶点
显示播放整体流程分析
重载QOpenGLWidget的三个函数
//重载那三个函数
//刷新初始化
void paintGL();//具体绘制在这里面实现
//初始化gl
void initializeGL();//初始化
//窗口大小变化
void resizeGL(int width,int height);//当窗口发生变化的时候调用,这个函数
initializeGL 在OPenGL创建的时候就会调用
初始化OpenGL函数
initializeOpenGLFunctions()
调用着色器program添加shader代码
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Fragment,tString);
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Vertex,vString);
//QGLShader::Fragment 纹理材质类型 tString,vString常量串是Shader具体源代码
//这个addShader还可以从文件获取 Fragment是fsh文件,Vertex是vsh文件
//给shader绑定属性 给顶点shader的attribute赋值,A\_VER、T\_VER这个只是标记作用
program.bindAttributeLocation("vertexIn", A_VER);
program.bindAttributeLocation("textureIn",T_VER);
//编译,绑定shader
program.link();
program.bind();
//定义顶点和材质纹理的坐标
static const GLfloat ver[] = {}
static const GLfloat tex[] = {}
//将顶点和材质纹理的坐标设置到shader当中去,A\_VER、T\_VER就是之前赋值属性的时候绑定的flag
glVertexAttribPointer(A_VER,2,GL_FLOAT,0,0,ver);
glEnableVertexAttribArray(A_VER);
glVertexAttribPointer(T_VER,2, GL_FLOAT, 0,0,tex);
glEnableVertexAttribArray(T_VER);
//从shader当中获取uniform sampler2D的位置,之后好传入
unis[0] = program.uniformLocation("tex\_y");
unis[1] = program.uniformLocation("tex\_u");
unis[2] = program.uniformLocation("tex\_v");
//创建材质,并绑定类型,属性及创建空间,对yuv都进行操作,注意uv的大小变化
glGenTextures(3,texs);
//Y
glBindTexture(GL_TEXTURE_2D, texs[0]);//绑定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的
//width/2 height/2根据yuv420的特性来的 uv是y的四分之一
//U
glBindTexture(GL_TEXTURE_2D, texs[1]);//绑定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的
//V
glBindTexture(GL_TEXTURE_2D, texs[2]);//绑定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性 放大 线性差值的属性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的
//分配材质的内存空间
datas[0] = new unsigned char[width\*height];
datas[1] = new unsigned char[width\*height/4];
datas[2] = new unsigned char[width\*height/4];
//读取文件
fp = fopen("output240X128.yuv", "rb");
if(!fp)
{
qDebug() << "fopen error";
}
//启动定时器,绑定到OPenGL的update()函数调用绘画函数
QTimer \*ti = new QTimer(this);
connect(ti, SIGNAL(timeout()), this, SLOT(update()));//定时器刷新到update()里面取
ti->start(40);
paintGL 绘画时调用
//读取帧数据,存放到分配材质的内存空间
fread(datas[0],1,width\*height, fp);
fread(datas[1], 1, width\*height/4, fp);
fread(datas[2], 1, width\*height/4, fp);
//在显卡中创建材质 并绑定到了0层渲染材质
glActiveTexture(GL_TEXTURE0);//激活第0层
glBindTexture(GL_TEXTURE_2D, texs[0]);//把0层绑定Y材质
//修改材质(复制内存内存)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
//与shader uni变量关联起来
glUniform1i(unis[0], 0);
重复三次
glActiveTexture(GL_TEXTURE0+1);//激活第1层
glBindTexture(GL_TEXTURE_2D, texs[1]);//把1层绑定Y材质
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
glUniform1i(unis[1], 1);
glActiveTexture(GL_TEXTURE0+2);//激活第0层
glBindTexture(GL_TEXTURE_2D, texs[2]);//把0层绑定Y材质
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
glUniform1i(unis[2], 2);
最后绘画 //画三角形 从0开始 四个顶点,
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//画三角形 从0开始 四个顶点,
视频播放实践
拿到output240X128.yuv也可以使用命令制作ffmpeg -i 1.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
注意几点
控件这里
qt工程文件.pro文件
需添加
QT += opengl
QT += openglextensions
代码
XVideoWidget.h文件
#ifndef XVIDEOWIDGET\_H
#define XVIDEOWIDGET\_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QGLShaderProgram>
/\*
\* //引入这个需要在pro文件添加模块
\* QT += opengl
\* QT += openglextensions
\*/
class XVideoWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
//注意Q\_OBJECT这个变量如果没有添加进来则槽函数无法调用
Q_OBJECT
public:
XVideoWidget(QWidget \*parent);
~XVideoWidget();
//从shader的yuv变量地址
GLuint unis[3] = {0};
//opengl的texture地址
GLuint texs[3] = {0};
//材质的内存空间
unsigned char \*datas[3] = {0};
//宽高度
int width = 240;
int height = 128;
protected:
//重载那三个函数
//刷新初始化
void paintGL();//具体绘制在这里面实现
//初始化gl
void initializeGL();//初始化
//窗口大小变化
void resizeGL(int width,int height);//当窗口发生变化的时候调用,这个函数
private:
QGLShaderProgram program;
};
#endif // XVIDEOWIDGET\_H
XVideoWidget.cpp文件
#include "XVideoWidget.h"
#include <QDebug>
#include <QTimer>
FILE \*fp = NULL;
//定义一个宏表示数组
#define A\_VER 3
#define T\_VER 4
//自动加双引号 定义常量串都可以使用
#define GET\_STR(x) #x
//顶点着色器的shader代码
const char \*vString = GET\_STR(
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;//varying顶点和片元shader共享变量
void main(void)
{
gl_Position = vertexIn;//传入的顶点坐标记录
textureOut = textureIn;//传入的材质坐标保存到textureOut,从而传出去了
}
);
//片元着色器的shader代码
const char \*tString = GET\_STR(
varying vec2 textureOut;//刚刚顶点着色器算出来的坐标 共享的
uniform sampler2D tex_y;//uniform是外部传入的变量
uniform sampler2D tex_u;//sampler2D是一个2d图像
uniform sampler2D tex_v;
void main(void)
{
//420P是平面存储的,最后都是要转换为每个像素都是有yuv再方便转换
//用的是灰度图的形式存储的
//如安装那边硬解码出来的都是yuv420sp,uv是打包存在一起的,则不能使用这套shader代码了,要增加透明度存储方式。
vec3 yuv;
vec3 rgb;
//根据那个坐标把材质坐标计算出来
//传入材质,和坐标 返回材质当中的颜色rgb,但是用灰度图存储的,都是一样的。
//三个材质就可以拼出一个yuv的数据
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);//转换成显示的颜色,再设置一下
}
);
XVideoWidget::XVideoWidget(QWidget \*parent)
:QOpenGLWidget(parent)//初始化列表调用父类构造方法调用paint画出OpenGLWidget
{
int a = 1;
}
XVideoWidget::~XVideoWidget()
{
}
//刷新初始化,每次移动都会调用一次,进行刷新
void XVideoWidget::paintGL()
{
if(feof(fp))
{
fseek(fp, 0, SEEK\_SET);
}
fread(datas[0],1,width\*height, fp);
fread(datas[1], 1, width\*height/4, fp);
fread(datas[2], 1, width\*height/4, fp);
//在显卡中创建材质 并绑定到了0层渲染材质


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**
[0],1,width\*height, fp);
fread(datas[1], 1, width\*height/4, fp);
fread(datas[2], 1, width\*height/4, fp);
//在显卡中创建材质 并绑定到了0层渲染材质
[外链图片转存中...(img-aozZahNP-1715872401958)]
[外链图片转存中...(img-Wek5Idf9-1715872401958)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**