=====================================================
最简单的视音频播放示例系列文章列表:
最简单的视音频播放示例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因为色彩准确,仍然是不能被取代的主角。
下文也是网上看的,搞懂了一部分,但是由于3D方面基础不牢固有些方面还没有完全弄懂。
OpenGL渲染管线(OpenGL Pipeline)按照特定的顺序对图形信息进行处理,这些图形信息可以分为两个部分:顶点信息(坐标、法向量等)和像素信息(图像、纹理等)。图形信息最终被写入帧缓存中,存储在帧缓存中的数据(图像),可以被应用程序获得(用于保存结果,或作为应用程序的输入等,见下图中灰色虚线)。
顶点处理之后,基本图元(点、线、多边形)经过投影矩阵变换,再被视见体裁剪平面裁剪,从观察坐标系转换为裁剪坐标系。之后,进行透视除法(除以w)和视口变换(viewport transform),将3d场景投影到窗口坐标系。
Pixel Transfer Operation(像素操作)
像素从客户内存中解包出来之后,要经过缩放、偏移、映射、箝拉(clamping)。这些处理即为像素转换操作。转换的数据存在纹理内存或直接经过光栅化转为片段(fragment)。
Texture Memory(纹理内存)
纹理图像载入到纹理内存中,然后应用到几何对象上。
Raterization(光栅化)
光栅化就是把几何(顶点坐标等)和像素数据转换为片段(fragment)的过程,每个片段对应于帧缓冲区中的一个像素,该像素对应屏幕上一点的颜色和不透明度信息。片段是一个矩形数组,包含了颜色、深度、线宽、点的大小等信息(反锯齿计算等)。如果渲染模式被设置为GL_FILL,多边形内部的像素信息在这个阶段会被填充。
1) 初始化2. 循环显示画面
2) 创建窗口
3) 设置绘图函数
4) 设置定时器
5) 进入消息循环
1) 调整显示位置,图像大小在这里有一点需要说明。即OpenGL不需要使用Direct3D那种使用WinMain()作为主函数的程序初始化窗口。在Direct3D中是必须要这样做的,即使用Win32的窗口程序并且调用CreateWindow()创建一个对话框,然后才可以在对话框上绘图。OpenGL只需要使用普通的控制台程序即可(入口函数为main())。当然,OpenGL也可以像Direct3D那样把图像绘制在Win32程序的窗口中。
2) 画图
3) 显示
void glutInit(int *argcp, char **argv);
glew是一个跨平台的扩展库。不是必需的。它能自动识别当前平台所支持的全部OpenGL高级扩展函数。还没有深入研究。
glutInitDisplayMode()用于设置初始显示模式。它的原型如下。
void glutInitDisplayMode(unsigned int mode)
GLUT_RGB: 指定 RGB 颜色模式的窗口需要注意的是,如果使用双缓冲(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: 窗口使用亮度颜色模型
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
glutInitWindowPosition(100, 100); glutInitWindowSize(500, 500); glutCreateWindow("Simplest Video Play OpenGL");
void glutDisplayFunc(void (*func)(void));
其中(*func)用于指定重绘函数。
例如在视频播放的时候,指定display()函数用于重绘:glutDisplayFunc(&display);
void glutTimerFunc(unsigned int millis, void (*func)(int value), int value);它的参数含义如下:
glutTimerFunc(40, timeFunc, 0);
void timeFunc(int value){ display(); // Present frame every 40 ms glutTimerFunc(40, timeFunc, 0); }
void glRasterPos3f (GLfloat x, GLfloat y, GLfloat z);
void glPixelZoom (GLfloat xfactor, GLfloat yfactor);
glPixelZoom((float)screen_w/(float)pixel_w, -(float)screen_h/pixel_h);
PS:这个方法属于比较笨的方法,应该还有更好的方法吧。不过再没有进行深入研究了。
void glDrawPixels ( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
glDrawPixels(pixel_w, pixel_h,GL_RGB, GL_UNSIGNED_BYTE, buffer);
视频显示的函数调用结构可以总结为下图
/** * 最简单的OpenGL播放视频的例子(OpenGL播放RGB/YUV) * Simplest Video Play OpenGL (OpenGL play RGB/YUV) * * 雷霄骅 Lei Xiaohua * [email protected] * 中国传媒大学/数字电视技术 * 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; }
//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
//Width, Height const int screen_w=500,screen_h=500; const int pixel_w=320,pixel_h=180;
无论选择加载哪个文件,运行结果都是一样的,如下图所示。
SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题,最新的版本请参考该系列文章的总述页面:
《最简单的视音频播放示例1:总述》
上述工程包含了使用各种API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒体例子。其中音频输入为PCM采样数据。输出至系统的声卡播放出来。视频输入为YUV/RGB像素数据。输出至显示器上的一个窗口播放出来。
通过本工程的代码初学者可以快速学习使用这几个API播放视频和音频的技术。
一共包括了如下几个子工程:
simplest_audio_play_directsound: 使用DirectSound播放PCM音频采样数据。