1. 程式人生 > >最簡單的視音訊播放示例6:OpenGL播放YUV420P(通過Texture,使用Shader)

最簡單的視音訊播放示例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庫。它原型如下:
void glutInit(int *argcp, char **argv);

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

需要注意的是,如果使用雙緩衝(GLUT_DOUBLE),則需要用glutSwapBuffers ()繪圖。如果使用單緩衝(GLUT_SINGLE),則需要用glFlush()繪圖。
在使用OpenGL播放視訊的時候,我們可以使用下述程式碼:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );


2)建立視窗
glutInitWindowPosition()用於設定視窗的位置。可以指定x,y座標。
glutInitWindowSize()用於設定視窗的大小。可以設定視窗的寬,高。
glutCreateWindow()建立一個視窗。可以指定視窗的標題。
上述幾個函式十分基礎,不再詳細敘述。直接貼出一段示例程式碼:
glutInitWindowPosition(100, 100);
glutInitWindowSize(500, 500);
glutCreateWindow("Simplest Video Play OpenGL");	


3)設定繪圖函式
glutDisplayFunc()用於設定繪圖函式。作業系統在必要時刻就會呼叫該函式對窗體進行重新繪製操作。類似於windows程式設計中處理WM_PAINT訊息。例如,當把視窗移動到螢幕邊上,然後又移動回來的時候,就會呼叫該函式對視窗進行重繪。它的原型如下。
void glutDisplayFunc(void (*func)(void));

其中(*func)用於指定重繪函式。
例如在視訊播放的時候,指定display()函式用於重繪:
glutDisplayFunc(&display);

4)設定定時器
播放視訊的時候,每秒需要播放一定的畫面(一般是25幀),因此使用定時器每間隔一段時間呼叫一下繪圖函式繪製圖形。定時器函式glutTimerFunc()的原型如下。
void glutTimerFunc(unsigned int millis, void (*func)(int value), int value);

它的引數含義如下:
millis:定時的時間,單位是毫秒。1秒=1000毫秒。

(*func)(int value):用於指定定時器呼叫的函式。

value:給回撥函式傳參。比較高階,沒有接觸過。

如果只在主函式中寫一個glutTimerFunc()函式的話,會發現只會呼叫該函式一次。因此需要在回撥函式中再寫一個glutTimerFunc()函式,並呼叫回撥函式自己。只有這樣才能實現反反覆覆迴圈呼叫回撥函式。
例如在視訊播放的時候,指定每40毫秒呼叫一次timeFunc ()函式:
主函式中:
glutTimerFunc(40, timeFunc, 0);

而後在timeFunc()函式中如下設定。
void timeFunc(int value){
    display();
    // Present frame every 40 ms
    glutTimerFunc(40, timeFunc, 0);
}

這樣就實現了每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

attribute vec4 vertexIn; 
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
    gl_Position = vertexIn; 
    textureOut = textureIn;
}

Shader.fsh
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);
}

從上述程式碼中可以看出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的容器,它的原型如下:
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函式的原型如下: 
void glShaderSource (int shader, String string) 

引數含義如下: 
shader:是代表shader容器的id(由glCreateShader()返回的整形數)。

strings:是包含源程式的字串陣列。

如果感覺通過“字串陣列”的方式寫原始碼不太習慣的話,可以把原始碼寫到單獨的一個文字檔案裡。然後在需要原始碼的時候,讀取該文字檔案中的所有內容。
4)線上編譯Shader原始碼。
使用glCompileShader()對shader容器中的原始碼進行編譯。函式的原型如下:  
void glCompileShader (int shader)

其中shader是代表Shader容器的id。
在編譯完成後,可能需要除錯。除錯一個Shader是非常困難的。Shader的世界裡沒有printf,無法在控制檯中列印除錯資訊。但是可以通過一些OpenGL提供的函式來獲取編譯和連線過程中的資訊。在編譯階段使用glGetShaderiv獲取編譯情況。glGetShaderiv()函式原型如下:
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)的容器,我們稱之為程式容器。
函式的原型如下:
int glCreateProgram ()

如果函式呼叫成功將返回一個整形正整數作為該著色器程式的id。
2)繫結shader到program。
使用glAttachShader()將shader容器新增到程式中。這時的shader容器不一定需要被編譯,他們甚至不需要包含任何的程式碼。
函式的原型如下:  
void glAttachShader (int program, int shader) 

引數含義: 
program:著色器程式容器的id。
shader:要新增的頂點或者片元shader容器的id。 
Vertex Shader和Fragment Shader需要分別將他們各自的兩個shader容器新增的程式容器中。

3)連結program。
使用glLinkProgram()連結程式物件。
函式的原型如下:  
void glLinkProgram (int program) 

program是著色器程式容器的id。
如果任何型別為GL_VERTEX_SHADER的shader物件連線到program,它將產生在“頂點著色器”(Vertex Shader)上可執行的程式;如果任何型別為GL_FRAGMENT_SHADER的shader物件連線到program,它將產生在“畫素著色器”(Pixel Shader)上可執行的程式。
在連結階段使用glGetProgramiv()獲取編譯情況。glGetProgramiv ()函式原型如下:
void glGetProgramiv (int program, int pname, int[] params, int offset) 

引數含義: 
program:一個著色器程式的id; 
pname:GL_LINK_STATUS; 
param:返回值,如果一切正常返回GL_TRUE代,否則返回GL_FALSE。

通過glBindAttribLocation()把“頂點屬性索引”繫結到“頂點屬性名”。
void glBindAttribLocation(GLuint program,GLuint index,const GLchar* name);

引數含義:
program:著色器程式容器的id。
index:頂點屬性索引。
name:頂點屬性名。

4)使用porgram。
在連結了程式以後,我們可以使用glUseProgram()函式來載入並使用連結好的程式。glUseProgram函式原型如下: 
void glUseProgram (int program) 

其中program是要使用的著色器程式的id。

(3)初始化Texture

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

1)定義頂點陣列

這一步需要初始化兩個陣列,

2)設定頂點陣列


這一步通過glVertexAttribPointer()完成。glVertexAttribPointer()定義一個通用頂點屬性陣列。當渲染時,它指定了通用頂點屬性陣列從索引index處開始的位置和資料格式。
glVertexAttribPointer()原型如下。
void glVertexAttribPointer(  
	GLuint   index, 
	GLint   size, 
	GLenum   type, 
	GLboolean   normalized, 
	GLsizei   stride, 
	const GLvoid *   pointer); 

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

使用函式glEnableVertexAttribArray()啟用屬性陣列。預設狀態下,所有客戶端的能力被Disabled,包括所有通用頂點屬性陣列。如果被Enable,通用頂點屬性陣列中的值將被訪問並被用於Rendering。函式的原型如下:
void glEnableVertexAttribArray( GLuint   index);

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

3)初始化紋理

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

glGenTextures(GLsizei n, GLuint *textures) 

引數含義:
n:用來生成紋理的數量

textures:儲存紋理索引的陣列

glGenTextures()就是用來產生你要操作的紋理物件的索引的,比如你告訴OpenGL,我需要5個紋理物件,它會從沒有用到的整數裡返回5個給你。
產生紋理索引之後,需要使用glBindTexture()繫結紋理,才能對該紋理進行操作。glBindTexture()告訴OpenGL下面對紋理的任何操作都是針對它所繫結的紋理物件的,比如glBindTexture(GL_TEXTURE_2D,1)即告訴OpenGL下面程式碼中對2D紋理的任何設定都是針對索引為1的紋理的。
glBindTexture()函式的宣告如下所示:
void glBindTexture(GLenum target, GLuint texture );

函式引數的含義:
target:紋理被繫結的目標,它只能取值GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP。

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

繫結紋理之後,就可以設定該紋理的一些屬性了。
紋理過濾函式glTexParameteri()可以用來確定如何把影象從紋理圖象空間對映到幀緩衝圖象空間。即把紋理畫素對映成畫素。glTexParameteri()的原型如下。
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()的原型如下。
void glActiveTexture(GLenum texUnit);

接著使用glBindTexture()告訴OpenGL下面對紋理的任何操作都是針對它所繫結的紋理物件的,這一點前文已經記錄,不再重複。
然後使用glTexImage2D()根據指定的引數,生成一個2D紋理(Texture)。相似的函式還有glTexImage1D、glTexImage3D。glTexImage2D()原型如下。
void glTexImage2D(	GLenum target,
 	GLint level,
 	GLint internalformat,
 	GLsizei width,
 	GLsizei height,
 	GLint border,
 	GLenum format,
 	GLenum type,
 	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()原型如下。

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)流程總結

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

 

程式碼

原始碼如下所示。

/**
 * 最簡單的OpenGL播放視訊的例子(OpenGL播放YUV)[Texture]
 * Simplest Video Play OpenGL (OpenGL play YUV) [Texture]
 *
 * 雷霄驊 Lei Xiaohua
 * [email protected]
 * 中國傳媒大學/數字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 * 本程式使用OpenGL播放YUV視訊畫素資料。本程式支援YUV420P的
 * 畫素資料作為輸入,經過轉換後輸出到螢幕上。其中用到了多種
 * 技術,例如Texture,Shader等,是一個相對比較複雜的例子。
 * 適合有一定OpenGL基礎的初學者學習。
 *
 * 函式呼叫步驟如下: 
 *
 * [初始化]
 * glutInit(): 初始化glut庫。
 * glutInitDisplayMode(): 設定顯示模式。
 * glutCreateWindow(): 建立一個視窗。
 * glewInit(): 初始化glew庫。
 * glutDisplayFunc(): 設定繪圖函式(重繪的時候呼叫)。
 * glutTimerFunc(): 設定定時器。
 * InitShaders(): 設定Shader。包含了一系列函式,暫不列出。
 * glutMainLoop(): 進入訊息迴圈。
 *
 * [迴圈渲染資料]
 * glActiveTexture(): 啟用紋理單位。
 * glBindTexture(): 繫結紋理
 * glTexImage2D(): 根據畫素資料,生成一個2D紋理。
 * glUniform1i(): 
 * glDrawArrays(): 繪製。
 * glutSwapBuffers(): 顯示。
 *
 * This software plays YUV raw video data using OpenGL.
 * It support read YUV420P raw file and show it on the screen.
 * It's use a slightly more complex technologies such as Texture,
 * Shaders etc. Suitable for beginner who already has some 
 * knowledge about OpenGL.
 *
 * The process is shown as follows:
 *
 * [Init]
 * glutInit(): Init glut library.
 * glutInitDisplayMode(): Set display mode.
 * glutCreateWindow(): Create a window.
 * glewInit(): Init glew library.
 * glutDisplayFunc(): Set the display callback.
 * glutTimerFunc(): Set timer.
 * InitShaders(): Set Shader, Init Texture. It contains some functions about Shader.
 * glutMainLoop(): Start message loop.
 *
 * [Loop to Render data]
 * glActiveTexture(): Active a Texture unit 
 * glBindTexture(): Bind Texture
 * glTexImage2D(): Specify pixel data to generate 2D Texture
 * glUniform1i(): 
 * glDrawArrays(): draw.
 * glutSwapBuffers(): show.
 */

#include <stdio.h>

#include "glew.h"
#include "glut.h"

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

//Select one of the Texture mode (Set '1'):
#define TEXTURE_DEFAULT   0
//Rotate the texture
#define TEXTURE_ROTATE    0
//Show half of the Texture
#define TEXTURE_HALF      1

const int screen_w=500,screen_h=500;
const int pixel_w = 320, pixel_h = 180;
//YUV file
FILE *infile = NULL;
unsigned char buf[pixel_w*pixel_h*3/2];
unsigned char *plane[3];


GLuint p;                
GLuint id_y, id_u, id_v; // Texture id
GLuint textureUniformY, textureUniformU,textureUniformV;


#define ATTRIB_VERTEX 3
#define ATTRIB_TEXTURE 4

void display(void){
    if (fread(buf, 1, pixel_w*pixel_h*3/2, infile) != pixel_w*pixel_h*3/2){
        // Loop
        fseek(infile, 0, SEEK_SET);
        fread(buf, 1, pixel_w*pixel_h*3/2, infile);
    }
	//Clear
	glClearColor(0.0,255,0.0,0.0);
	glClear(GL_COLOR_BUFFER_BIT);
	//Y
	//
    glActiveTexture(GL_TEXTURE0);
	
    glBindTexture(GL_TEXTURE_2D, id_y);
	
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w, pixel_h, 0, GL_RED, GL_UNSIGNED_BYTE, plane[0]); 
	
	glUniform1i(textureUniformY, 0);    
	//U
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, id_u);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[1]);       
    glUniform1i(textureUniformU, 1);
	//V
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, id_v);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[2]);    
    glUniform1i(textureUniformV, 2);   

    // Draw
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	// Show
	//Double
    glutSwapBuffers();
	//Single
	//glFlush();
}

void timeFunc(int value){
    display();
    // Timer: 40ms
    glutTimerFunc(40, timeFunc, 0);
}

char *textFileRead(char * filename)
{
    char *s = (char *)malloc(8000);
    memset(s, 0, 8000);
    FILE *infile = fopen(filename, "rb");
    int len = fread(s, 1, 8000, infile);
    fclose(infile);
    s[len] = 0;
    return s;
}

//Init Shader
void InitShaders()
{
    GLint vertCompiled, fragCompiled, linked;
    
    GLint v, f;
    const char *vs,*fs;
	//Shader: step1
    v = glCreateShader(GL_VERTEX_SHADER);
    f = glCreateShader(GL_FRAGMENT_SHADER);
	//Get source code
    vs = textFileRead("Shader.vsh");
    fs = textFileRead("Shader.fsh");
	//Shader: step2
    glShaderSource(v, 1, &vs,NULL);
    glShaderSource(f, 1, &fs,NULL);
	//Shader: step3
    glCompileShader(v);
	//Debug
    glGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled);
    glCompileShader(f);
    glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);

	//Program: Step1
    p = glCreateProgram(); 
	//Program: Step2
    glAttachShader(p,v);
    glAttachShader(p,f); 

    glBindAttribLocation(p, ATTRIB_VERTEX, "vertexIn");
    glBindAttribLocation(p, ATTRIB_TEXTURE, "textureIn");
	//Program: Step3
    glLinkProgram(p);
	//Debug
    glGetProgramiv(p, GL_LINK_STATUS, &linked);  
	//Program: Step4
    glUseProgram(p);


	//Get Uniform Variables Location
	textureUniformY = glGetUniformLocation(p, "tex_y");
	textureUniformU = glGetUniformLocation(p, "tex_u");
	textureUniformV = glGetUniformLocation(p, "tex_v"); 

#if TEXTURE_ROTATE
    static const GLfloat vertexVertices[] = {
        -1.0f, -0.5f,
         0.5f, -1.0f,
        -0.5f,  1.0f,
         1.0f,  0.5f,
    };    
#else
	static const GLfloat vertexVertices[] = {
		-1.0f, -1.0f,
		1.0f, -1.0f,
		-1.0f,  1.0f,
		1.0f,  1.0f,
	};    
#endif

#if TEXTURE_HALF
	static const GLfloat textureVertices[] = {
		0.0f,  1.0f,
		0.5f,  1.0f,
		0.0f,  0.0f,
		0.5f,  0.0f,
	}; 
#else
	static const GLfloat textureVertices[] = {
		0.0f,  1.0f,
		1.0f,  1.0f,
		0.0f,  0.0f,
		1.0f,  0.0f,
	}; 
#endif
	//Set Arrays
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
	//Enable it
    glEnableVertexAttribArray(ATTRIB_VERTEX);    
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);


	//Init Texture
    glGenTextures(1, &id_y); 
    glBindTexture(GL_TEXTURE_2D, id_y);    
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glGenTextures(1, &id_u);
    glBindTexture(GL_TEXTURE_2D, id_u);   
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glGenTextures(1, &id_v); 
    glBindTexture(GL_TEXTURE_2D, id_v);    
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

}



int main(int argc, char* argv[])
{
	//Open YUV420P file
	if((infile=fopen("../test_yuv420p_320x180.yuv", "rb"))==NULL){
		printf("cannot open this file\n");
		return -1;
	}

	//YUV Data
    plane[0] = buf;
    plane[1] = plane[0] + pixel_w*pixel_h;
    plane[2] = plane[1] + pixel_w*pixel_h/4;

    //Init GLUT
    glutInit(&argc, argv);  
	//GLUT_DOUBLE
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA /*| GLUT_STENCIL | GLUT_DEPTH*/);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(screen_w, screen_h);
    glutCreateWindow("Simplest Video Play OpenGL (Texture)");
	printf("Lei Xiaohua\n");
	printf("http://blog.csdn.net/leixiaohua1020\n");
    printf("Version: %s\n", glGetString(GL_VERSION));
    GLenum l = glewInit();

    glutDisplayFunc(&display);
    glutTimerFunc(40, timeFunc, 0); 

    InitShaders();

    // Begin!
    glutMainLoop();

    return 0;
}

Shader.vsh

attribute vec4 vertexIn; 
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
    gl_Position = vertexIn; 
    textureOut = textureIn;
}

Shader.fsh
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);
}

程式碼注意事項

1.目前支援讀取YUV420P格式的畫素資料。


2.視窗的寬高為screen_w,screen_h。畫素資料的寬高為pixel_w,pixel_h。它們的定義如下。
//Width, Height    
const int screen_w=500,screen_h=500;    
const int pixel_w=320,pixel_h=180;    

3.通過程式碼前面的巨集,可以選擇幾種不同的紋理對映方式
//Select one of the Texture mode (Set '1'):  
#define TEXTURE_DEFAULT 1  
//Rotate the texture  
#define TEXTURE_ROTATE  0  
//Show half of the Texture  
#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視訊畫素資料。

相關推薦

簡單音訊播放示例6OpenGL播放YUV420P通過Texture使用Shader

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄OpenGL播放視訊

簡單音訊播放示例5OpenGL播放RGB/YUV

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄OpenGL播放視訊

簡單音訊播放示例9SDL2播放PCM

最簡單的視音訊播放示例系列文章列表: ===================================================== 本文記錄SDL播放音訊的技術。在這裡使用的版本是SDL2。實際上SDL本身並不提供視音訊播放的功能,它只

簡單音訊播放示例7SDL2播放RGB/YUV

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄SDL播放視訊的技術

javaMybatis框架1基本配置log4j

屬性 apache led sta sql inpu ack ima 文件中 1.mybatis01:   db.properties: driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3

簡單的基於libVLC的例子簡單的基於libVLC的視訊播放圖形介面版

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄使用libVLC

HTML5用audio標籤做一個簡單音訊播放

在做系統的時候,要求做一個音訊播放器,就在網上查找了一些資料,發現這樣的資料還是很千篇一律的,EasyUI框架並沒有給我們一個音訊播放器的功能,在bootstrap上有,但是也是結合html5來寫的,因此,我們在這裡就用純的html5血一個音訊播放器,如何播放本地的音訊。

Android開發6Service的使用簡單音樂播放器的實現

閱讀目錄前言基礎知識實驗內容實驗過程注意事項實驗截圖原始碼下載注回到頂部前言   啦啦啦~各位好久不見啦~博主最近比較忙,而且最近一次實驗也是剛剛結束~   好了不廢話了,直接進入我們這次的內容~   在這篇博文裡我們將學習Service(服務)的相關知識,學會使用 Service 進行後臺工作, 學會使用

簡單的基於libVLC的例子簡單的基於libVLC的視訊播放

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄使用libVLC

手把手教你ExtJS從入門到放棄——篇9(示例6windowGroup物件操作window組)

 windowGroup物件 操作window組 重點分析:該例項主要目的針對於特殊需求進行具體的實現,利用windowGroup去操作多個窗體同步執行某些任務,這有點類似於javascript裡的組合模式,原理就是上級負責執行一個動作但並不真正去執行,而是分別傳遞給所有的下級元件

MongoDB簡單的入門教程之四使用Spring Boot操作MongoDB

Spring Boot 是一個輕量級框架,可以完成基於 Spring 的應用程式的大部分配置工作。Spring Boot的目的是提供一組工具,以便快速構建容易配置的Spring應用程式,省去大量傳統Spring專案的繁瑣配置。 MongoDB是一個基於分散式檔

音訊資料處理入門PCM音訊取樣資料處理

                =====================================================視音訊資料處理入門系列文章:=====================================================上一篇文章記錄了RGB/YUV視訊畫素

史上簡單的 SpringCloud 教程 | 第一篇 服務的註冊與發現Eureka

一、spring cloud簡介 spring cloud 為開發人員提供了快速構建分散式系統的一些工具,包括配置管理、服務發現、斷路器、路由、微代理

史上簡單的 SpringCloud 教程 | 第一篇 服務的註冊與發現Eureka(Finchley版本)

一、spring cloud簡介 鑑於《史上最簡單的Spring Cloud教程》很受讀者歡迎,再次我特意升級了一下版本,目前支援的版本為Spring Boot版本2.0.3.RELEASE,Spring Cloud版本為Finchley.RELEASE。

簡單的spring入門示例

應群裡一位朋友的要求,寫一個最簡單的spring示例,使用spring的MVC,並應用了spring的依賴注入 ,實現簡單應用,索性放在這裡供還沒入門的spring愛好者參考,初步感受一下spring應用(spring高手就不必看了,這裡並沒有涉及高階特性,比如與ORM框架的

簡單的基於libVLC的例子簡單的基於libVLC的推流器

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄基於libVLC

shareding-jdbc實現讀寫分離簡單的容易理解示例

資料庫建立create database demo_ds_master; //建立主庫create database demo_ds_slave_0; //建立從庫1create database

windows下簡單音訊採集示例

最近需要在window下進行音訊採集,網上找了很久都沒找到win7下如何採集pcm資料的完整示例,經過一翻折騰後寫了一個很簡單的demo程式以供同行進行參考,如有不正確的地方請指正 本例是採用audio core進行音訊採集 程式碼塊 #include "

簡單的基於FFMPEG+SDL的音視訊播放

一、概述         在《最簡單的基於FFMPEG+SDL的音訊播放器》記錄一中,我們實現了音訊的播放。更早前,我們在《最簡單的基於FFMPEG+SDL的視訊播放器》記錄一和二中,實現了視訊的播放。在實現視訊播放的時候,我們設定了一個延遲40ms,否則視訊就會以解碼的速

音訊資料處理入門UDP-RTP協議解析

=====================================================視音訊資料處理入門系列文章:=====================================================本文介紹網路協議資料的處理程式。網路