本文記錄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
- * leixiaohua1020@126.com
- * 中國傳媒大學/數字電視技術
- * 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視頻像素數據。
from:http://blog.csdn.net/leixiaohua1020/article/details/40379845