通過Qt opengl不是為了3D繪制,而是為了將視頻繪制起來
使用opengl 可以極大降低yuv轉(zhuǎn)rgb的轉(zhuǎn)換開銷
使用Opengl需要考慮三大問題:
1、QOpenGLWidget(與界面如何交互)
1、為什么用QT的opengl
簡單,界面可以自動疊加
void paintGL(); // 具體的繪制寫在該函數(shù)里
void initializeGL(); // 材質(zhì)初始化
void resizeGL(int width, int height); // 當窗口發(fā)生變化(縮放)
QOpenGLFunctions // 不需要手動添加庫,直接繼承該函數(shù)
2、Program GLSL 頂點和片元(如何與顯卡交互)
GLSL是新的語言,通過GLSL與顯卡進行交互,GLSL 跑在顯卡上
QGLShaderProgram
Program用來編譯和運行Shader代碼,包括與shader的交互
編譯和運行shader // shader兩部分:頂點和片元
addShaderFromSourceCode // 加入shader代碼
bindAttributeLocation // 設(shè)置傳入的變量, 頂點和坐標
uniformLocation // 獲取變量
GLSL著色器語言,專門針對opengl所設(shè)計,用于顯卡運行
頂點著色器是針對每個頂點執(zhí)行一次,用于確定頂點的位置;——三維
片元著色器是針對每個片元(可以理解為每個像素)執(zhí)行一次,用于確定每個片元(像素)的顏色 ——平面
GLSL基本語法與C基本相同
它完美地支持向量和矩陣操作
GLSL提供了大量的內(nèi)置函數(shù)來提供豐富的拓展功能
它是通過限定符操作來管理輸入輸出類型
頂點著色器(畫兩個三角形,形成一個矩形)
顯卡運算能力:值以三角形為單位,所畫的數(shù)量
頂點著色器被使用在傳統(tǒng)的基于頂點的操作, 例如位移矩陣、計算光照方程、產(chǎn)生貼圖坐標。
頂點著色器被應用指定, 應用于客戶的頂點轉(zhuǎn)化。
片元著色器
在片元著色器階 段只有唯一的 varying 輸出變量- 即內(nèi)建變量: gl_FragColor(像素點顏色)
頂點信息
材質(zhì)坐標信息(全部在第一象限)
傳入頂點和材質(zhì)坐標
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
ATTRIB_VERTEX:頂點坐標 2 :坐標數(shù)量 GL_FLOAT:單位數(shù)
0:法線 0:步寬
glEnableVertexAttribArray(ATTRIB_VERTEX);
使生效
glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
材質(zhì)坐標
glEnableVertexAttribArray(ATTRIB_TEXTURE);
三種GLSL變量類型
varying 頂點與片元共享 // 算出頂點坐標
attribute 頂點使用,由bindAttributeLocation傳入
uniform 程序傳入 uniformLocation獲取地址
glUniform1i(textureUniformY, 0); 設(shè)置
頂點shader
attribute vec4 vertexIn; // 頂點輸入
attribute vec2 textureIn; // 材質(zhì)輸入
void main(void)
{ gl_Position = vertexIn;
textureOut = textureIn; }
片元shader
varying vec2 textureOut; //取出材質(zhì)數(shù)值
uniform sampler2D tex_y; // 三個材質(zhì)
uniform sampler2D tex_u;
uniform sampler2D tex_v;
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, 1, 1, 0, -0.39465, 2.03211, 1.13983, -0.58060, 0) * yuv;
gl_FragColor = vec4(rgb, 1);
}
3、材質(zhì)Texture(如何寫入ffmpeg數(shù)據(jù))
前面通過OpenGLWidget管理整個窗口,最終顯示涉及在某個材質(zhì)上,最終要把ffmpeg數(shù)據(jù)寫入,要考慮如何在材質(zhì)中寫入ffmpeg數(shù)據(jù)文章來源:http://www.zghlxwxcb.cn/news/detail-406708.html
創(chuàng)建材質(zhì)
glGenTextures(1, t); // 創(chuàng)建材質(zhì)個數(shù),指針地址
glBindTexture(GL_TEXTURE_2D, *t); // 綁定材質(zhì)類型成2D圖像
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 放大、縮?。ㄍㄟ^線性插值)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_2D: 操作2D紋理.
GL_TEXTURE_MIN_FILTE: 縮小過濾
GL_TEXTURE_MAG_FILTER: 放大過濾
GL_LINEAR: 線性過濾, 使用距離當前渲染像素中心最近的4個紋素加權(quán)平均值.
ps:如果是一個點直接復制四倍的話,會產(chǎn)生馬賽克的現(xiàn)象
加權(quán)計算的話就比較柔和文章來源地址http://www.zghlxwxcb.cn/news/detail-406708.html
寫入和繪制材質(zhì)
glActiveTexture(GL_TEXTURE0); // 激活材質(zhì),通過編號
glBindTexture(GL_TEXTURE_2D, id_y); // 綁定
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixel_w, pixel_h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]); glUniform1i(textureUniformY, 0); // 0層材質(zhì),材質(zhì)可以多層
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 從0開始繪制,4個
glViewport(0, 0, width, height);
glTexImage2D 材質(zhì)創(chuàng)建函數(shù)
glTexImage2D(GL_TEXTURE_2D, // 在顯存中創(chuàng)建紋理
0, //細節(jié) 0默認 鏡頭拉遠拉近
GL_RED, //gpu內(nèi)部格式
videoWidth,
videoHeight
, GL_RED, //數(shù)據(jù)格式 數(shù)據(jù)格式和gpu內(nèi)部格式 要一致
GL_UNSIGNED_BYTE //像素的數(shù)據(jù)類型
, data);
glTexSubImage2D // 修改紋理
解決方案:VS2017中QT的ui文件打開閃退問題
https://blog.csdn.net/jiaolu295/article/details/115898600
項目代碼 cpp
#include "XVideoWidget.h"
#include <QDebug>
#include <QTimer>
// 自動加雙引號
#define GET_STR(x) #x
#define A_VER 3
#define T_VER 4
FILE *fp = NULL; // 文件接口
// 頂點shader
const char *vString = GET_STR(
attribute vec4 vertexIn; // 頂點坐標
attribute vec2 textureIn; // 材質(zhì)坐標
varying vec2 textureOut; // 頂點shader和片元shader共享的變量
void main(void)
{
gl_Position = vertexIn;
textureOut = textureIn;
}
);
// 片元shader
const char *tString = GET_STR(
varying vec2 textureOut; // 共享變量
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
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;
// 用矩陣轉(zhuǎn)換yuv
rgb = mat3(1.0, 1.0, 1.0,
0, -0.39465, 2.03211,
1.13983, -0.58060, 0.0)*yuv;
// 獲取輸出顏色
gl_FragColor = vec4(rgb, 1.0);
}
);
// 準備yuv數(shù)據(jù)
// ffmpeg -i v1080.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
// -t 10: 時長10秒鐘, 指定輸出yuv420p
XVideoWidget::XVideoWidget(QWidget *parent)
:QOpenGLWidget(parent)
{
}
XVideoWidget::~XVideoWidget()
{
}
// 初始化opengl
void XVideoWidget::initializeGL()
{
qDebug() << "initializeGL";
// 初始化opengl函數(shù)(QOpenGLFunctions繼承)函數(shù)
initializeOpenGLFunctions();
// 用program加載shader(頂點和片元)腳本
// 片元(像素)shader
qDebug() << program.addShaderFromSourceCode(QGLShader::Fragment, tString);
// 頂點shader
qDebug() << program.addShaderFromSourceCode(QGLShader::Vertex, vString);
// #############################################以上shader已創(chuàng)建好,接下來要與shader進行交互
// 設(shè)置頂點坐標的變量
program.bindAttributeLocation("vertexIn", A_VER); // 將變量名稱關(guān)聯(lián)到一個索引中,索引可以用一個宏來實現(xiàn)
// 設(shè)置材質(zhì)坐標
program.bindAttributeLocation("textureIn", T_VER);
// 編譯shader,打印
qDebug() << "program.link() = " << program.link();
// 綁定shader,打印
qDebug() << "program.bind() = " << program.bind(); // 將opengl 和shader關(guān)聯(lián)起來
// 傳遞頂點和材質(zhì)坐標
// 頂點 頂點坐標是三維,但最后一位不傳默認為0
static const GLfloat ver[] = {
-1.0f,-1.0f,
1.0f,-1.0f,
-1.0f,1.0f,
1.0f,1.0f,
};
// 材質(zhì)
static const GLfloat tex[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
// 將坐標寫入opengl中
//頂點 位置索引,一個頂點的元素個數(shù)(2),存放類型GL_FLOAT,是否有法線向量 0沒有 0默認,ver頂點地址
glVertexAttribPointer(A_VER, 2, GL_FLOAT, 0, 0, ver);
glEnableVertexAttribArray(A_VER); // 使頂點坐標生效
// 材質(zhì)
glVertexAttribPointer(T_VER, 2, GL_FLOAT, 0, 0, tex);
glEnableVertexAttribArray(T_VER); // 使頂點坐標生效
// 接下來對材質(zhì)進行處理
// 從shader獲取材質(zhì)
unis[0] = program.uniformLocation("tex_y");
unis[1] = program.uniformLocation("tex_u");
unis[2] = program.uniformLocation("tex_v");
// 創(chuàng)建材質(zhì)
glGenTextures(3, texs);
// 綁定Y
glBindTexture(GL_TEXTURE_2D, texs[0]);
// 放大過濾,線性插值(要對周邊的點進行加權(quán)處理,有漸變的效果) GL_NEAREST()臨近插值,效率高(當前點直接復制),但是馬賽克嚴重
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 縮小過濾,線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 創(chuàng)建材質(zhì)顯卡空間
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
// 綁定U
glBindTexture(GL_TEXTURE_2D, texs[1]);
// 放大過濾,線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 縮小過濾,線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 創(chuàng)建材質(zhì)顯卡空間
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]);
// 放大過濾,線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 縮小過濾,線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 創(chuàng)建材質(zhì)顯卡空間
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width / 2 , height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
// 分配材質(zhì)內(nèi)存空間
datas[0] = new unsigned char[width * height]; // Y
datas[1] = new unsigned char[width * height / 4]; // U
datas[2] = new unsigned char[width * height / 4]; // V
fp = fopen("out240x128.yuv", "rb");
if (!fp) // 讀取失敗
{
qDebug() << "out240x128.yuv file open failed!";
}
// 啟動定時器
QTimer *ti = new QTimer(this);
connect(ti, SIGNAL(timeout()), this, SLOT(update())); // 信號槽 timeout信號 this:當前窗體 更新
ti->start(40); // 25幀,40ms刷新一次
}
// 刷新顯示,實現(xiàn)按鈕的疊加
void XVideoWidget::paintGL()
{
if (feof(fp)) // 假如到了結(jié)尾,移到開頭的位置
{
fseek(fp, 0, SEEK_SET);// 循環(huán)播放
}
// 讀取數(shù)據(jù),存放在datas
fread(datas[0], 1, width*height, fp);
fread(datas[1], 1, width*height / 4, fp);
fread(datas[2], 1, width*height / 4, fp);
glActiveTexture(GL_TEXTURE0); // 激活第0層
glBindTexture(GL_TEXTURE_2D, texs[0]); // 把0層 綁定到材質(zhì)Y的位置 將顯卡中創(chuàng)建的材質(zhì)綁定到0層材質(zhì)
//修改材質(zhì)內(nèi)容(復制內(nèi)存內(nèi)容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]); // 再與內(nèi)存空間datas進行關(guān)聯(lián)
// 與shader uni 變量關(guān)聯(lián)起來
glUniform1i(unis[0],0);
glActiveTexture(GL_TEXTURE0 + 1 ); // 激活第1層
glBindTexture(GL_TEXTURE_2D, texs[1]); // 把1層 綁定到材質(zhì)U的位置 將顯卡中創(chuàng)建的材質(zhì)綁定到0層材質(zhì)
//修改材質(zhì)內(nèi)容(復制內(nèi)存內(nèi)容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[1]); // 再與內(nèi)存空間datas進行關(guān)聯(lián)
// 與shader uni 變量關(guān)聯(lián)起來
glUniform1i(unis[1], 1);
glActiveTexture(GL_TEXTURE0 + 2); // 激活第2層
glBindTexture(GL_TEXTURE_2D, texs[2]); // 把2層 綁定到材質(zhì)V的位置 將顯卡中創(chuàng)建的材質(zhì)綁定到0層材質(zhì)
//修改材質(zhì)內(nèi)容(復制內(nèi)存內(nèi)容)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[2]); // 再與內(nèi)存空間datas進行關(guān)聯(lián)
// 與shader uni 變量關(guān)聯(lián)起來
glUniform1i(unis[2], 2);
// 開始畫
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 從0 開始 畫4個點
qDebug() << "paintGL";
}
// 窗口尺寸變化
void XVideoWidget::resizeGL(int width, int height)
{
qDebug() << "resizeGL"<< width<< height;
}
項目代碼 頭文件
#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QGLShaderProgram>
class XVideoWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
XVideoWidget(QWidget *parent);
~XVideoWidget();
protected:
//重載三個函數(shù)
// 刷新顯示,實現(xiàn)按鈕的疊加
void paintGL();
// 初始化gl
void initializeGL();
// 窗口尺寸變化
void resizeGL(int width, int height);
private:
// shader程序,通過program運行
QGLShaderProgram program;
// shader中yuv變量地址
GLuint unis[3] = { 0 };
// opengl的 texture 地址
GLuint texs[3] = { 0 };
// 材質(zhì)的內(nèi)存空間
unsigned char *datas[3] = { 0 };
int width = 240;
int height = 128;
};
到了這里,關(guān)于【QT項目:視頻播放器——Qt opengl編程】通過shader完成顯示yuv的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!