【QT项目:视频播放器——Qt opengl编程】通过shader完成显示yuv
通過Qt opengl不是為了3D繪制,而是為了將視頻繪制起來
使用opengl 可以極大降低yuv轉rgb的轉換開銷
使用Opengl需要考慮三大問題:
1、QOpenGLWidget(與界面如何交互)
1、為什么用QT的opengl
簡單,界面可以自動疊加
void paintGL(); // 具體的繪制寫在該函數里
void initializeGL(); // 材質初始化
void resizeGL(int width, int height); // 當窗口發生變化(縮放)
QOpenGLFunctions // 不需要手動添加庫,直接繼承該函數
2、Program GLSL 頂點和片元(如何與顯卡交互)
GLSL是新的語言,通過GLSL與顯卡進行交互,GLSL 跑在顯卡上
QGLShaderProgram
Program用來編譯和運行Shader代碼,包括與shader的交互
編譯和運行shader // shader兩部分:頂點和片元
addShaderFromSourceCode // 加入shader代碼
bindAttributeLocation // 設置傳入的變量, 頂點和坐標
uniformLocation // 獲取變量
GLSL著色器語言,專門針對opengl所設計,用于顯卡運行
頂點著色器是針對每個頂點執行一次,用于確定頂點的位置;——三維
片元著色器是針對每個片元(可以理解為每個像素)執行一次,用于確定每個片元(像素)的顏色 ——平面
GLSL基本語法與C基本相同
它完美地支持向量和矩陣操作
GLSL提供了大量的內置函數來提供豐富的拓展功能
它是通過限定符操作來管理輸入輸出類型
頂點著色器(畫兩個三角形,形成一個矩形)
顯卡運算能力:值以三角形為單位,所畫的數量
頂點著色器被使用在傳統的基于頂點的操作, 例如位移矩陣、計算光照方程、產生貼圖坐標。
頂點著色器被應用指定, 應用于客戶的頂點轉化。
片元著色器
在片元著色器階 段只有唯一的 varying 輸出變量- 即內建變量: gl_FragColor(像素點顏色)
頂點信息
材質坐標信息(全部在第一象限)
傳入頂點和材質坐標
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices); ATTRIB_VERTEX:頂點坐標 2 :坐標數量 GL_FLOAT:單位數0:法線 0:步寬 glEnableVertexAttribArray(ATTRIB_VERTEX); 使生效 glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);材質坐標 glEnableVertexAttribArray(ATTRIB_TEXTURE);三種GLSL變量類型
varying 頂點與片元共享 // 算出頂點坐標 attribute 頂點使用,由bindAttributeLocation傳入 uniform 程序傳入 uniformLocation獲取地址 glUniform1i(textureUniformY, 0); 設置頂點shader
attribute vec4 vertexIn; // 頂點輸入 attribute vec2 textureIn; // 材質輸入void main(void) { gl_Position = vertexIn;textureOut = textureIn; }片元shader
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; rgb = mat3(1, 1, 1, 0, -0.39465, 2.03211, 1.13983, -0.58060, 0) * yuv; gl_FragColor = vec4(rgb, 1);}3、材質Texture(如何寫入ffmpeg數據)
前面通過OpenGLWidget管理整個窗口,最終顯示涉及在某個材質上,最終要把ffmpeg數據寫入,要考慮如何在材質中寫入ffmpeg數據
創建材質
glGenTextures(1, t); // 創建材質個數,指針地址 glBindTexture(GL_TEXTURE_2D, *t); // 綁定材質類型成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個紋素加權平均值.ps:如果是一個點直接復制四倍的話,會產生馬賽克的現象
加權計算的話就比較柔和
寫入和繪制材質
glActiveTexture(GL_TEXTURE0); // 激活材質,通過編號 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層材質,材質可以多層 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 從0開始繪制,4個 glViewport(0, 0, width, height);glTexImage2D 材質創建函數
glTexImage2D(GL_TEXTURE_2D, // 在顯存中創建紋理 0, //細節 0默認 鏡頭拉遠拉近 GL_RED, //gpu內部格式 videoWidth, videoHeight , GL_RED, //數據格式 數據格式和gpu內部格式 要一致GL_UNSIGNED_BYTE //像素的數據類型, 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 4FILE *fp = NULL; // 文件接口// 頂點shader const char *vString = GET_STR(attribute vec4 vertexIn; // 頂點坐標attribute vec2 textureIn; // 材質坐標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;// 用矩陣轉換yuvrgb = 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數據 // 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函數(QOpenGLFunctions繼承)函數initializeOpenGLFunctions();// 用program加載shader(頂點和片元)腳本// 片元(像素)shaderqDebug() << program.addShaderFromSourceCode(QGLShader::Fragment, tString);// 頂點shaderqDebug() << program.addShaderFromSourceCode(QGLShader::Vertex, vString);// #############################################以上shader已創建好,接下來要與shader進行交互// 設置頂點坐標的變量program.bindAttributeLocation("vertexIn", A_VER); // 將變量名稱關聯到一個索引中,索引可以用一個宏來實現// 設置材質坐標program.bindAttributeLocation("textureIn", T_VER);// 編譯shader,打印qDebug() << "program.link() = " << program.link();// 綁定shader,打印qDebug() << "program.bind() = " << program.bind(); // 將opengl 和shader關聯起來// 傳遞頂點和材質坐標// 頂點 頂點坐標是三維,但最后一位不傳默認為0static const GLfloat ver[] = {-1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f,1.0f,1.0f,};// 材質static const GLfloat tex[] = {0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 0.0f};// 將坐標寫入opengl中//頂點 位置索引,一個頂點的元素個數(2),存放類型GL_FLOAT,是否有法線向量 0沒有 0默認,ver頂點地址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獲取材質unis[0] = program.uniformLocation("tex_y");unis[1] = program.uniformLocation("tex_u");unis[2] = program.uniformLocation("tex_v");// 創建材質glGenTextures(3, texs);// 綁定YglBindTexture(GL_TEXTURE_2D, texs[0]);// 放大過濾,線性插值(要對周邊的點進行加權處理,有漸變的效果) GL_NEAREST()臨近插值,效率高(當前點直接復制),但是馬賽克嚴重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);// 綁定UglBindTexture(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);// 創建材質顯卡空間glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width / 2, height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);// 綁定VglBindTexture(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);// 創建材質顯卡空間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]; // Ydatas[1] = new unsigned char[width * height / 4]; // Udatas[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刷新一次}// 刷新顯示,實現按鈕的疊加 void XVideoWidget::paintGL() {if (feof(fp)) // 假如到了結尾,移到開頭的位置{fseek(fp, 0, SEEK_SET);// 循環播放}// 讀取數據,存放在datasfread(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層 綁定到材質Y的位置 將顯卡中創建的材質綁定到0層材質//修改材質內容(復制內存內容)glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]); // 再與內存空間datas進行關聯// 與shader uni 變量關聯起來glUniform1i(unis[0],0);glActiveTexture(GL_TEXTURE0 + 1 ); // 激活第1層glBindTexture(GL_TEXTURE_2D, texs[1]); // 把1層 綁定到材質U的位置 將顯卡中創建的材質綁定到0層材質//修改材質內容(復制內存內容)glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[1]); // 再與內存空間datas進行關聯// 與shader uni 變量關聯起來glUniform1i(unis[1], 1);glActiveTexture(GL_TEXTURE0 + 2); // 激活第2層glBindTexture(GL_TEXTURE_2D, texs[2]); // 把2層 綁定到材質V的位置 將顯卡中創建的材質綁定到0層材質//修改材質內容(復制內存內容)glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RED, GL_UNSIGNED_BYTE, datas[2]); // 再與內存空間datas進行關聯// 與shader uni 變量關聯起來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_OBJECTpublic:XVideoWidget(QWidget *parent);~XVideoWidget(); protected://重載三個函數// 刷新顯示,實現按鈕的疊加void paintGL();// 初始化glvoid initializeGL();// 窗口尺寸變化void resizeGL(int width, int height);private:// shader程序,通過program運行QGLShaderProgram program;// shader中yuv變量地址GLuint unis[3] = { 0 };// opengl的 texture 地址GLuint texs[3] = { 0 };// 材質的內存空間unsigned char *datas[3] = { 0 };int width = 240;int height = 128;};總結
以上是生活随笔為你收集整理的【QT项目:视频播放器——Qt opengl编程】通过shader完成显示yuv的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1+X云计算平台运维与开发认证(初级)样
- 下一篇: bitnami 的pgpool连接pos