最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
2015-12-29 09:21
483 查看
=====================================================
最简单的视音频播放演示样例系列文章列表:
最简单的视音频播放演示样例1:总述
最简单的视音频播放演示样例2:GDI播放YUV, RGB
最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
最简单的视音频播放演示样例6:OpenGL播放YUV420P(通过Texture。使用Shader)
最简单的视音频播放演示样例7:SDL2播放RGB/YUV
最简单的视音频播放演示样例8:DirectSound播放PCM
最简单的视音频播放演示样例9:SDL2播放PCM
=====================================================
本文记录OpenGL播放视频的技术。
OpenGL是一个和Direct3D同一层面的技术。
相比于Direct3D。OpenGL具有跨平台的优势。虽然在游戏领域。DirectX的影响力已渐渐超越OpenGL并被大多数PC游戏开发商所採用,但在专业高端画图领域,OpenGL由于色彩准确。仍然是不能被代替的主角。
开放图形库(英语:Open Graphics Library。缩写为OpenGL)是个定义了一个跨编程语言、跨平台的应用程序接口(API)的规范。它用于生成二维、三维图像。
OpenGL规范由1992年成立的OpenGL架构评审委员会(ARB)维护。ARB由一些对创建一个统一的、普遍可用的API特别感兴趣的公司组成。依据OpenGL官方站点,2002年6月的ARB投票成员包括3Dlabs、Apple Computer、ATI Technologies、Dell Computer、Evans & Sutherland、Hewlett-Packard、IBM、Intel、Matrox、NVIDIA、SGI和Sun Microsystems,Microsoft曾是创立成员之中的一个,但已于2003年3月退出。
OpenGL仍然是唯一能够代替微软对3D图形技术的全然控制的API。
它仍然具有一定的生命力。可是Silicon Graphics已经不再以不论什么让微软不悦的方式推广OpenGL,因而它存在较高的风险。
在高端的图形设备和专业应用方面OpenGL占领着统治地位(Direct3D眼下还不支持)。开放源代码社区(尤其是Mesa项目)一直致力于提供OpenGL支持。
OpenGL渲染管线(OpenGL Pipeline)依照特定的顺序对图形信息进行处理,这些图形信息能够分为两个部分:顶点信息(坐标、法向量等)和像素信息(图像、纹理等)。
图形信息终于被写入帧缓存中,存储在帧缓存中的数据(图像),能够被应用程序获得(用于保存结果,或作为应用程序的输入等,见下图中灰色虚线)。
Display List(显示列表)
显示列表是一组OpenGL命令。被存储(编译)起来用于兴许的运行。
全部数据,几何(顶点)数据和像素数据都能够存入显示列表。数据和命令缓存到显示列表中能够提高性能。
Vertex Operation(顶点处理)
顶点坐标和法线坐标经过模式视图矩阵从物体坐标系(object coordinates)转换为观察坐标系(eye coordinates)。若启用了光照。对转换后的定点和法线坐标运行光照计算。光照计算更新了顶点的颜色值。
Primitive Assembly(图元装配)
顶点处理之后。基本图元(点、线、多边形)经过投影矩阵变换。再被视见体裁剪平面裁剪,从观察坐标系转换为裁剪坐标系。之后,进行透视除法(除以w)和视口变换(viewport transform),将3d场景投影到窗体坐标系。
Pixel Transfer Operation(像素操作)
像素从客户内存中解包出来之后,要经过缩放、偏移、映射、箝拉(clamping)。
这些处理即为像素转换操作。
转换的数据存在纹理内存或直接经过光栅化转为片段(fragment)。
Texture Memory(纹理内存)
纹理图像加载到纹理内存中,然后应用到几何对象上。
Raterization(光栅化)
光栅化就是把几何(顶点坐标等)和像素数据转换为片段(fragment)的过程。每一个片段相应于帧缓冲区中的一个像素,该像素相应屏幕上一点的颜色和不透明度信息。片段是一个矩形数组,包括了颜色、深度、线宽、点的大小等信息(反锯齿计算等)。假设渲染模式被设置为GL_FILL。多边形内部的像素信息在这个阶段会被填充。
如上图中的三角形,输入三角形的三个顶点坐标以及其颜色,顶点操作会对三角形的顶点坐标以及法向量进行变换,颜色信息不须要经过变换,但光照计算会影响顶点的颜色信息。
经过光栅化后,三角形被离散为一个个点。不在是三个坐标表示,而是由一系列的点组成,每一个点存储了相应的颜色、深度和不透明度等信息。
Fragment Operation(片段操作)
这是将片段转为帧缓冲区中的像素要进行的最后处理。首先是纹理单元(texel)生成。
一个纹理单元由纹理内存中的数据生成,然后应用到每一个片段上。
之后进行雾计算。
雾计算完毕后,还要按序进行若干片段測试,依次为蒙板(scissor)測试,alpha測试。模版(stencil)測试,深度測试。最后,运行混合,抖动。逻辑操作和遮蔽操作,终于的像素存入framebuffer。
OpenGL与Direct3D的一点点对照
OGL比D3D好的地方:
OGL是业界标准,很多非Windows操作系统下还找不到D3D
OGL的色彩比D3D的要好,表面更光滑
OGL的函数非常有规律。不像D3D的。都是指针method,函数名太长了!
!
OGL是右手坐标系,这是数学里用惯了的.D3D虽然也能够改变成右手坐标系,可是须要d3dx9_36.dll的支持
OGL的经常使用Matrix。如WorldMatrix都封装好了,D3D要自己写。
OGL的画图方式非常灵活,而D3D的则要事先定义好FVF,要等全部信息写进Stream中才绘制。这就使它产生了VertexBuffer和IndexBuffer.好象微软嫌D3D的Buffer不够多?搞的多不好学??看人家OGL,哪里要这个东西?
D3D有好多版本号,要是显卡不支持就废柴一垛了。而OGL从几年前就一直没变过,所以大部分显卡都支持。
还有,我发现D3D的半透明功能有非常大的问题!!就是两个半透明的物体前后顺序的问题——前面的会被后面的挡住。
可是D3D也有比OGL好的地方:
D3D支持很多格式的图片文件,而OGL加载jpg都得自己写代码。
由于D3D是指针调用模式。所以做D3D的钩子有难度,从而添加了外挂的制作难度。
D3D是DirectX的成员。
程序猿要实现声音播放能够用DirectMusic,配套用总是好的,而OGL则仅仅能画画
D3D是被微软大力推广的连接库。
相反。微软则大力压制OGL(都是Microsoft參与研制出来的产品,待遇怎这么大?)
正由于此。D3D已成为中国大型游戏界的主流(我认为他们是盲目跟风。事实上国外非常多游戏都是用OGL)
1. 初始化
即OpenGL不须要使用Direct3D那种使用WinMain()作为主函数的程序初始化窗体。
在Direct3D中是必须要这样做的,即使用Win32的窗体程序而且调用CreateWindow()创建一个对话框。然后才干够在对话框上画图。
OpenGL仅仅须要使用普通的控制台程序就可以(入口函数为main())。当然。OpenGL也能够像Direct3D那样把图像绘制在Win32程序的窗体中。
以下结合OpenGL播放YUV/RGB的演示样例代码,具体分析一下上文的流程。
在详述播放流程之前。再说一点自己学习OpenGL时候的一个明显的感觉:OpenGL的函数好多啊。
OpenGL的函数的特点是数量多,可是每一个函数的參数少。
而Direct3D的特点和它正好反过来。函数少,可是每一个函数的參数多。
glutInit()用于初始化glut库。它原型例如以下:
它包括两个參数:argcp和argv。
普通情况下,直接把main()函数中的argc,argv传递给它就可以。
在这里简单介绍OpenGL中的3个库:glu。glut,glew
glu是有用库,包括有43个函数,函数名的前缀为glu。Glu 为了减轻繁重的编程工作,封装了OpenGL函数,Glu函数通过调用核心库的函数。为开发人员提供相对简单的使用方法。实现一些较为复杂的操作。
glut是有用工具库。基本上是用于做窗体界面的,而且是跨平台的。
glew是一个跨平台的扩展库。没必要的。
它能自己主动识别当前平台所支持的全部OpenGL高级扩展函数。还没有深入研究。
glutInitDisplayMode()用于设置初始显示模式。它的原型例如以下。
当中mode能够选择以下值或组合:
在使用OpenGL播放视频的时候。我们能够使用下述代码:
2) 创建窗体
glutInitWindowPosition()用于设置窗体的位置。
能够指定x。y坐标。
glutInitWindowSize()用于设置窗体的大小。
能够设置窗体的宽,高。
glutCreateWindow()创建一个窗体。能够指定窗体的标题。
上述几个函数十分基础,不再具体叙述。直接贴出一段演示样例代码:
3) 设置画图函数
glutDisplayFunc()用于设置画图函数。
操作系统在必要时刻就会调用该函数对窗体进行又一次绘制操作。
相似于windows程序设计中处理WM_PAINT消息。比如。当把窗体移动到屏幕边上,然后又移动回来的时候,就会调用该函数对窗体进行重绘。
它的原型例如以下。
当中(*func)用于指定重绘函数。
比如在视频播放的时候,指定display()函数用于重绘:
4) 设置定时器
播放视频的时候,每秒须要播放一定的画面(通常是25帧)。因此使用定时器每间隔一段时间调用一下画图函数绘制图形。定时器函数glutTimerFunc()的原型例如以下。
millis:定时的时间。单位是毫秒。1秒=1000毫秒。
(*func)(int value):用于指定定时器调用的函数。
value:给回调函数传參。比較高端,没有接触过。
假设仅仅在主函数中写一个glutTimerFunc()函数的话。会发现仅仅会调用该函数一次。
因此须要在回调函数中再写一个glutTimerFunc()函数,并调用回调函数自己。仅仅有这样才干实现反重复复循环调用回调函数。
比如在视频播放的时候。指定每40毫秒调用一次timeFunc ()函数:
主函数中:
而后在timeFunc()函数中例如以下设置。
这样就实现了每40ms调用一次display()。
5) 进入消息循环
glutMainLoop()将会进入GLUT事件处理循环。一旦被调用,这个程序将永远不会返回。视频播放的时候,调用该函数之后即開始播放视频。
这一步主要是调整一下图像的大小和位置。
假设不做这一步而直接使用glDrawPixels()进行画图的话,会发现图像位于窗体的左下角。而且是上下颠倒的(当然,假设窗体和图像一样大的话,就不存在图像位于角落的问题)。效果例如以下图所看到的。
为了解决上述问题,须要调用有关的函数对图像进行变换。
变换用到了两个函数:glRasterPos3f()和glPixelZoom()。
glRasterPos3f()能够平移图像。它的原型例如以下。
当中x用于指定x坐标;y用于指定y坐标。Z这里还没有用到。
在这里介绍一下OpenGL的坐标。
原点位于屏幕的中心。屏幕的边上相应的值是1.0。和数学中的坐标系基本上是一样的。屏幕的左下角是(-1,-1),左上角是(-1。1)。
比如我们使用glRasterPos3f(-1.0f,0.0f,0)。图像就会移动至(-1。0),例如以下图所看到的。
glPixelZoom()能够放大、缩小和翻转图像。它的原型例如以下。
当中xfactor、yfactor用于指定在x轴,y轴上放大的倍数(假设数值小于1则是缩小)。
假设指定负值,则能够实现翻转。上文已经说过。使用OpenGL直接显示像素数据的话,会发现图像是倒着的。因此须要在Y轴方向对图像进行翻转。
比如:像素数据的宽高分别为pixel_w 。pixel_h ;窗体大小为screen_w,screen_h的话。使用下述代码能够将图像拉伸至窗体大小,而且翻转:
结合上述两个函数。即“平移+翻转+拉伸之后”,就能够得到全屏的图像了,例如以下图所看到的。
PS:这种方法属于比較笨的方法。应该还有更好的方法吧。只是再没有进行深入研究了。
2) 画图
使用glDrawPixels()能够绘制指定内存中的像素数据。该函数的原型例如以下。
该函数的參数的含义例如以下所看到的:
Width:像素数据的宽。
Height:像素数据的高。
Format:像素数据的格式,比如GL_RGB,GL_BGR,GL_BGRA等。
Type:像素数据在内存中的格式。
Pixels:指针,指向存储像素数据的内存。
比如绘制RGB24格式的数据。宽为pixel_w,高为pixel_h,像素数据存储在buffer中。能够使用例如以下代码。
3) 显示
使用双缓冲的时候。调用函数glutSwapBuffers()进行显示。
使用单缓冲的时候。调用函数glFlush()进行显示。
2. 窗体的宽高为screen_w,screen_h。像素数据的宽高为pixel_w,pixel_h。它们的定义例如以下。
3. 注意显示方式的不同
BGRA,BGR24,RGB24这3种格式能够直接在glDrawPixels()中设置像素格式显示出来。而YUV420P是不能直接显示出来的。
本文演示样例採用的方式是先将YUV420P转换成RGB24,然后进行显示。
SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题。最新的版本号请參考该系列文章的总述页面:
《最简单的视音频播放演示样例1:总述》
上述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视频像素数据。
最简单的视音频播放演示样例系列文章列表:
最简单的视音频播放演示样例1:总述
最简单的视音频播放演示样例2:GDI播放YUV, RGB
最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
最简单的视音频播放演示样例6:OpenGL播放YUV420P(通过Texture。使用Shader)
最简单的视音频播放演示样例7:SDL2播放RGB/YUV
最简单的视音频播放演示样例8:DirectSound播放PCM
最简单的视音频播放演示样例9:SDL2播放PCM
=====================================================
本文记录OpenGL播放视频的技术。
OpenGL是一个和Direct3D同一层面的技术。
相比于Direct3D。OpenGL具有跨平台的优势。虽然在游戏领域。DirectX的影响力已渐渐超越OpenGL并被大多数PC游戏开发商所採用,但在专业高端画图领域,OpenGL由于色彩准确。仍然是不能被代替的主角。
OpenGL简单介绍
从网上搜集了一些有关OpenGL简单介绍方面的知识,在这里列出来。开放图形库(英语:Open Graphics Library。缩写为OpenGL)是个定义了一个跨编程语言、跨平台的应用程序接口(API)的规范。它用于生成二维、三维图像。
OpenGL规范由1992年成立的OpenGL架构评审委员会(ARB)维护。ARB由一些对创建一个统一的、普遍可用的API特别感兴趣的公司组成。依据OpenGL官方站点,2002年6月的ARB投票成员包括3Dlabs、Apple Computer、ATI Technologies、Dell Computer、Evans & Sutherland、Hewlett-Packard、IBM、Intel、Matrox、NVIDIA、SGI和Sun Microsystems,Microsoft曾是创立成员之中的一个,但已于2003年3月退出。
OpenGL仍然是唯一能够代替微软对3D图形技术的全然控制的API。
它仍然具有一定的生命力。可是Silicon Graphics已经不再以不论什么让微软不悦的方式推广OpenGL,因而它存在较高的风险。
在高端的图形设备和专业应用方面OpenGL占领着统治地位(Direct3D眼下还不支持)。开放源代码社区(尤其是Mesa项目)一直致力于提供OpenGL支持。
OpenGL渲染管线
下文也是网上看的。搞懂了一部分。可是由于3D方面基础不坚固有些方面还没有全然弄懂。OpenGL渲染管线(OpenGL Pipeline)依照特定的顺序对图形信息进行处理,这些图形信息能够分为两个部分:顶点信息(坐标、法向量等)和像素信息(图像、纹理等)。
图形信息终于被写入帧缓存中,存储在帧缓存中的数据(图像),能够被应用程序获得(用于保存结果,或作为应用程序的输入等,见下图中灰色虚线)。
Display List(显示列表)
显示列表是一组OpenGL命令。被存储(编译)起来用于兴许的运行。
全部数据,几何(顶点)数据和像素数据都能够存入显示列表。数据和命令缓存到显示列表中能够提高性能。
Vertex Operation(顶点处理)
顶点坐标和法线坐标经过模式视图矩阵从物体坐标系(object coordinates)转换为观察坐标系(eye coordinates)。若启用了光照。对转换后的定点和法线坐标运行光照计算。光照计算更新了顶点的颜色值。
Primitive Assembly(图元装配)
顶点处理之后。基本图元(点、线、多边形)经过投影矩阵变换。再被视见体裁剪平面裁剪,从观察坐标系转换为裁剪坐标系。之后,进行透视除法(除以w)和视口变换(viewport transform),将3d场景投影到窗体坐标系。
Pixel Transfer Operation(像素操作)
像素从客户内存中解包出来之后,要经过缩放、偏移、映射、箝拉(clamping)。
这些处理即为像素转换操作。
转换的数据存在纹理内存或直接经过光栅化转为片段(fragment)。
Texture Memory(纹理内存)
纹理图像加载到纹理内存中,然后应用到几何对象上。
Raterization(光栅化)
光栅化就是把几何(顶点坐标等)和像素数据转换为片段(fragment)的过程。每一个片段相应于帧缓冲区中的一个像素,该像素相应屏幕上一点的颜色和不透明度信息。片段是一个矩形数组,包括了颜色、深度、线宽、点的大小等信息(反锯齿计算等)。假设渲染模式被设置为GL_FILL。多边形内部的像素信息在这个阶段会被填充。
如上图中的三角形,输入三角形的三个顶点坐标以及其颜色,顶点操作会对三角形的顶点坐标以及法向量进行变换,颜色信息不须要经过变换,但光照计算会影响顶点的颜色信息。
经过光栅化后,三角形被离散为一个个点。不在是三个坐标表示,而是由一系列的点组成,每一个点存储了相应的颜色、深度和不透明度等信息。
Fragment Operation(片段操作)
这是将片段转为帧缓冲区中的像素要进行的最后处理。首先是纹理单元(texel)生成。
一个纹理单元由纹理内存中的数据生成,然后应用到每一个片段上。
之后进行雾计算。
雾计算完毕后,还要按序进行若干片段測试,依次为蒙板(scissor)測试,alpha測试。模版(stencil)測试,深度測试。最后,运行混合,抖动。逻辑操作和遮蔽操作,终于的像素存入framebuffer。
OpenGL与Direct3D的对照
有关视频显示的技术在《Direct3D》文章中已经有过叙述,在这里不再重复。在网上看了一下有关于他们不同点的文章,写得简单明了。在这里引用一下:OpenGL与Direct3D的一点点对照
OGL比D3D好的地方:
OGL是业界标准,很多非Windows操作系统下还找不到D3D
OGL的色彩比D3D的要好,表面更光滑
OGL的函数非常有规律。不像D3D的。都是指针method,函数名太长了!
!
OGL是右手坐标系,这是数学里用惯了的.D3D虽然也能够改变成右手坐标系,可是须要d3dx9_36.dll的支持
OGL的经常使用Matrix。如WorldMatrix都封装好了,D3D要自己写。
OGL的画图方式非常灵活,而D3D的则要事先定义好FVF,要等全部信息写进Stream中才绘制。这就使它产生了VertexBuffer和IndexBuffer.好象微软嫌D3D的Buffer不够多?搞的多不好学??看人家OGL,哪里要这个东西?
D3D有好多版本号,要是显卡不支持就废柴一垛了。而OGL从几年前就一直没变过,所以大部分显卡都支持。
还有,我发现D3D的半透明功能有非常大的问题!!就是两个半透明的物体前后顺序的问题——前面的会被后面的挡住。
可是D3D也有比OGL好的地方:
D3D支持很多格式的图片文件,而OGL加载jpg都得自己写代码。
由于D3D是指针调用模式。所以做D3D的钩子有难度,从而添加了外挂的制作难度。
D3D是DirectX的成员。
程序猿要实现声音播放能够用DirectMusic,配套用总是好的,而OGL则仅仅能画画
D3D是被微软大力推广的连接库。
相反。微软则大力压制OGL(都是Microsoft參与研制出来的产品,待遇怎这么大?)
正由于此。D3D已成为中国大型游戏界的主流(我认为他们是盲目跟风。事实上国外非常多游戏都是用OGL)
OpenGL视频显示的流程
使用OpenGL播放视频最简单的情况下须要例如以下步骤:1. 初始化
1) 初始化
2) 创建窗体
3) 设置画图函数
4) 设置定时器
5) 进入消息循环
2. 循环显示画面2) 创建窗体
3) 设置画图函数
4) 设置定时器
5) 进入消息循环
1) 调整显示位置,图像大小
2) 画图
3) 显示
在这里有一点须要说明。2) 画图
3) 显示
即OpenGL不须要使用Direct3D那种使用WinMain()作为主函数的程序初始化窗体。
在Direct3D中是必须要这样做的,即使用Win32的窗体程序而且调用CreateWindow()创建一个对话框。然后才干够在对话框上画图。
OpenGL仅仅须要使用普通的控制台程序就可以(入口函数为main())。当然。OpenGL也能够像Direct3D那样把图像绘制在Win32程序的窗体中。
以下结合OpenGL播放YUV/RGB的演示样例代码,具体分析一下上文的流程。
在详述播放流程之前。再说一点自己学习OpenGL时候的一个明显的感觉:OpenGL的函数好多啊。
OpenGL的函数的特点是数量多,可是每一个函数的參数少。
而Direct3D的特点和它正好反过来。函数少,可是每一个函数的參数多。
1. 初始化
1) 初始化glutInit()用于初始化glut库。它原型例如以下:
void glutInit(int *argcp, char **argv);
它包括两个參数:argcp和argv。
普通情况下,直接把main()函数中的argc,argv传递给它就可以。
在这里简单介绍OpenGL中的3个库:glu。glut,glew
glu是有用库,包括有43个函数,函数名的前缀为glu。Glu 为了减轻繁重的编程工作,封装了OpenGL函数,Glu函数通过调用核心库的函数。为开发人员提供相对简单的使用方法。实现一些较为复杂的操作。
glut是有用工具库。基本上是用于做窗体界面的,而且是跨平台的。
glew是一个跨平台的扩展库。没必要的。
它能自己主动识别当前平台所支持的全部OpenGL高级扩展函数。还没有深入研究。
glutInitDisplayMode()用于设置初始显示模式。它的原型例如以下。
void glutInitDisplayMode(unsigned int mode)
当中mode能够选择以下值或组合:
GLUT_RGB: 指定 RGB 颜色模式的窗体
GLUT_RGBA: 指定 RGBA 颜色模式的窗体
GLUT_INDEX: 指定颜色索引模式的窗体
GLUT_SINGLE: 指定单缓存窗体
GLUT_DOUBLE: 指定双缓存窗体
GLUT_ACCUM: 窗体使用累加缓存
GLUT_ALPHA: 窗体的颜色分量包括 alpha 值
GLUT_DEPTH: 窗体使用深度缓存
GLUT_STENCIL: 窗体使用模板缓存
GLUT_MULTISAMPLE: 指定支持多样本功能的窗体
GLUT_STEREO: 指定立体窗体
GLUT_LUMINANCE: 窗体使用亮度颜色模型
须要注意的是,假设使用双缓冲(GLUT_DOUBLE),则须要用glutSwapBuffers ()画图。假设使用单缓冲(GLUT_SINGLE),则须要用glFlush()画图。GLUT_RGBA: 指定 RGBA 颜色模式的窗体
GLUT_INDEX: 指定颜色索引模式的窗体
GLUT_SINGLE: 指定单缓存窗体
GLUT_DOUBLE: 指定双缓存窗体
GLUT_ACCUM: 窗体使用累加缓存
GLUT_ALPHA: 窗体的颜色分量包括 alpha 值
GLUT_DEPTH: 窗体使用深度缓存
GLUT_STENCIL: 窗体使用模板缓存
GLUT_MULTISAMPLE: 指定支持多样本功能的窗体
GLUT_STEREO: 指定立体窗体
GLUT_LUMINANCE: 窗体使用亮度颜色模型
在使用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) 进入消息循环
glutMainLoop()将会进入GLUT事件处理循环。一旦被调用,这个程序将永远不会返回。视频播放的时候,调用该函数之后即開始播放视频。
2. 循环显示画面
1) 调整显示位置,图像大小这一步主要是调整一下图像的大小和位置。
假设不做这一步而直接使用glDrawPixels()进行画图的话,会发现图像位于窗体的左下角。而且是上下颠倒的(当然,假设窗体和图像一样大的话,就不存在图像位于角落的问题)。效果例如以下图所看到的。
为了解决上述问题,须要调用有关的函数对图像进行变换。
变换用到了两个函数:glRasterPos3f()和glPixelZoom()。
glRasterPos3f()能够平移图像。它的原型例如以下。
void glRasterPos3f (GLfloat x, GLfloat y, GLfloat z);
当中x用于指定x坐标;y用于指定y坐标。Z这里还没有用到。
在这里介绍一下OpenGL的坐标。
原点位于屏幕的中心。屏幕的边上相应的值是1.0。和数学中的坐标系基本上是一样的。屏幕的左下角是(-1,-1),左上角是(-1。1)。
比如我们使用glRasterPos3f(-1.0f,0.0f,0)。图像就会移动至(-1。0),例如以下图所看到的。
glPixelZoom()能够放大、缩小和翻转图像。它的原型例如以下。
void glPixelZoom (GLfloat xfactor, GLfloat yfactor);
当中xfactor、yfactor用于指定在x轴,y轴上放大的倍数(假设数值小于1则是缩小)。
假设指定负值,则能够实现翻转。上文已经说过。使用OpenGL直接显示像素数据的话,会发现图像是倒着的。因此须要在Y轴方向对图像进行翻转。
比如:像素数据的宽高分别为pixel_w 。pixel_h ;窗体大小为screen_w,screen_h的话。使用下述代码能够将图像拉伸至窗体大小,而且翻转:
glPixelZoom((float)screen_w/(float)pixel_w, -(float)screen_h/pixel_h);
结合上述两个函数。即“平移+翻转+拉伸之后”,就能够得到全屏的图像了,例如以下图所看到的。
PS:这种方法属于比較笨的方法。应该还有更好的方法吧。只是再没有进行深入研究了。
2) 画图
使用glDrawPixels()能够绘制指定内存中的像素数据。该函数的原型例如以下。
void glDrawPixels ( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
该函数的參数的含义例如以下所看到的:
Width:像素数据的宽。
Height:像素数据的高。
Format:像素数据的格式,比如GL_RGB,GL_BGR,GL_BGRA等。
Type:像素数据在内存中的格式。
Pixels:指针,指向存储像素数据的内存。
比如绘制RGB24格式的数据。宽为pixel_w,高为pixel_h,像素数据存储在buffer中。能够使用例如以下代码。
glDrawPixels(pixel_w, pixel_h,GL_RGB, GL_UNSIGNED_BYTE, buffer);
3) 显示
使用双缓冲的时候。调用函数glutSwapBuffers()进行显示。
使用单缓冲的时候。调用函数glFlush()进行显示。
视频显示的流程总结
视频显示的函数调用结构能够总结为下图代码
贴上源代码。/**
* 最简单的OpenGL播放视频的样例(OpenGL播放RGB/YUV)
* Simplest Video Play OpenGL (OpenGL play RGB/YUV)
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020 *
* 本程序使用OpenGL播放RGB/YUV视频像素数据。
本程序实际上仅仅能
* 播放RGB(RGB24,BGR24,BGRA)数据。假设输入数据为YUV420P
* 数据的话。须要先转换为RGB数据之后再进行播放。
* 本程序是最简单的使用OpenGL播放像素数据的样例,适合OpenGL新手学习。
*
* 函数调用过程例如以下:
*
* [初始化]
* glutInit(): 初始化glut库。
* glutInitDisplayMode(): 设置显示模式。
* glutCreateWindow(): 创建一个窗体。
* glutDisplayFunc(): 设置画图函数(重绘的时候调用)。
* glutTimerFunc(): 设置定时器。
* glutMainLoop(): 进入消息循环。
*
* [循环渲染数据]
* glRasterPos3f(),glPixelZoom(): 调整显示位置,图像大小。
* glDrawPixels(): 绘制。
* glutSwapBuffers(): 显示。
*
* This software plays RGB/YUV raw video data using OpenGL. This
* software support show RGB (RGB24, BGR24, BGRA) data on the screen.
* If the input data is YUV420P, it need to be convert to RGB first.
* This program is the simplest example about play raw video data
* using OpenGL, Suitable for the beginner of OpenGL.
*
* The process is shown as follows:
*
* [Init]
* glutInit(): Init glut library.
* glutInitDisplayMode(): Set display mode.
* glutCreateWindow(): Create a window.
* glutDisplayFunc(): Set the display callback.
* glutTimerFunc(): Set timer.
* glutMainLoop(): Start message loop.
*
* [Loop to Render data]
* glRasterPos3f(),glPixelZoom(): Change picture's size and position.
* glDrawPixels(): draw.
* glutSwapBuffers(): show.
*/
#include <stdio.h>
#include "glew.h"
#include "glut.h"
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
//set '1' to choose a type of file to play #define LOAD_RGB24 1 #define LOAD_BGR24 0 #define LOAD_BGRA 0 #define LOAD_YUV420P 0
int screen_w=500,screen_h=500;
const int pixel_w = 320, pixel_h = 180;
//Bit per Pixel
#if LOAD_BGRA
const int bpp=32;
#elif LOAD_RGB24|LOAD_BGR24
const int bpp=24;
#elif LOAD_YUV420P
const int bpp=12;
#endif
//YUV file
FILE *fp = NULL;
unsigned char buffer[pixel_w*pixel_h*bpp/8];
unsigned char buffer_convert[pixel_w*pixel_h*3];
inline unsigned char CONVERT_ADJUST(double tmp)
{
return (unsigned char)((tmp >= 0 && tmp <= 255)?
tmp:(tmp < 0 ?
0 : 255));
}
//YUV420P to RGB24
void CONVERT_YUV420PtoRGB24(unsigned char* yuv_src,unsigned char* rgb_dst,int nWidth,int nHeight)
{
unsigned char *tmpbuf=(unsigned char *)malloc(nWidth*nHeight*3);
unsigned char Y,U,V,R,G,B;
unsigned char* y_planar,*u_planar,*v_planar;
int rgb_width , u_width;
rgb_width = nWidth * 3;
u_width = (nWidth >> 1);
int ypSize = nWidth * nHeight;
int upSize = (ypSize>>2);
int offSet = 0;
y_planar = yuv_src;
u_planar = yuv_src + ypSize;
v_planar = u_planar + upSize;
for(int i = 0; i < nHeight; i++)
{
for(int j = 0; j < nWidth; j ++)
{
// Get the Y value from the y planar
Y = *(y_planar + nWidth * i + j);
// Get the V value from the u planar
offSet = (i>>1) * (u_width) + (j>>1);
V = *(u_planar + offSet);
// Get the U value from the v planar
U = *(v_planar + offSet);
// Cacular the R,G,B values
// Method 1
R = CONVERT_ADJUST((Y + (1.4075 * (V - 128))));
G = CONVERT_ADJUST((Y - (0.3455 * (U - 128) - 0.7169 * (V - 128))));
B = CONVERT_ADJUST((Y + (1.7790 * (U - 128))));
/*
// The following formulas are from MicroSoft' MSDN
int C,D,E;
// Method 2
C = Y - 16;
D = U - 128;
E = V - 128;
R = CONVERT_ADJUST(( 298 * C + 409 * E + 128) >> 8);
G = CONVERT_ADJUST(( 298 * C - 100 * D - 208 * E + 128) >> 8);
B = CONVERT_ADJUST(( 298 * C + 516 * D + 128) >> 8);
R = ((R - 128) * .6 + 128 )>255?
255:(R - 128) * .6 + 128;
G = ((G - 128) * .6 + 128 )>255?255:(G - 128) * .6 + 128;
B = ((B - 128) * .6 + 128 )>255?255:(B - 128) * .6 + 128;
*/
offSet = rgb_width * i + j * 3;
rgb_dst[offSet] = B;
rgb_dst[offSet + 1] = G;
rgb_dst[offSet + 2] = R;
}
}
free(tmpbuf);
}
void display(void){
if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){
// Loop
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);
}
//Make picture full of window
//Move to(-1.0,1.0)
glRasterPos3f(-1.0f,1.0f,0);
//Zoom, Flip
glPixelZoom((float)screen_w/(float)pixel_w, -(float)screen_h/(float)pixel_h);
#if LOAD_BGRA
glDrawPixels(pixel_w, pixel_h,GL_BGRA, GL_UNSIGNED_BYTE, buffer);
#elif LOAD_RGB24
glDrawPixels(pixel_w, pixel_h,GL_RGB, GL_UNSIGNED_BYTE, buffer);
#elif LOAD_BGR24
glDrawPixels(pixel_w, pixel_h,GL_BGR_EXT, GL_UNSIGNED_BYTE, buffer);
#elif LOAD_YUV420P
CONVERT_YUV420PtoRGB24(buffer,buffer_convert,pixel_w,pixel_h);
glDrawPixels(pixel_w, pixel_h,GL_RGB, GL_UNSIGNED_BYTE, buffer_convert);
#endif
//GLUT_DOUBLE
glutSwapBuffers();
//GLUT_SINGLE
//glFlush();
}
void timeFunc(int value){ display(); // Present frame every 40 ms glutTimerFunc(40, timeFunc, 0); }
int main(int argc, char* argv[])
{
#if LOAD_BGRA
fp=fopen("../test_bgra_320x180.rgb","rb+");
#elif LOAD_RGB24
fp=fopen("../test_rgb24_320x180.rgb","rb+");
#elif LOAD_BGR24
fp=fopen("../test_bgr24_320x180.rgb","rb+");
#elif LOAD_YUV420P
fp=fopen("../test_yuv420p_320x180.yuv","rb+");
#endif
if(fp==NULL){
printf("Cannot open this file.\n");
return -1;
}
// GLUT init
glutInit(&argc, argv);
//Double, Use glutSwapBuffers() to show
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
//Single, Use glFlush() to show
//glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB );
glutInitWindowPosition(100, 100);
glutInitWindowSize(screen_w, screen_h);
glutCreateWindow("Simplest Video Play OpenGL");
printf("Simplest Video Play OpenGL\n");
printf("Lei Xiaohua\n");
printf("http://blog.csdn.net/leixiaohua1020\n");
printf("OpenGL Version: %s\n", glGetString(GL_VERSION));
glutDisplayFunc(&display);
glutTimerFunc(40, timeFunc, 0);
// Start!
glutMainLoop();
return 0;
}
代码注意事项
1. 能够通过设置定义在文件開始出的宏。决定读取哪个格式的像素数据(bgra,rgb24,bgr24,yuv420p)。//set '1' to choose a type of file to play #define LOAD_RGB24 1 #define LOAD_BGR24 0 #define LOAD_BGRA 0 #define LOAD_YUV420P 0
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. 注意显示方式的不同
BGRA,BGR24,RGB24这3种格式能够直接在glDrawPixels()中设置像素格式显示出来。而YUV420P是不能直接显示出来的。
本文演示样例採用的方式是先将YUV420P转换成RGB24,然后进行显示。
运行结果
不管选择加载哪个文件,运行结果都是一样的,例如以下图所看到的。下载
代码位于“Simplest Media Play”中SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题。最新的版本号请參考该系列文章的总述页面:
《最简单的视音频播放演示样例1:总述》
上述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视频像素数据。
相关文章推荐
- NopCommerce使用Autofac实现依赖注入
- 读深入分析DOcker镜像原理笔记
- shell脚本中的数学运算
- linux 常用命令
- Linux中断流程分析
- Centos6.5 tomcat8 基本安装(适合初学者)
- tomcat安全防护之普通用户运行
- 【转载】架构师谈五层网站架构
- CentOS 修改主机名
- linux挂载共享文件
- Linux下yum命令详解
- Linux Grub 开机画面制作
- Linux sync命令的作用
- DropBox为什么一直那么红——靠用户体验,旗帜鲜明,它要保存的是你的重要随身资料,并且开放API
- linux下监测命令执行结果的工具watch小结
- IntelliJ IDEA+Tomcat+Nginx运行git项目
- linux下网卡配置管理工具ifconfig小结
- copy和mutableCopy
- 循序渐进看Java web日志跟踪(1)-Tomcat 日志追踪与配置
- 在命令行中通过adb shell am broadcast发送广播通知