视频和音频播放的演示最简单的例子6:OpenGL广播YUV420P(T经exture,采用Shader)
=====================================================
最简单的视频和音频播放的演示样品系列列表:
最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
最简单的视音频播放演示样例6:OpenGL播放YUV420P(通过Texture,使用Shader)
最简单的视音频播放演示样例8:DirectSound播放PCM
=====================================================
本文记录OpenGL播放视频的技术。上一篇文章中,介绍了一种简单的使用OpenGL显示视频的方式。可是那还不是OpenGL显示视频技术的精髓。
和Direct3D一样。OpenGL更好的显示视频的方式也是通过纹理(Texture)。
本文介绍OpenGL通过纹理的方式显示视频的技术。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpeGlhb2h1YTEwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
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()启用属性数组。
默认状态下。全部client的能力被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)流程总结
上文流程的函数流程能够用下图表示。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpeGlhb2h1YTEwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
代码
源代码例如以下所看到的。
/**
* 最简单的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
第一种是正常的映射方式,另外一种是“旋转”的方式,第三种是仅仅映射一半的方式。
结果
程序运行结果例如以下。默认的纹理映射:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpeGlhb2h1YTEwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
“旋转”:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpeGlhb2h1YTEwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
一半纹理:
下载
代码位于“Simplest Media Play”中
SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题,最新的版本号请參考该系列文章的总述页面:
上述project包括了使用各种API(Direct3D,OpenGL,GDI,DirectSound。SDL2)播放多媒体样例。
当中音频输入为PCM採样数据。
输出至系统的声卡播放出来。视频输入为YUV/RGB像素数据。输出至显示器上的一个窗体播放出来。
通过本project的代码刚開始学习的人能够高速学习使用这几个API播放视频和音频的技术。
一共包括了例如以下几个子project:
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视频像素数据。
版权声明:本文博主原创文章,博客,未经同意不得转载。
视频和音频播放的演示最简单的例子6:OpenGL广播YUV420P(T经exture,采用Shader)的更多相关文章
- 视频和音频播放的演示最简单的例子9:SDL2广播PCM
===================================================== 最简单的视频和音频播放的演示样品系列列表: 最简单的视音频播放演示样例1:总述 最简单的视音 ...
- H5多媒体(用面向对象的方法控制视频、音频播放、暂停、延时暂停)
视频,音频播放器会是我们在工作中用到的一些h5新标签,它自带一些属性,比如暂停播放,快进快退,但是,我们经常不用原生的样式或者方法,我们需要自定义这些按钮来达到我们需要的样式,也需要我们自定义来实现一 ...
- video.js不能控制本地视频或者音频播放时长
问题: 把视频放到本地,然后对视频进行测试,想要控制视频或者音频的播放时长,没办法做到,每次拉动进度条,都会使得本地视频重新播放 原因: 所有浏览器默认js无法访问本地地址,也就是说js不能对本地文件 ...
- 总结: 在fc23中, 安装音频mp3 视频flv 的播放插件其实很简单, 只要一步就可以了: dnf install gstreamer1-libav
同样是 firefox, 单词的在线发音, 跟 百度mp3的在线播放不是一样的!!! 百度/优酷 的在线播放, 用的确实是 flash player , 所以 你安装好libflashplayer后, ...
- 基于jQuery的视频和音频播放器jPlayer
jPlayer见网络上资料很少,官方英文资料太坑爹TAT,于是就写一个手记给大家参考下.据我观察,jPlayer的原理主要是用到HTML5,在不支持HTML5的浏览器上使用SWF.做到全兼容,这一点很 ...
- SAP播放本地视频及音频(仅限于window MediaPlayer可播放文件)
这个是从SCN上看到的,自己稍加修改,编制,做的还可以,可以播放视频,音频,唯一的不足就是不能控制播放视频的显示窗口大小,希望有人能帮忙解决,感激! 视频播放类:(新建类Z_CL_MEDIA,点击基于 ...
- OCiOS开发:音频播放器 AVAudioPlayer
简单介绍 AVAudioPlayer音频播放器可以提供简单的音频播放功能.其头文件包括在AVFoudation.framework中. AVAudioPlayer未提供可视化界面,须要通过其提供的播放 ...
- 最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...
- 最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...
随机推荐
- CSS选项卡
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org ...
- Android点滴---ViewHolder通用,优雅写法
近期在做项目时,又要写 ViewHolder. 突然想到网上看看有没什么好的写法! 不知道你是不是也烦透了写那些没有技术含量的ViewHolder 看看这些.也许会有收获! 然后就找到了以下两篇文章( ...
- 用友CDM系统期初导入商品资料经验
1. 倒入商品资料,是导入表spkfk(商品档案表).spkfjc(商品总结存表),主要是将spkfk全部编码导入. 2. 导入客商资料,是导入表mchk(业务单位登记表).m ...
- hdu5338 (二进制,双指针)
这种双循环的优化问题碰到过很多了.层出不穷. 但无非就是要利用前面循环时,所产生的信息,从而减少计算. 可以注意到log其实是不超过40的, 那么以这方面入手,时间复杂度就可以降为nlogn log= ...
- C陷阱与缺陷之语法陷阱
2.1理解函数声明 不论什么C变量的声明都由两部分组成:类型以及一组类似表达式的声明符号.比如 float f; 这个声明的含义是:当对其求值时,表达式f和g的类型为浮点数类.由于声 明符与表达式的相 ...
- OpenCms创建网站的过程示意图——专用OpenCms人们刚开始学习
很多人听说过OpenCms,我知道它的强大,只需下载并安装,最后,我们看到了久违OpenCms,我们看到了它的简单的界面,喜悦之后,但难免困惑.如何用这个东西,我如何用它来网站,从哪里开始,无从下手. ...
- 在Eclipse在使用JUnit4单元测试(0基础知识)
自这篇文章: http://www.devx.com/Java/Article/31983/0/page/1 我们在编写大型程序的时候,须要写成千上万个方法或函数.这些函数的功能可能非常强大,但我们在 ...
- Follow your hear(跟着心走)
端午三天的哈尔滨之旅已经over,非常开心真的非常开心.听了刘四风老师的"为爱开讲.我爱这世界"的论坛,尽管.这三天老师讲的不多.可是句句是精华.Follow your heart ...
- 《Effective C++ 》学习笔记——规定10
***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...
- HTML学习_01
html总结 html是一门标记语言,也就是不经过编译就能直接执行的语言,不像是c/c++/java等等须要转换成二进制码, html是一门最主要的学科,提供了一个框架,提供了各种标签和规则,使得语言 ...