1、音視頻播放的基礎(chǔ)知識
內(nèi)容來自雷神博客
1、在Windows平臺下的視頻播放技術(shù)主要有以下三種:GDI,Direct3D和OpenGL;音頻播放技術(shù)主要是DirectSound。
SDL本身并不具有播放顯示的功能,它只是封裝了底層播放顯示的代碼
記錄三種視頻顯示技術(shù):GDI,Direct3D,OpenGL。其中Direct3D包含簡單和復(fù)雜的兩種顯示方式:使用Surface和使用Texture;OpenGL也包含簡單和復(fù)雜的兩種顯示方式:直接畫像素和使用Texture。
GDI 微軟顯示窗口提供的一套顯示機(jī)制,他工作的主要流程就是構(gòu)建BMP文件(原始的RGB數(shù)據(jù),構(gòu)建bmp文件頭),調(diào)用接口將bmp文件繪制到屏幕上。注意都需要轉(zhuǎn)換為RGB格式數(shù)據(jù)。
Direct3D微軟開發(fā)的一套3D繪圖API,Direct3D的抽象概念包括:Devices(設(shè)備),Swap Chains(交換鏈)和Resources(資源)。
Device(設(shè)備)用于渲染3D場景。例如單色設(shè)備就會渲染黑白圖片,而彩色設(shè)備則會渲染彩色圖片。
每一個Device至少要有一個Swap Chain(交換鏈)。一個Swap Chain由一個或多個Back Buffer Surfaces(后臺緩沖表面)組成。渲染在Back Buffer中完成。
Device包含了一系列的Resources(資源),用于定義渲染時候的數(shù)據(jù)
Direct3D API定義了一組Vertices(頂點(diǎn)), Textures(紋理), Buffers(緩沖區(qū))轉(zhuǎn)換到屏幕上的流程。這樣的流程稱為Rendering Pipeline(渲染流水線),具體就是從應(yīng)用程序里讀取vertex頂點(diǎn)數(shù)據(jù),對每個頂點(diǎn)屬性進(jìn)行著色,最后將著色器處理的數(shù)據(jù)輸出給使用者。
2、視頻顯示的基礎(chǔ)知識
三角形;在繪圖中所有平面都可以用三角形繪制出來的,并且顯卡的性能強(qiáng)弱都是比較單位時間內(nèi)畫三角形的個數(shù)的。
后臺緩沖表面,前臺表面,交換鏈,離屏表面。
后臺緩沖表面和前臺表面的概念總是同時出現(xiàn)的。簡單解釋一下它們的作用。當(dāng)我們進(jìn)行復(fù)雜的繪圖操作時,畫面可能有明顯的閃爍。這是由于繪制的東西沒有同時出現(xiàn)在屏幕上而導(dǎo)致的?!扒芭_表面”+“后臺緩沖表面”的技術(shù)可以解決這個問題,就是一個緩沖的作用。
交換鏈;其實就是一個后臺緩沖表面,一個前臺表面。所謂的“交換”,即是在需要呈現(xiàn)后臺緩沖表面中的內(nèi)容的時候,交換這兩個表面的“地位”。即前臺表面變成后臺緩沖表面,后臺緩沖表面變成前臺表面。如此一來,后臺緩沖表面的內(nèi)容就呈現(xiàn)在屏幕上了。原先的前臺表面,則扮演起了新的后臺緩沖表面的角色,準(zhǔn)備進(jìn)行新的繪圖操作。當(dāng)下一次需要顯示畫面的時候,這兩個表面再次交換,如此循環(huán)往復(fù),永不停止。
離屏表面、離屏表面是永遠(yuǎn)看不到的表面(所謂“離屏”),它通常被用來存放位圖,并對其中的數(shù)據(jù)做一些處理。通常的做法是把離屏表面上的位圖復(fù)制到后臺緩沖表面,后臺緩沖表面再顯示到前臺表面。
使用Direct3D的Surface播放視頻一般情況下需要如下步驟:
創(chuàng)建一個窗口(不屬于D3D的API)
初始化
創(chuàng)建一個Device
基于Device創(chuàng)建一個Surface(離屏表面)
循環(huán)顯示畫面
清理
一幀視頻數(shù)據(jù)拷貝至 Surface
開始一個Scene
Surface數(shù)據(jù)拷貝至 后臺緩沖表面
結(jié)束Scene
顯示( 后臺緩沖表面-> 前臺表面)
3、OpenGL開放圖形庫,定義了一個跨編程語言、跨平臺的應(yīng)用程序接口(API)的規(guī)范,它用于生成二維、三維圖像。
OpenGL渲染管線(OpenGL Pipeline)按照特定的順序?qū)D形信息進(jìn)行處理,這些圖形信息可以分為兩個部分:頂點(diǎn)信息(坐標(biāo)、法向量等)和像素信息(圖像、紋理等)。圖形信息最終被寫入幀緩存中,存儲在幀緩存中的數(shù)據(jù)(圖像),可以被應(yīng)用程序獲得(用于保存結(jié)果,或作為應(yīng)用程序的輸入等。
關(guān)鍵就是頂點(diǎn)操作和紋理操作。
通過openGL的紋理顯示圖片
注意通過紋理顯示則;輸入的YUV420P像素數(shù)據(jù)通過Shader轉(zhuǎn)換為YUV數(shù)據(jù),傳送給OpenGL播放。像素的轉(zhuǎn)換YUV到RGB是通過顯卡上的GPU完成的,則效率更高視頻播放性能更好。
使用Shader通過OpenGL的紋理(Texture)播放視頻一般情況下需要如下步驟:
初始化
初始化
創(chuàng)建窗口
設(shè)置繪圖函數(shù)
設(shè)置定時器
初始化Shader
初始化Shader的步驟比較多,主要可以分為3步:創(chuàng)建Shader,創(chuàng)建Program,初始化Texture。
(1) 創(chuàng)建一個Shader對象;Shader有點(diǎn)類似于一個程序的編譯器
1)編寫Vertex Shader和Fragment Shader源碼。
2)創(chuàng)建兩個shader 實例 。
3)給Shader實例指定源碼。
4)在線編譯shaer源碼。
(2) 創(chuàng)建一個Program對象
1)創(chuàng)建program。
2)綁定shader到program。
3)鏈接program。
4)使用porgram。
(3) 初始化Texture??梢苑譃橐韵虏襟E。
1)定義定點(diǎn)數(shù)組
2)設(shè)置頂點(diǎn)數(shù)組
3)初始化紋理
進(jìn)入消息循環(huán)
循環(huán)顯示畫面
設(shè)置紋理
繪制
顯示
2、QT的音頻錄制 QAudioFormat
QAudioFormat記錄音頻參數(shù)的格式
setSampleRate 樣本率
setSampleSize 設(shè)置的位數(shù),S16,S8等,這里不區(qū)分layout,設(shè)置的是位數(shù)而不是字節(jié)數(shù),則這樣要根據(jù)不同的樣本類型進(jìn)行轉(zhuǎn)換。
setChannelCount 設(shè)置通道數(shù)量
setCodec(“audio/pcm”)設(shè)置編碼格式,暫時可能只支持pcm格式的,與聲卡硬件有關(guān)的。
setByteOrder(QAudioFormat::LittleEndian)設(shè)置大小端模式,注意網(wǎng)絡(luò)傳輸一般是采用大端的(低字節(jié)在高位),x86windows一般都是小端的(低位在低字節(jié))。
setSampleType(QAudioFormat::UnSignedInt) 設(shè)置樣本類型
QAudioOutput 打開播放音頻設(shè)備
其構(gòu)造函數(shù)創(chuàng)建是傳入一個設(shè)置好的QAudioFormat對象的
QIODevice *start();創(chuàng)建成功后調(diào)用開始函數(shù),會返回QIODevice ,這個是QT內(nèi)部的一個類,做IO輸入輸出設(shè)備的類。
suspend() 掛起暫停
resume() 恢復(fù)播放
因為音頻不像視頻,如果音頻丟幀了那么現(xiàn)象會有很嚴(yán)重的失真的,因此需要存在緩沖機(jī)制的。
bufferSize() 緩沖的大小
bytesFree() 得到緩沖內(nèi)部還有多少內(nèi)存
bufferSize() - bytesFree() 可以得到我們緩沖區(qū)里面還有多少內(nèi)存沒有釋放的,在做音視頻同步的時候需要考慮。
periodSize() 內(nèi)部硬件讀取多大才寫入,
QIODevice 音頻設(shè)備的抽象
qint64 write(const char *data, qint64 len) 寫入,這個就是我們解碼出來重采樣后直接把數(shù)據(jù)通過write寫入就可以播放了。返回的大小才是真正寫入的大小,也可以重載一下保證把傳入的len都寫入進(jìn)去,還可以采用判斷bytesFree緩沖空間足夠大的時候才把他寫入進(jìn)去,保證都能寫入。
代碼實戰(zhàn)
//注意如果在QT CRTEAT下編寫的話要在pro文件加入QT += multimediawidgets
#include <QCoreApplication>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QThread>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QAudioFormat fmt;
fmt.setSampleRate(44100);//樣本率
fmt.setSampleSize(16); //樣本大小 S16
fmt.setChannelCount(2); //雙通道
fmt.setCodec("audio/pcm"); //設(shè)置pcm編碼器
fmt.setByteOrder(QAudioFormat::LittleEndian);//這是字節(jié)序
fmt.setSampleType(QAudioFormat::UnSignedInt);//設(shè)置樣本類型
QAudioOutput *out = new QAudioOutput(fmt);//創(chuàng)建QAudioOutput
QIODevice *io = out->start(); //開始播放
int size = out->periodSize();//一個周期的大小,硬件設(shè)備一次讀多大
char *buf = new char[size];
FILE *fp = fopen("out.pcm", "rb");
while (!feof(fp))
{
//如果緩沖空間剩余內(nèi)存不夠size,硬件還沒取走
if (out->bytesFree() < size)
{
QThread::msleep(1);
continue;
}
int len = fread(buf, 1, size, fp);
if (len <= 0)break;
io->write(buf, len);
}
fclose(fp);
delete buf;
buf = 0;
return a.exec();
}
總結(jié);QT音頻播放基于三個類進(jìn)行操作
先設(shè)置QAudioFormat音頻格式,
再通過設(shè)置的音頻格式創(chuàng)建QAudioOutput 音頻輸出設(shè)備啟動設(shè)備和設(shè)置緩沖,
最后通過QAudioOutput 的啟動得到具體聲卡設(shè)備的抽象對象QIODevice ,通過向QIODevice 進(jìn)行寫入音頻數(shù)據(jù)實現(xiàn)音頻播放的。
3、QT視頻繪制 QT openGL編程
雷神博客介紹OpenGL基于紋理顯示的流程
因為視頻顯示存在像素格式的轉(zhuǎn)換問題,要從解碼出來的YUV格式轉(zhuǎn)化為顯示需要的RGB格式,視頻的每一幀圖像這么多像素點(diǎn)都需要轉(zhuǎn)換,這是一個很大的開銷,如果這一部分效率不高,那么對整體的視頻播放性能都是十分有影響的,而QTopenGL也是效率很高的,直接操作顯卡。
Shader有點(diǎn)類似于一個程序的編譯器,
1)編寫Vertex Shader和Fragment Shader源碼。GLSL語言編寫類似與C語言的一樣的代碼,它可以放在GPU里面被并行運(yùn)行。
Vertex Shader(如果要存放到文件中就在.vsh)負(fù)責(zé)搞定像素位置,填寫gl_Posizion;
Fragment Shader(Fragment Shader fsh)負(fù)責(zé)搞定像素外觀,填寫 gl_FragColor;
Program有點(diǎn)類似于一個程序的鏈接器。
program對象提供了把需要做的事連接在一起的機(jī)制,在一個program中,shader對象可以連接在一起。
QTopenGL顯示的幾個關(guān)鍵點(diǎn)
QOpenGLWidget (與界面如何交互)
Program GLSL 頂點(diǎn)與片元(如何與顯卡交互的,GLSL 是跑在顯卡上面的)
材質(zhì)紋理Texture (如何寫入ffmpeg數(shù)據(jù)的)
頂點(diǎn)和紋理材質(zhì)
下面有相關(guān)介紹
為什么要采用QT的OpenGL三維的來繪制,直接使用QopenWidget、QWight也是可以的,但是如果使用后面的那么其實圖像顯示和界面的按鈕是一套東西,當(dāng)點(diǎn)擊按鈕刷新的時候就會存在閃屏的情況。因此采用三維的,QTOpenGL這種就是提供三維繪制,就可以自動在界面上疊加了。
使用的時候需要重載這些函數(shù)
void paintGL();//具體繪制在這里面實現(xiàn)
void initializeGL();//初始化
void resizeGL(int width,int height)//當(dāng)窗口發(fā)生變化的時候調(diào)用,這個函數(shù)
還提供了很大的便利,如如果需要視頻放縮的時候就可以直接調(diào)用OpenGL里面的函數(shù),不然就需要在ffmpeg進(jìn)行像素尺寸的轉(zhuǎn)換效率就比較低了并且采用不同的算法效果可能也不一定好。而openGL中就是采用差值的算法來放縮,整體界面就減少失真的情況。
封裝了操作OpenGL對象的函數(shù)類QOpenGLFunctions
QOpenGL相關(guān)的函數(shù)是單獨(dú)有一個類來封裝的,是QOpenGLFunctions
類,這里面就有很多與openGL相關(guān)的函數(shù)了,因此也可以進(jìn)行QOpenGLFunctions繼承來獲取操作openGL的相關(guān)函數(shù)。
QGLShaderProgram編譯運(yùn)行shader和shader交互
shader 著色器
這個程序最終是在顯卡上運(yùn)行的,是通過QGLShaderProgram 來與顯卡進(jìn)行交互的。
QGLShaderProgram 主要封裝的函數(shù)有
編譯運(yùn)行shader
addShaderFromSourceCode 把shader的源代碼加載進(jìn)來,有兩部分代碼,是頂點(diǎn)shader和片元shader(又稱為像素shader)
bindAttributeLocation 設(shè)置傳入的屬性,只要是設(shè)置頂點(diǎn)坐標(biāo)的屬性和材質(zhì)紋理Texture坐標(biāo)的屬性,后面可以傳入頂點(diǎn)坐標(biāo)或材質(zhì)坐標(biāo)然后對應(yīng)坐標(biāo)設(shè)置什么變量是通過他來設(shè)置的。
uniformLocation 獲取shader變量 就是將顯卡當(dāng)中變量的一塊地址取出來
總結(jié);就是編譯運(yùn)行shader、傳入shader程序,設(shè)置變量,獲取變量
GLSL著色器語言 基于openGL設(shè)計的,給顯卡用的語言
OpenGL Shader Language,簡稱GLSL。它是一種類似于C語言的專門為GPU設(shè)計的語言,它可以放在GPU里面被并行運(yùn)行。
頂點(diǎn)著色器,就是針對每個頂點(diǎn)執(zhí)行一次,用于確定頂點(diǎn)的位置,因為在三維空間中要將所有頂點(diǎn)的參數(shù)都獲取。一般頂點(diǎn)著色器都是畫三角形,如計算顯卡的能力都是看他能畫出多少個三角形,這里圖片的顯示我們采用畫矩形,也就是兩個三角形組合。因此我們這里頂點(diǎn)著色器的代碼就是畫兩個三角形組成矩形。

代碼填充就是
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這里不傳入。再傳入的時候告訴他填充的是二維的要他在內(nèi)部填充
片元著色器,就是針對一個平面的,針對每個片元(可以理解為每個像素)執(zhí)行一次,用于確定每個片元(像素)的顏色 。傳入YUV的數(shù)據(jù)過來,至于至于轉(zhuǎn)換可以到顯卡自己那邊轉(zhuǎn)換。片元著色器他有一個內(nèi)置變量gl_FragColor,就是在遍歷像素點(diǎn)的時候?qū)⑦@個值進(jìn)行改變,那么他的顏色一會跟著變化的。
流程就是通過頂點(diǎn)著色器獲取頂點(diǎn)的位置,再通過片元著色器對這些頂點(diǎn)構(gòu)成的片元像素進(jìn)行填充顏色。
GLSL基本語法與C基本相同,這里只是做簡單圖像顯示,真正的GLSL是用來做特效的,涉及算法的,
GLSL 完美支持向量和矩陣操作,因此在像素格式的轉(zhuǎn)換方面十分適合選用,并且提供了大量的內(nèi)置函數(shù)來提供豐富的擴(kuò)展功能的,
他是通過限定符操作來管理輸入輸出類型的,例如傳入頂點(diǎn)坐標(biāo),傳出材質(zhì)紋理等。
材質(zhì)坐標(biāo)信息

與頂點(diǎn)不同的是他是二維的并且坐標(biāo)都是正數(shù)。
代碼設(shè)置;
float *textureVertexData = new float[8]{
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
}
//也可以第三維的0這里不傳入。再傳入的時候告訴他填充的是二維的要他在內(nèi)部填充
三種GLSL變量類型
varying頂點(diǎn)與片元共享,就是有兩部分代碼頂點(diǎn)著色器代碼計算出來的頂點(diǎn)坐標(biāo)通過這個變量傳給片元著色器那段代碼,然后就可以獲取到材質(zhì)位置。可見代碼理解。
atrribute 頂點(diǎn)使用 由bindAttributeLocation傳入,就是我們創(chuàng)建的數(shù)組通過他傳入過去進(jìn)行計算
uniform 程序傳入 uniformLocation獲取地址,就是我們ffmpeg解碼出來的YUV數(shù)據(jù)要傳入進(jìn)去變成uniform。
最后再通過glUniform1i(textureUniformY,0)傳入與材質(zhì)層進(jìn)行綁定
//自動加雙引號 定義常量串都可以使用
#define GET_STR(x) #x
//頂點(diǎn)著色器的shader代碼
const char *vString = GET_STR(
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
gl_Position = vertexIn;//傳入的頂點(diǎn)坐標(biāo)記錄
textureOut = textureIn;//傳入的材質(zhì)坐標(biāo)保存到textureOut
}
);
//自動加雙引號 定義常量串都可以使用
#define GET_STR(x) #x
//片元著色器的shader代碼
const char *tString = GET_STR(
varying vec2 textureOut;//剛剛頂點(diǎn)著色器算出來的坐標(biāo)
uniform sampler2D tex_y;//uniform是外部傳入的變量
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main(void)
{
//420P是平面存儲的,最后都是要轉(zhuǎn)換為每個像素都是有yuv再方便轉(zhuǎn)換
//用的是灰度圖的形式存儲的
//如安裝那邊硬解碼出來的都是yuv420sp,uv是打包存在一起的,則不能使用這套shader代碼了,要增加透明度存儲方式。
vec3 yuv;
vec3 rgb;
//根據(jù)那個坐標(biāo)把材質(zhì)坐標(biāo)計算出來
//傳入材質(zhì),和坐標(biāo) 返回材質(zhì)當(dāng)中的顏色rgb,但是用灰度圖存儲的,都是一樣的。
//三個材質(zhì)就可以拼出一個yuv的數(shù)據(jù)
yuv.x = texture2D(tex_y, textureOut).r;//獲取材質(zhì)當(dāng)中的顏色
yuv.y = texture2D(tex_u, textureOut).r - 0.5;
yuv.z = texture2D(tex_v, textureOut).r - 0.5;
//轉(zhuǎn)換公式
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);//轉(zhuǎn)換成顯示的顏色,再設(shè)置一下
}
);
創(chuàng)建材質(zhì)
shader其實就是繪制某一個材質(zhì),那么就要看創(chuàng)建材質(zhì)了。qt也有創(chuàng)建材質(zhì)的函數(shù),但是openGL這邊要簡單一點(diǎn)。
glGenTextures(1,t);創(chuàng)建幾個材質(zhì),傳入二維數(shù)組存放創(chuàng)建的材質(zhì)地址
glBindTexture(GL_TEXTURE_2D *t)進(jìn)行綁定 綁定為2d的圖像
glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)設(shè)置屬性 放大的屬性
glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)設(shè)置屬性 縮小的屬性
GL_TEXTURE_2D操作的是2D紋理
GL_TEXTURE_MAG_FILTER 放大過濾
GL_TEXTURE_MIN_FILTER 縮小過濾
GL_LINEAR用線性差值的方式進(jìn)行縮放 使用距離當(dāng)前渲染像素中心最近的四個紋素加權(quán)平均值,則最后的圖像變化就比較榮和
寫入和繪制材質(zhì)
glActiveTexture(GL_TEXTURE0);激活材質(zhì) 序號0
glBindTexture(GL_TEXTURE_2D,id_y);綁定之前創(chuàng)建的材質(zhì)
在顯存當(dāng)中創(chuàng)建材質(zhì)
//將openGL里面的創(chuàng)建的材質(zhì)設(shè)置傳入到內(nèi)存當(dāng)中,就是顯存到內(nèi)存的操作
//GL_LUMINANCE,pixel_w,pixel_h 這個就表示已灰度圖的形式存放的,傳入寬高
glTexImage2D(GL_TEXTURE_2D,//創(chuàng)建為了
0, //細(xì)節(jié)顯示 0默認(rèn) 是拉遠(yuǎn)拉近攝像機(jī)相關(guān)的顯示
GL_LUMINANCE,//gpu顯卡內(nèi)部的格式,就是創(chuàng)建的材質(zhì)是存放到顯卡當(dāng)中,這是灰度圖
pixel_w,pixel_h,
0,
GL_LUMINANCE, 數(shù)據(jù)格式,內(nèi)存到顯存的格式,但是無法轉(zhuǎn)換因此要一致
GL_UNSIGNED_BYTE,//單個像素的存放格式
plane[0])
//因為創(chuàng)建紋理有很大的開銷,因此提供了一個修改紋理的函數(shù)
glTexSubImage2D() 修改紋理 存在偏移值的變量就是表示從當(dāng)前的紋理當(dāng)中只取一部分
glUniform1i(textureUniformY, 0);材質(zhì)設(shè)完之后與shaeder相關(guān)聯(lián),通過uniform變量出入到材質(zhì)0層
glDrawArrays(GL_TRINGLE_STRIP,0,4)//繪制矩形,四個頂點(diǎn)
顯示播放整體流程分析
重載QOpenGLWidget的三個函數(shù)
//重載那三個函數(shù)
//刷新初始化
void paintGL();//具體繪制在這里面實現(xiàn)
//初始化gl
void initializeGL();//初始化
//窗口大小變化
void resizeGL(int width,int height);//當(dāng)窗口發(fā)生變化的時候調(diào)用,這個函數(shù)
initializeGL 在OPenGL創(chuàng)建的時候就會調(diào)用
初始化OpenGL函數(shù)
initializeOpenGLFunctions()
調(diào)用著色器program添加shader代碼
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Fragment,tString);
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Vertex,vString);
//QGLShader::Fragment 紋理材質(zhì)類型 tString,vString常量串是Shader具體源代碼
//這個addShader還可以從文件獲取 Fragment是fsh文件,Vertex是vsh文件
//給shader綁定屬性 給頂點(diǎn)shader的attribute賦值,A_VER、T_VER這個只是標(biāo)記作用
program.bindAttributeLocation("vertexIn", A_VER);
program.bindAttributeLocation("textureIn",T_VER);
//編譯,綁定shader
program.link();
program.bind();
//定義頂點(diǎn)和材質(zhì)紋理的坐標(biāo)
static const GLfloat ver[] = {}
static const GLfloat tex[] = {}
//將頂點(diǎn)和材質(zhì)紋理的坐標(biāo)設(shè)置到shader當(dāng)中去,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當(dāng)中獲取uniform sampler2D的位置,之后好傳入
unis[0] = program.uniformLocation("tex_y");
unis[1] = program.uniformLocation("tex_u");
unis[2] = program.uniformLocation("tex_v");
//創(chuàng)建材質(zhì),并綁定類型,屬性及創(chuàng)建空間,對yuv都進(jìn)行操作,注意uv的大小變化
glGenTextures(3,texs);
//Y
glBindTexture(GL_TEXTURE_2D, texs[0]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//width/2 height/2根據(jù)yuv420的特性來的 uv是y的四分之一
//U
glBindTexture(GL_TEXTURE_2D, texs[1]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//V
glBindTexture(GL_TEXTURE_2D, texs[2]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//分配材質(zhì)的內(nèi)存空間
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()函數(shù)調(diào)用繪畫函數(shù)
QTimer *ti = new QTimer(this);
connect(ti, SIGNAL(timeout()), this, SLOT(update()));//定時器刷新到update()里面取
ti->start(40);
paintGL 繪畫時調(diào)用
//讀取幀數(shù)據(jù),存放到分配材質(zhì)的內(nèi)存空間
fread(datas[0],1,width*height, fp);
fread(datas[1], 1, width*height/4, fp);
fread(datas[2], 1, width*height/4, fp);
//在顯卡中創(chuàng)建材質(zhì) 并綁定到了0層渲染材質(zhì)
glActiveTexture(GL_TEXTURE0);//激活第0層
glBindTexture(GL_TEXTURE_2D, texs[0]);//把0層綁定Y材質(zhì)
//修改材質(zhì)(復(fù)制內(nèi)存內(nèi)存)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
//與shader uni變量關(guān)聯(lián)起來
glUniform1i(unis[0], 0);
重復(fù)三次
glActiveTexture(GL_TEXTURE0+1);//激活第1層
glBindTexture(GL_TEXTURE_2D, texs[1]);//把1層綁定Y材質(zhì)
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材質(zhì)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
glUniform1i(unis[2], 2);
最后繪畫 //畫三角形 從0開始 四個頂點(diǎn),
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//畫三角形 從0開始 四個頂點(diǎn),
視頻播放實踐
拿到output240X128.yuv也可以使用命令制作ffmpeg -i 1.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
注意幾點(diǎn)
控件這里

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這個變量如果沒有添加進(jìn)來則槽函數(shù)無法調(diào)用
Q_OBJECT
public:
XVideoWidget(QWidget *parent);
~XVideoWidget();
//從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;
protected:
//重載那三個函數(shù)
//刷新初始化
void paintGL();//具體繪制在這里面實現(xiàn)
//初始化gl
void initializeGL();//初始化
//窗口大小變化
void resizeGL(int width,int height);//當(dāng)窗口發(fā)生變化的時候調(diào)用,這個函數(shù)
private:
QGLShaderProgram program;
};
#endif // XVIDEOWIDGET_H
XVideoWidget.cpp文件
#include "XVideoWidget.h"
#include <QDebug>
#include <QTimer>
FILE *fp = NULL;
//定義一個宏表示數(shù)組
#define A_VER 3
#define T_VER 4
//自動加雙引號 定義常量串都可以使用
#define GET_STR(x) #x
//頂點(diǎn)著色器的shader代碼
const char *vString = GET_STR(
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;//varying頂點(diǎn)和片元shader共享變量
void main(void)
{
gl_Position = vertexIn;//傳入的頂點(diǎn)坐標(biāo)記錄
textureOut = textureIn;//傳入的材質(zhì)坐標(biāo)保存到textureOut,從而傳出去了
}
);
//片元著色器的shader代碼
const char *tString = GET_STR(
varying vec2 textureOut;//剛剛頂點(diǎn)著色器算出來的坐標(biāo) 共享的
uniform sampler2D tex_y;//uniform是外部傳入的變量
uniform sampler2D tex_u;//sampler2D是一個2d圖像
uniform sampler2D tex_v;
void main(void)
{
//420P是平面存儲的,最后都是要轉(zhuǎn)換為每個像素都是有yuv再方便轉(zhuǎn)換
//用的是灰度圖的形式存儲的
//如安裝那邊硬解碼出來的都是yuv420sp,uv是打包存在一起的,則不能使用這套shader代碼了,要增加透明度存儲方式。
vec3 yuv;
vec3 rgb;
//根據(jù)那個坐標(biāo)把材質(zhì)坐標(biāo)計算出來
//傳入材質(zhì),和坐標(biāo) 返回材質(zhì)當(dāng)中的顏色rgb,但是用灰度圖存儲的,都是一樣的。
//三個材質(zhì)就可以拼出一個yuv的數(shù)據(jù)
yuv.x = texture2D(tex_y, textureOut).r;//獲取材質(zhì)當(dāng)中的顏色
yuv.y = texture2D(tex_u, textureOut).r - 0.5;//四舍五入
yuv.z = texture2D(tex_v, textureOut).r - 0.5;
//轉(zhuǎn)換公式
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);//轉(zhuǎn)換成顯示的顏色,再設(shè)置一下
}
);
XVideoWidget::XVideoWidget(QWidget *parent)
:QOpenGLWidget(parent)//初始化列表調(diào)用父類構(gòu)造方法調(diào)用paint畫出OpenGLWidget
{
int a = 1;
}
XVideoWidget::~XVideoWidget()
{
}
//刷新初始化,每次移動都會調(diào)用一次,進(jìn)行刷新
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);
//在顯卡中創(chuàng)建材質(zhì) 并綁定到了0層渲染材質(zhì)
glActiveTexture(GL_TEXTURE0);//激活第0層
glBindTexture(GL_TEXTURE_2D, texs[0]);//把0層綁定Y材質(zhì)
//修改材質(zhì)(復(fù)制內(nèi)存內(nèi)存)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
//與shader uni變量關(guān)聯(lián)起來
glUniform1i(unis[0], 0);
glActiveTexture(GL_TEXTURE0+1);//激活第1層
glBindTexture(GL_TEXTURE_2D, texs[1]);//把1層綁定Y材質(zhì)
//修改材質(zhì)(復(fù)制內(nèi)存內(nèi)存)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
//與shader uni變量關(guān)聯(lián)起來
glUniform1i(unis[1], 1);
glActiveTexture(GL_TEXTURE0+2);//激活第0層
glBindTexture(GL_TEXTURE_2D, texs[2]);//把0層綁定Y材質(zhì)
//修改材質(zhì)(復(fù)制內(nèi)存內(nèi)存)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
//與shader uni變量關(guān)聯(lián)起來
glUniform1i(unis[2], 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//畫三角形 從0開始 四個頂點(diǎn),
qDebug() << "paintGL";
}
//初始化gl
void XVideoWidget::initializeGL()
{
qDebug() << "initializeGL";
//初始化 opengl(QOpenGLFunctions繼承過來的函數(shù))
initializeOpenGLFunctions();
//program(opengl和qt都提供了)加載shader腳本文件 頂點(diǎn)shader 片元shader
// qt 提供的 QGLShaderProgram
qDebug() << "addShaderFromSourceCode :" <<program.addShaderFromSourceCode(QGLShader::Fragment,tString);//在源代碼中添加好一些,從文件怕有泄露
qDebug() << "addShaderFromSourceCode :" << program.addShaderFromSourceCode(QGLShader::Vertex,vString);
//設(shè)置頂點(diǎn)坐標(biāo)的變量
program.bindAttributeLocation("vertexIn", A_VER);//這個vertexIn變量對應(yīng)的本地位置 vertexIn是頂點(diǎn)shader定義的 3的位置
//材質(zhì)坐標(biāo)
program.bindAttributeLocation("textureIn",T_VER);//下標(biāo)四,等下往4的位置存
//編譯shader
qDebug() << "program.link():" << program.link();
//綁定shader
qDebug() << "program.link():" << program.bind();
//傳遞頂點(diǎn)和材質(zhì)坐標(biāo),
//這個坐標(biāo)是在這個函數(shù)中時候,之后draw的時候還要使用的,只傳入二維
static 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
};
//將坐標(biāo)寫入GL當(dāng)中
glVertexAttribPointer(A_VER,//索引地址
2,//表示一點(diǎn)頂點(diǎn)幾個元素,
GL_FLOAT,//存放的類型,浮點(diǎn)數(shù)
0,
0,
ver //頂點(diǎn)地址
);
//頂點(diǎn)坐標(biāo)生效
glEnableVertexAttribArray(A_VER);
//材質(zhì) 就是shader準(zhǔn)備數(shù)據(jù)
glVertexAttribPointer(T_VER,2, GL_FLOAT, 0,0,tex);
glEnableVertexAttribArray(T_VER);
//從shader當(dāng)中獲取材質(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);//創(chuàng)建三個對象
//分別對每個材質(zhì)進(jìn)行設(shè)置
//Y
glBindTexture(GL_TEXTURE_2D, texs[0]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//width/2 height/2根據(jù)yuv420的特性來的 uv是y的四分之一
//U
glBindTexture(GL_TEXTURE_2D, texs[1]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//V
glBindTexture(GL_TEXTURE_2D, texs[2]);//綁定2D
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//設(shè)置屬性 放大 線性差值的屬性
glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//創(chuàng)建材質(zhì)顯卡的空間 ,與內(nèi)存的空間是不一樣的
//分配材質(zhì)的內(nèi)存空間
datas[0] = new unsigned char[width*height];
datas[1] = new unsigned char[width*height/4];
datas[2] = new unsigned char[width*height/4];
//讀取材質(zhì)并顯示
//再通過opengl接口,實現(xiàn)內(nèi)存空間和顯卡空間的轉(zhuǎn)換
fp = fopen("output240X128.yuv", "rb");
if(!fp)
{
qDebug() << "fopen error";
}
//啟動定時器
QTimer *ti = new QTimer(this);
connect(ti, SIGNAL(timeout()), this, SLOT(update()));//定時器刷新到update()里面取
ti->start(40);
}
//窗口大小變化
void XVideoWidget::resizeGL(int width,int height)
{
qDebug() << "resizeGL width = " << width << " height = " << height;
}
源碼:Git地址
內(nèi)容參考來自夏曹俊老師的ffmpeg和QT開發(fā)
原文?FFMpeg-3、基于QT實現(xiàn)音視頻播放顯示_雷神 博客 opengl_賣酒的小碼農(nóng)的博客-CSDN博客
?
★文末名片可以免費(fèi)領(lǐng)取音視頻開發(fā)學(xué)習(xí)資料,內(nèi)容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音視頻學(xué)習(xí)路線圖等等。文章來源:http://www.zghlxwxcb.cn/news/detail-770272.html
見下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章來源地址http://www.zghlxwxcb.cn/news/detail-770272.html
到了這里,關(guān)于FFMpeg-3、基于QT實現(xiàn)音視頻播放顯示的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!