=====================================================
最简单的视音频播放示例系列文章列表:
最简单的视音频播放示例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
#include "glew.h"
#include "glut.h"
#include
#include
#include
//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音频采样数据。