最簡單的視音頻播放示例6:OpenGL播放YUV420P(通過Texture,使用Shader)


本文記錄OpenGL播放視頻的技術。上一篇文章中,介紹了一種簡單的使用OpenGL顯示視頻的方式。但是那還不是OpenGL顯示視頻技術的精髓。和Direct3D一樣,OpenGL更好的顯示視頻的方式也是通過紋理(Texture)。本文介紹OpenGL通過紋理的方式顯示視頻的技術。

OpenGL中坐標和Direct3D坐標的不同

OpenGL中的紋理的坐標和Direct3D中的坐標是不一樣的。

在Direct3D中。紋理坐標如下圖所示。取值是0到1。坐標系原點在左上角。

物體表面坐標如下圖所示。取值是實際的像素值。坐標系原點在左上角。

OpenGL紋理坐標取值范圍是0-1,坐標原點位於左下角。這一點和Direct3D是不同的,Direct3D紋理坐標的取值雖然也是0-1,但是他的坐標原點位於左上角。

 

在OpenGL中,物體表面坐標取值范圍是-1到1。坐標系原點在中心位置。

 

OpenGL視頻顯示的流程

有關紋理方面的知識已經在文章《最簡單的視音頻播放示例4:Direct3D播放RGB(通過Texture)》中有詳細的記錄。OpenGL中紋理的概念和Direct3D中紋理的概念基本上是等同的,因此不再重復記錄了。

本文記錄的程序,播放的是YUV420P格式的像素數據。上一篇文章中的程序也可以播放YUV420P格式的像素數據。但是它們的原理是不一樣的。上一篇文章中,輸入的YUV420P像素數據通過一個普通的函數轉換為RGB數據后,傳送給OpenGL播放。也就是像素的轉換是通過CPU完成的。本文的程序,輸入的YUV420P像素數據通過Shader轉換為YUV數據,傳送給OpenGL播放。像素的轉換是通過顯卡上的GPU完成的。通過本程序,可以了解使用OpenGL進行GPU編程的基礎知識。

使用Shader通過OpenGL的紋理(Texture)播放視頻一般情況下需要如下步驟:
1. 初始化

1) 初始化
2) 創建窗口
3) 設置繪圖函數
4) 設置定時器
5) 初始化Shader
初始化Shader的步驟比較多,主要可以分為3步:創建Shader,創建Program,初始化Texture。
(1) 創建一個Shader對象
1)編寫Vertex Shader和Fragment Shader源碼。
2)創建兩個shader 實例 。
3)給Shader實例指定源碼。
4)在線編譯shaer源碼。
(2) 創建一個Program對象
1)創建program。
2)綁定shader到program。
3)鏈接program。
4)使用porgram。
(3) 初始化Texture。可以分為以下步驟。
1)定義定點數組
2)設置頂點數組
3)初始化紋理
6) 進入消息循環

2. 循環顯示畫面

1) 設置紋理
2) 繪制
3) 顯示

下面詳述一下使用Shader通過OpenGL的紋理的播放YUV的步驟。有些地方和上一篇文章是重復的,會比較簡單的提一下。
1. 初始化
1) 初始化

glutInit()用於初始化glut庫。它原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glutInit(int *argcp, char **argv);  


它包含兩個參數:argcp和argv。一般情況下,直接把main()函數中的argc,argv傳遞給它即可。
glutInitDisplayMode()用於設置初始顯示模式。它的原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glutInitDisplayMode(unsigned int mode);  


需要注意的是,如果使用雙緩沖(GLUT_DOUBLE),則需要用glutSwapBuffers ()繪圖。如果使用單緩沖(GLUT_SINGLE),則需要用glFlush()繪圖。
在使用OpenGL播放視頻的時候,我們可以使用下述代碼:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );  



2) 創建窗口
glutInitWindowPosition()用於設置窗口的位置。可以指定x,y坐標。
glutInitWindowSize()用於設置窗口的大小。可以設置窗口的寬,高。
glutCreateWindow()創建一個窗口。可以指定窗口的標題。
上述幾個函數十分基礎,不再詳細敘述。直接貼出一段示例代碼:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. glutInitWindowPosition(100, 100);  
  2. glutInitWindowSize(500, 500);  
  3. glutCreateWindow("Simplest Video Play OpenGL");   



3) 設置繪圖函數
glutDisplayFunc()用於設置繪圖函數。操作系統在必要時刻就會調用該函數對窗體進行重新繪制操作。類似於windows程序設計中處理WM_PAINT消息。例如,當把窗口移動到屏幕邊上,然后又移動回來的時候,就會調用該函數對窗口進行重繪。它的原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glutDisplayFunc(void (*func)(void));  


其中(*func)用於指定重繪函數。
例如在視頻播放的時候,指定display()函數用於重繪:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. glutDisplayFunc(&display);  


4) 設置定時器
播放視頻的時候,每秒需要播放一定的畫面(一般是25幀),因此使用定時器每間隔一段時間調用一下繪圖函數繪制圖形。定時器函數glutTimerFunc()的原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glutTimerFunc(unsigned int millis, void (*func)(int value), int value);  


它的參數含義如下:

millis:定時的時間,單位是毫秒。1秒=1000毫秒。

(*func)(int value):用於指定定時器調用的函數。

value:給回調函數傳參。比較高端,沒有接觸過。

 

如果只在主函數中寫一個glutTimerFunc()函數的話,會發現只會調用該函數一次。因此需要在回調函數中再寫一個glutTimerFunc()函數,並調用回調函數自己。只有這樣才能實現反反復復循環調用回調函數。
例如在視頻播放的時候,指定每40毫秒調用一次timeFunc ()函數:
主函數中:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. glutTimerFunc(40, timeFunc, 0);  


而后在timeFunc()函數中如下設置。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void timeFunc(int value){  
  2.     display();  
  3.     // Present frame every 40 ms  
  4.     glutTimerFunc(40, timeFunc, 0);  
  5. }  


這樣就實現了每40ms調用一次display()。


5) 初始化Shader
初始化Shader的步驟比較多,主要可以分為3步:創建Shader,創建Program,初始化Texture。它們的步驟如下所示。
(1) 創建一個Shader對象
Shader有點類似於一個程序的編譯器。創建一個Shader可以分成以下4步:

1)編寫Vertex Shader和Fragment Shader源碼。
2)創建兩個shader 實例:glCreateShader()。
3)給Shader實例指定源碼:glShaderSource()。
4)在線編譯shaer源碼 glCompileShader()。

下面詳細分析這4步。
1) 編寫Vertex Shader和Fragment Shader源碼。
在這里用到了一種新的語言:OpenGL Shader Language,簡稱GLSL。它是一種類似於C語言的專門為GPU設計的語言,它可以放在GPU里面被並行運行。
OpenGL的着色器有.fsh和.vsh兩個文件。這兩個文件在被編譯和鏈接后就可以產生可執行程序與GPU交互。.vsh 是Vertex Shader(頂點着色器),用於頂點計算,可以理解控制頂點的位置,在這個文件中我們通常會傳入當前頂點的位置,和紋理的坐標。.fsh 是Fragment Shader(片元着色器),在這里面我可以對於每一個像素點進行重新計算。
下面這張圖可以更好的解釋Vertex Shader和Fragment Shader的作用。這張圖是OpenGL的渲染管線。其中的信息太多先不一一記錄了。從圖中可以看出,Vertex Shader在前,Fragment Shader在后。

 

在這里貼出本文的示例程序的fsh和vsh的代碼。

Shader.vsh

[plain]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. attribute vec4 vertexIn;   
  2. attribute vec2 textureIn;  
  3. varying vec2 textureOut;  
  4. void main(void)  
  5. {  
  6.     gl_Position = vertexIn;   
  7.     textureOut = textureIn;  
  8. }  


Shader.fsh

[plain]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. varying vec2 textureOut;  
  2. uniform sampler2D tex_y;  
  3. uniform sampler2D tex_u;  
  4. uniform sampler2D tex_v;  
  5. void main(void)  
  6. {  
  7.     vec3 yuv;  
  8.     vec3 rgb;      
  9.     yuv.x = texture2D(tex_y, textureOut).r;  
  10.     yuv.y = texture2D(tex_u, textureOut).r - 0.5;  
  11.     yuv.z = texture2D(tex_v, textureOut).r - 0.5;  
  12.     rgb = mat3( 1,       1,         1,  
  13.                 0,       -0.39465,  2.03211,  
  14.                 1.13983, -0.58060,  0) * yuv;      
  15.     gl_FragColor = vec4(rgb, 1);  
  16. }  


從上述代碼中可以看出GLSL的語法和C語言很類似。每一個Shader程序都有一個main函數,這一點和c語言是一樣的。這里的變量命名規則保持跟c一樣就行了,注意gl_開頭的變量名是系統內置的變量。有以下幾種變量:

attribute:外部傳入vsh文件的變量,每一個頂點都會有這兩個屬性。變化率高,用於定義每個點。
varying:用於 vsh和fsh之間相互傳遞的參數。
uniform:外部傳入vsh文件的變量。變化率較低,對於可能在整個渲染過程沒有改變,只是個常量。
上文代碼中使用了以下數據類型:
vec2:包含了2個浮點數的向量
vec3:包含了3個浮點數的向量
vec4:包含了4個浮點數的向量
sampler1D:1D紋理着色器
sampler2D:2D紋理着色器
sampler3D:3D紋理着色器
mat2:2*2維矩陣 
mat3:3*3維矩陣 

mat4:4*4維矩陣

 

上文代碼中還使用到了OpenGL的幾個全局變量:
gl_Position:原始的頂點數據在Vertex Shader中經過平移、旋轉、縮放等數學變換后,生成新的頂點位置(一個四維 (vec4) 變量,包含頂點的 x、y、z 和 w 值)。新的頂點位置通過在Vertex Shader中寫入gl_Position傳遞到渲染管線的后繼階段繼續處理。
gl_FragColor:Fragment Shader的輸出,它是一個四維變量(或稱為 vec4)。gl_FragColor 表示在經過着色器代碼處理后,正在呈現的像素的 R、G、B、A 值。
Vertex Shader是作用於每一個頂點的,如果Vertex有三個點,那么Vertex Shader會被執行三次。Fragment Shader是作用於每個像素的,一個像素運行一次。從源代碼中可以看出,像素的轉換在Fragment Shader中完成。
在網上看到兩張圖可以很好地說明Vertex Shader和Fragment Shader的作用:

 
 

Vertex Shader(頂點着色器)主要是傳入相應的Attribute變量、Uniforms變量、采樣器以及臨時變量,最后生成Varying變量,以及gl_Posizion等變量。Fragment Shade(片元着色器)可以執行紋理的訪問、顏色的匯總、霧化等操作,最后生成gl_FragColor變量。有高手總結如下:“vsh負責搞定像素位置,填寫gl_Posizion;fsh負責搞定像素外觀,填寫 gl_FragColor。”

2) 創建兩個shader 實例。
創建一個容納shader的容器。用glCreateShader ()創建一個容納shader的容器,它的原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. int glCreateShader (int type)  


其中type包含2種: 

GLES20.GL_VERTEX_SHADER:Vertex Shader.

GLES20.GL_FRAGMENT_SHADER:Fragment Shader. 

 

如果調用成功的話,函數將返回一個整形的正整數作為Shader容器的id。
3) 給Shader實例指定源碼。
Shader容器中添加shader的源代碼。源代碼應該以字符串數組的形式表示。glShaderSource函數的原型如下: 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glShaderSource (int shader, String string)   


參數含義如下: 

shader:是代表shader容器的id(由glCreateShader()返回的整形數)。

strings:是包含源程序的字符串數組。

 

如果感覺通過“字符串數組”的方式寫源代碼不太習慣的話,可以把源代碼寫到單獨的一個文本文件里。然后在需要源代碼的時候,讀取該文本文件中的所有內容。
4) 在線編譯Shader源碼。
使用glCompileShader()對shader容器中的源代碼進行編譯。函數的原型如下:  

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glCompileShader (int shader)  


其中shader是代表Shader容器的id。
在編譯完成后,可能需要調試。調試一個Shader是非常困難的。Shader的世界里沒有printf,無法在控制台中打印調試信息。但是可以通過一些OpenGL提供的函數來獲取編譯和連接過程中的信息。在編譯階段使用glGetShaderiv獲取編譯情況。glGetShaderiv()函數原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glGetShaderiv (int shader, int pname, int[] params, int offset)   


參數含義: 

shader:一個shader的id; 
pname:使用GL_COMPILE_STATUS; 
params:返回值,如果一切正常返回GL_TRUE代,否則返回GL_FALSE。


(2) 創建一個Program對象
Program有點類似於一個程序的鏈接器。program對象提供了把需要做的事連接在一起的機制。在一個program中,shader對象可以連接在一起。
創建一個Program可以分成以下4步:

1)創建program:glCreateProgram()
2)綁定shader到program :glAttachShader()。
*每個program必須綁定一個Vertex Shader 和一個Fragment Shader。
3)鏈接program :glLinkProgram()。
4)使用porgram :glUseProgram()。

下面詳細分析這4步。
1) 創建program。
首先使用glCreateProgram ()創建一個容納程序(Program)的容器,我們稱之為程序容器。
函數的原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. int glCreateProgram ()  


如果函數調用成功將返回一個整形正整數作為該着色器程序的id。
2) 綁定shader到program。
使用glAttachShader()將shader容器添加到程序中。這時的shader容器不一定需要被編譯,他們甚至不需要包含任何的代碼。
函數的原型如下:  

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glAttachShader (int program, int shader)   


參數含義: 

program:着色器程序容器的id。
shader:要添加的頂點或者片元shader容器的id。 

Vertex Shader和Fragment Shader需要分別將他們各自的兩個shader容器添加的程序容器中。

3) 鏈接program。
使用glLinkProgram()鏈接程序對象。
函數的原型如下:  

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glLinkProgram (int program)   


program是着色器程序容器的id。
如果任何類型為GL_VERTEX_SHADER的shader對象連接到program,它將產生在“頂點着色器”(Vertex Shader)上可執行的程序;如果任何類型為GL_FRAGMENT_SHADER的shader對象連接到program,它將產生在“像素着色器”(Pixel Shader)上可執行的程序。
在鏈接階段使用glGetProgramiv()獲取編譯情況。glGetProgramiv ()函數原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glGetProgramiv (int program, int pname, int[] params, int offset)   


參數含義: 

program:一個着色器程序的id; 
pname:GL_LINK_STATUS; 
param:返回值,如果一切正常返回GL_TRUE代,否則返回GL_FALSE。


通過glBindAttribLocation()把“頂點屬性索引”綁定到“頂點屬性名”。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glBindAttribLocation(GLuint program,GLuint index,const GLchar* name);  


參數含義:

program:着色器程序容器的id。
index:頂點屬性索引。
name:頂點屬性名。


4) 使用porgram。
在鏈接了程序以后,我們可以使用glUseProgram()函數來加載並使用鏈接好的程序。glUseProgram函數原型如下: 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glUseProgram (int program)   


其中program是要使用的着色器程序的id。

(3) 初始化Texture

初始化Texture可以分為以下步驟。

1) 定義頂點數組

這一步需要初始化兩個數組,

2) 設置頂點數組


這一步通過glVertexAttribPointer()完成。glVertexAttribPointer()定義一個通用頂點屬性數組。當渲染時,它指定了通用頂點屬性數組從索引index處開始的位置和數據格式。
glVertexAttribPointer()原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glVertexAttribPointer(    
  2.     GLuint   index,   
  3.     GLint   size,   
  4.     GLenum   type,   
  5.     GLboolean   normalized,   
  6.     GLsizei   stride,   
  7.     const GLvoid *   pointer);   


每個參數的含義:

index:指示將被修改的通用頂點屬性的索引 
size:指點每個頂點元素個數(1~4)  
type:數組中每個元素的數據類型 
normalized:指示定點數據值是否被歸一化(歸一化<[-1,1]或[0,1]>:GL_TRUE,直接使用:GL_FALSE)  
stride:連續頂點屬性間的偏移量,如果為0,相鄰頂點屬性間緊緊相鄰 
pointer:頂點數組 


使用函數glEnableVertexAttribArray()啟用屬性數組。默認狀態下,所有客戶端的能力被Disabled,包括所有通用頂點屬性數組。如果被Enable,通用頂點屬性數組中的值將被訪問並被用於Rendering。函數的原型如下:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glEnableVertexAttribArray( GLuint   index);  

 

其中index用於指定通用頂點屬性的索引。

3) 初始化紋理

使用glGenTextures()初始化紋理,其原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. glGenTextures(GLsizei n, GLuint *textures)   


參數含義:

n:用來生成紋理的數量

textures:存儲紋理索引的數組

 

glGenTextures()就是用來產生你要操作的紋理對象的索引的,比如你告訴OpenGL,我需要5個紋理對象,它會從沒有用到的整數里返回5個給你。
產生紋理索引之后,需要使用glBindTexture()綁定紋理,才能對該紋理進行操作。glBindTexture()告訴OpenGL下面對紋理的任何操作都是針對它所綁定的紋理對象的,比如glBindTexture(GL_TEXTURE_2D,1)即告訴OpenGL下面代碼中對2D紋理的任何設置都是針對索引為1的紋理的。
glBindTexture()函數的聲明如下所示:

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glBindTexture(GLenum target, GLuint texture );  


函數參數的含義:

target:紋理被綁定的目標,它只能取值GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP。

texture:紋理的名稱,並且,該紋理的名稱在當前的應用中不能被再次使用。

 

綁定紋理之后,就可以設置該紋理的一些屬性了。
紋理過濾函數glTexParameteri()可以用來確定如何把圖像從紋理圖象空間映射到幀緩沖圖象空間。即把紋理像素映射成像素。glTexParameteri()的原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glTexParameteri(GLenum target,GLenum pname,GLint param);  


部分參數功能說明如下:

pname:參數。可以指定為GL_TEXTURE_MAG_FILTER(放大過濾),GL_TEXTURE_MIN_FILTER(縮小過濾)等。
param:參數的值。例如GL_LINEAR(線性插值。使用距離當前渲染像素中心最近的4個紋素加權平均值),GL_NEAREST(臨近像素插值。該方法質量較差)

 

6) 進入消息循環

glutMainLoop()將會進入GLUT事件處理循環。一旦被調用,這個程序將永遠不會返回。視頻播放的時候,調用該函數之后即開始播放視頻。

 

2. 循環顯示畫面

1) 設置紋理

使用glActiveTexture()選擇可以由紋理函數進行修改的當前紋理單位。后續的操作都是對選擇的紋理進行的。glActiveTexture()的原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glActiveTexture(GLenum texUnit);  


接着使用glBindTexture()告訴OpenGL下面對紋理的任何操作都是針對它所綁定的紋理對象的,這一點前文已經記錄,不再重復。
然后使用glTexImage2D()根據指定的參數,生成一個2D紋理(Texture)。相似的函數還有glTexImage1D、glTexImage3D。glTexImage2D()原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glTexImage2D(  GLenum target,  
  2.     GLint level,  
  3.     GLint internalformat,  
  4.     GLsizei width,  
  5.     GLsizei height,  
  6.     GLint border,  
  7.     GLenum format,  
  8.     GLenum type,  
  9.     const GLvoid * data);  


參數說明如下:

target:指定目標紋理,這個值必須是GL_TEXTURE_2D。
level:執行細節級別。0是最基本的圖像級別,n表示第N級貼圖細化級別。
internalformat:指定紋理中的顏色格式。可選的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等幾種。
width:紋理圖像的寬度。
height:紋理圖像的高度。
border:邊框的寬度。必須為0。
format:像素數據的顏色格式, 不需要和internalformatt取值必須相同。可選的值參考internalformat。
type:指定像素數據的數據類型。可以使用的值有GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_5_5_5_1等。
pixels:指定內存中指向圖像數據的指針


glUniform()為當前程序對象指定Uniform變量的值。(注意,由於OpenGL由C語言編寫,但是C語言不支持函數的重載,所以會有很多名字相同后綴不同的函數版本存在。其中函數名中包含數字(1、2、3、4)表示接受該數字個用於更改uniform變量的值,i表示32位整形,f表示32位浮點型,ub表示8位無符號byte,ui表示32位無符號整形,v表示接受相應的指針類型。 )

2) 繪制

使用glDrawArrays()進行繪制。glDrawArrays()原型如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void glDrawArrays (GLenum mode, GLint first, GLsizei count);  


參數說明:

mode:繪制方式,提供以下參數:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
first:從數組緩存中的哪一位開始繪制,一般為0。
count:數組中頂點的數量。

 

3) 顯示

如果使用“雙緩沖”方式的話,使用glutSwapBuffers()繪制。如果使用“單緩沖”方式的話,使用glFlush()繪制。glutSwapBuffers()的功能是交換兩個緩沖區指針,表現的形式即是把畫面呈現到屏幕上。

簡單解釋一下雙緩沖技術。當我們進行復雜的繪圖操作時,畫面便可能有明顯的閃爍。這是由於繪制的東西沒有同時出現在屏幕上而導致的。使用雙緩沖可以解決這個問題。所謂雙緩沖技術, 是指使用兩個緩沖區: 前台緩沖和后台緩沖。前台緩沖即我們看到的屏幕,后台緩沖則在內存當中,對我們來說是不可見的。每次的所有繪圖操作不是在屏幕上直接繪制,而是在后台緩沖中進行, 當繪制完成時,再把繪制的最終結果顯示到屏幕上。

glutSwapBuffers()函數執行之后,緩沖區指針交換,兩個緩沖的“角色”也發生了對調。原先的前台緩沖變成了后台緩沖,等待進行下一次繪制。而原先的后台緩沖變成了前台緩沖,展現出繪制的結果。

 

視頻顯示(使用Texture)流程總結

上文流程的函數流程可以用下圖表示。

 

 

代碼

源代碼如下所示。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. /** 
  2.  * 最簡單的OpenGL播放視頻的例子(OpenGL播放YUV)[Texture] 
  3.  * Simplest Video Play OpenGL (OpenGL play YUV) [Texture] 
  4.  * 
  5.  * 雷霄驊 Lei Xiaohua 
  6.  * leixiaohua1020@126.com 
  7.  * 中國傳媒大學/數字電視技術 
  8.  * Communication University of China / Digital TV Technology 
  9.  * http://blog.csdn.net/leixiaohua1020 
  10.  * 
  11.  * 本程序使用OpenGL播放YUV視頻像素數據。本程序支持YUV420P的 
  12.  * 像素數據作為輸入,經過轉換后輸出到屏幕上。其中用到了多種 
  13.  * 技術,例如Texture,Shader等,是一個相對比較復雜的例子。 
  14.  * 適合有一定OpenGL基礎的初學者學習。 
  15.  * 
  16.  * 函數調用步驟如下:  
  17.  * 
  18.  * [初始化] 
  19.  * glutInit(): 初始化glut庫。 
  20.  * glutInitDisplayMode(): 設置顯示模式。 
  21.  * glutCreateWindow(): 創建一個窗口。 
  22.  * glewInit(): 初始化glew庫。 
  23.  * glutDisplayFunc(): 設置繪圖函數(重繪的時候調用)。 
  24.  * glutTimerFunc(): 設置定時器。 
  25.  * InitShaders(): 設置Shader。包含了一系列函數,暫不列出。 
  26.  * glutMainLoop(): 進入消息循環。 
  27.  * 
  28.  * [循環渲染數據] 
  29.  * glActiveTexture(): 激活紋理單位。 
  30.  * glBindTexture(): 綁定紋理 
  31.  * glTexImage2D(): 根據像素數據,生成一個2D紋理。 
  32.  * glUniform1i():  
  33.  * glDrawArrays(): 繪制。 
  34.  * glutSwapBuffers(): 顯示。 
  35.  * 
  36.  * This software plays YUV raw video data using OpenGL. 
  37.  * It support read YUV420P raw file and show it on the screen. 
  38.  * It's use a slightly more complex technologies such as Texture, 
  39.  * Shaders etc. Suitable for beginner who already has some  
  40.  * knowledge about OpenGL. 
  41.  * 
  42.  * The process is shown as follows: 
  43.  * 
  44.  * [Init] 
  45.  * glutInit(): Init glut library. 
  46.  * glutInitDisplayMode(): Set display mode. 
  47.  * glutCreateWindow(): Create a window. 
  48.  * glewInit(): Init glew library. 
  49.  * glutDisplayFunc(): Set the display callback. 
  50.  * glutTimerFunc(): Set timer. 
  51.  * InitShaders(): Set Shader, Init Texture. It contains some functions about Shader. 
  52.  * glutMainLoop(): Start message loop. 
  53.  * 
  54.  * [Loop to Render data] 
  55.  * glActiveTexture(): Active a Texture unit  
  56.  * glBindTexture(): Bind Texture 
  57.  * glTexImage2D(): Specify pixel data to generate 2D Texture 
  58.  * glUniform1i():  
  59.  * glDrawArrays(): draw. 
  60.  * glutSwapBuffers(): show. 
  61.  */  
  62.   
  63. #include <stdio.h>  
  64.   
  65. #include "glew.h"  
  66. #include "glut.h"  
  67.   
  68. #include <stdio.h>  
  69. #include <stdlib.h>  
  70. #include <malloc.h>  
  71. #include <string.h>  
  72.   
  73. //Select one of the Texture mode (Set '1'):  
  74. #define TEXTURE_DEFAULT   0  
  75. //Rotate the texture  
  76. #define TEXTURE_ROTATE    0  
  77. //Show half of the Texture  
  78. #define TEXTURE_HALF      1  
  79.   
  80. const int screen_w=500,screen_h=500;  
  81. const int pixel_w = 320, pixel_h = 180;  
  82. //YUV file  
  83. FILE *infile = NULL;  
  84. unsigned char buf[pixel_w*pixel_h*3/2];  
  85. unsigned char *plane[3];  
  86.   
  87.   
  88. GLuint p;                  
  89. GLuint id_y, id_u, id_v; // Texture id  
  90. GLuint textureUniformY, textureUniformU,textureUniformV;  
  91.   
  92.   
  93. #define ATTRIB_VERTEX 3  
  94. #define ATTRIB_TEXTURE 4  
  95.   
  96. void display(void){  
  97.     if (fread(buf, 1, pixel_w*pixel_h*3/2, infile) != pixel_w*pixel_h*3/2){  
  98.         // Loop  
  99.         fseek(infile, 0, SEEK_SET);  
  100.         fread(buf, 1, pixel_w*pixel_h*3/2, infile);  
  101.     }  
  102.     //Clear  
  103.     glClearColor(0.0,255,0.0,0.0);  
  104.     glClear(GL_COLOR_BUFFER_BIT);  
  105.     //Y  
  106.     //  
  107.     glActiveTexture(GL_TEXTURE0);  
  108.       
  109.     glBindTexture(GL_TEXTURE_2D, id_y);  
  110.       
  111.     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w, pixel_h, 0, GL_RED, GL_UNSIGNED_BYTE, plane[0]);   
  112.       
  113.     glUniform1i(textureUniformY, 0);      
  114.     //U  
  115.     glActiveTexture(GL_TEXTURE1);  
  116.     glBindTexture(GL_TEXTURE_2D, id_u);  
  117.     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[1]);         
  118.     glUniform1i(textureUniformU, 1);  
  119.     //V  
  120.     glActiveTexture(GL_TEXTURE2);  
  121.     glBindTexture(GL_TEXTURE_2D, id_v);  
  122.     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[2]);      
  123.     glUniform1i(textureUniformV, 2);     
  124.   
  125.     // Draw  
  126.     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);  
  127.     // Show  
  128.     //Double  
  129.     glutSwapBuffers();  
  130.     //Single  
  131.     //glFlush();  
  132. }  
  133.   
  134. void timeFunc(int value){  
  135.     display();  
  136.     // Timer: 40ms  
  137.     glutTimerFunc(40, timeFunc, 0);  
  138. }  
  139.   
  140. char *textFileRead(char * filename)  
  141. {  
  142.     char *s = (char *)malloc(8000);  
  143.     memset(s, 0, 8000);  
  144.     FILE *infile = fopen(filename, "rb");  
  145.     int len = fread(s, 1, 8000, infile);  
  146.     fclose(infile);  
  147.     s[len] = 0;  
  148.     return s;  
  149. }  
  150.   
  151. //Init Shader  
  152. void InitShaders()  
  153. {  
  154.     GLint vertCompiled, fragCompiled, linked;  
  155.       
  156.     GLint v, f;  
  157.     const char *vs,*fs;  
  158.     //Shader: step1  
  159.     v = glCreateShader(GL_VERTEX_SHADER);  
  160.     f = glCreateShader(GL_FRAGMENT_SHADER);  
  161.     //Get source code  
  162.     vs = textFileRead("Shader.vsh");  
  163.     fs = textFileRead("Shader.fsh");  
  164.     //Shader: step2  
  165.     glShaderSource(v, 1, &vs,NULL);  
  166.     glShaderSource(f, 1, &fs,NULL);  
  167.     //Shader: step3  
  168.     glCompileShader(v);  
  169.     //Debug  
  170.     glGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled);  
  171.     glCompileShader(f);  
  172.     glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);  
  173.   
  174.     //Program: Step1  
  175.     p = glCreateProgram();   
  176.     //Program: Step2  
  177.     glAttachShader(p,v);  
  178.     glAttachShader(p,f);   
  179.   
  180.     glBindAttribLocation(p, ATTRIB_VERTEX, "vertexIn");  
  181.     glBindAttribLocation(p, ATTRIB_TEXTURE, "textureIn");  
  182.     //Program: Step3  
  183.     glLinkProgram(p);  
  184.     //Debug  
  185.     glGetProgramiv(p, GL_LINK_STATUS, &linked);    
  186.     //Program: Step4  
  187.     glUseProgram(p);  
  188.   
  189.   
  190.     //Get Uniform Variables Location  
  191.     textureUniformY = glGetUniformLocation(p, "tex_y");  
  192.     textureUniformU = glGetUniformLocation(p, "tex_u");  
  193.     textureUniformV = glGetUniformLocation(p, "tex_v");   
  194.   
  195. #if TEXTURE_ROTATE  
  196.     static const GLfloat vertexVertices[] = {  
  197.         -1.0f, -0.5f,  
  198.          0.5f, -1.0f,  
  199.         -0.5f,  1.0f,  
  200.          1.0f,  0.5f,  
  201.     };      
  202. #else  
  203.     static const GLfloat vertexVertices[] = {  
  204.         -1.0f, -1.0f,  
  205.         1.0f, -1.0f,  
  206.         -1.0f,  1.0f,  
  207.         1.0f,  1.0f,  
  208.     };      
  209. #endif  
  210.   
  211. #if TEXTURE_HALF  
  212.     static const GLfloat textureVertices[] = {  
  213.         0.0f,  1.0f,  
  214.         0.5f,  1.0f,  
  215.         0.0f,  0.0f,  
  216.         0.5f,  0.0f,  
  217.     };   
  218. #else  
  219.     static const GLfloat textureVertices[] = {  
  220.         0.0f,  1.0f,  
  221.         1.0f,  1.0f,  
  222.         0.0f,  0.0f,  
  223.         1.0f,  0.0f,  
  224.     };   
  225. #endif  
  226.     //Set Arrays  
  227.     glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);  
  228.     //Enable it  
  229.     glEnableVertexAttribArray(ATTRIB_VERTEX);      
  230.     glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);  
  231.     glEnableVertexAttribArray(ATTRIB_TEXTURE);  
  232.   
  233.   
  234.     //Init Texture  
  235.     glGenTextures(1, &id_y);   
  236.     glBindTexture(GL_TEXTURE_2D, id_y);      
  237.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  
  238.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  
  239.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
  240.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
  241.       
  242.     glGenTextures(1, &id_u);  
  243.     glBindTexture(GL_TEXTURE_2D, id_u);     
  244.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  
  245.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  
  246.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
  247.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
  248.       
  249.     glGenTextures(1, &id_v);   
  250.     glBindTexture(GL_TEXTURE_2D, id_v);      
  251.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  
  252.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  
  253.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
  254.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
  255.   
  256. }  
  257.   
  258.   
  259.   
  260. int main(int argc, char* argv[])  
  261. {  
  262.     //Open YUV420P file  
  263.     if((infile=fopen("../test_yuv420p_320x180.yuv", "rb"))==NULL){  
  264.         printf("cannot open this file\n");  
  265.         return -1;  
  266.     }  
  267.   
  268.     //YUV Data  
  269.     plane[0] = buf;  
  270.     plane[1] = plane[0] + pixel_w*pixel_h;  
  271.     plane[2] = plane[1] + pixel_w*pixel_h/4;  
  272.   
  273.     //Init GLUT  
  274.     glutInit(&argc, argv);    
  275.     //GLUT_DOUBLE  
  276.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA /*| GLUT_STENCIL | GLUT_DEPTH*/);  
  277.     glutInitWindowPosition(100, 100);  
  278.     glutInitWindowSize(screen_w, screen_h);  
  279.     glutCreateWindow("Simplest Video Play OpenGL (Texture)");  
  280.     printf("Lei Xiaohua\n");  
  281.     printf("http://blog.csdn.net/leixiaohua1020\n");  
  282.     printf("Version: %s\n", glGetString(GL_VERSION));  
  283.     GLenum l = glewInit();  
  284.   
  285.     glutDisplayFunc(&display);  
  286.     glutTimerFunc(40, timeFunc, 0);   
  287.   
  288.     InitShaders();  
  289.   
  290.     // Begin!  
  291.     glutMainLoop();  
  292.   
  293.     return 0;  
  294. }  

 

 

Shader.vsh

[plain]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. attribute vec4 vertexIn;   
  2. attribute vec2 textureIn;  
  3. varying vec2 textureOut;  
  4. void main(void)  
  5. {  
  6.     gl_Position = vertexIn;   
  7.     textureOut = textureIn;  
  8. }  


Shader.fsh

[plain]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. varying vec2 textureOut;  
  2. uniform sampler2D tex_y;  
  3. uniform sampler2D tex_u;  
  4. uniform sampler2D tex_v;  
  5. void main(void)  
  6. {  
  7.     vec3 yuv;  
  8.     vec3 rgb;      
  9.     yuv.x = texture2D(tex_y, textureOut).r;  
  10.     yuv.y = texture2D(tex_u, textureOut).r - 0.5;  
  11.     yuv.z = texture2D(tex_v, textureOut).r - 0.5;  
  12.     rgb = mat3( 1,       1,         1,  
  13.                 0,       -0.39465,  2.03211,  
  14.                 1.13983, -0.58060,  0) * yuv;      
  15.     gl_FragColor = vec4(rgb, 1);  
  16. }  
 

代碼注意事項

1. 目前支持讀取YUV420P格式的像素數據。


2. 窗口的寬高為screen_w,screen_h。像素數據的寬高為pixel_w,pixel_h。它們的定義如下。

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. //Width, Height      
  2. const int screen_w=500,screen_h=500;      
  3. const int pixel_w=320,pixel_h=180;      


3. 通過代碼前面的宏,可以選擇幾種不同的紋理映射方式

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. //Select one of the Texture mode (Set '1'):    
  2. #define TEXTURE_DEFAULT 1    
  3. //Rotate the texture    
  4. #define TEXTURE_ROTATE  0    
  5. //Show half of the Texture    
  6. #define TEXTURE_HALF    0    


第一種是正常的映射方式,第二種是“旋轉”的方式,第三種是只映射一半的方式。

結果

程序運行結果如下。默認的紋理映射:

“旋轉”:

一半紋理:

 

下載

代碼位於“Simplest Media Play”中



SourceForge項目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8054395


上述工程包含了使用各種API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒體例子。其中音頻輸入為PCM采樣數據。輸出至系統的聲卡播放出來。視頻輸入為YUV/RGB像素數據。輸出至顯示器上的一個窗口播放出來。
通過本工程的代碼初學者可以快速學習使用這幾個API播放視頻和音頻的技術。
一共包括了如下幾個子工程:
simplest_audio_play_directsound:  使用DirectSound播放PCM音頻采樣數據。
simplest_audio_play_sdl2:  使用SDL2播放PCM音頻采樣數據。
simplest_video_play_direct3d:  使用Direct3D的Surface播放RGB/YUV視頻像素數據。
simplest_video_play_direct3d_texture: 使用Direct3D的Texture播放RGB視頻像素數據。
simplest_video_play_gdi:  使用GDI播放RGB/YUV視頻像素數據。
simplest_video_play_opengl:  使用OpenGL播放RGB/YUV視頻像素數據。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV視頻像素數據。
simplest_video_play_sdl2:  使用SDL2播放RGB/YUV視頻像素數據。

 

from:http://blog.csdn.net/leixiaohua1020/article/details/40379845


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM