OpenGL播放yuv数据流(着色器SHADER)-windows(一)
在写这篇文章之前首先要感谢老雷,http://blog.csdn.net/leixiaohua1020/article/details/40379845这篇文章,可以老雷英年早逝,在此致敬...
下面是代码,具体看注释
//Lvs_OpenGl_Interface.h
- /** Copyright (c/c++) <2016.11.22> <zwg/>
- * Function
- * Opanal for video rendering related implementation and definition, etc.
- * OpanAl 用于视频渲染相关实现及定义,等
- */
- #ifndef __LVS_OPENGL_INTERFACE_H__
- #define __LVS_OPENGL_INTERFACE_H__
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- #include <string.h>
- //windows
- #ifdef WIN32
- //opengl库
- #include "glew.h"
- #include "glut.h"
- #pragma comment(lib,"glew32.lib")
- //ios
- #elif __APPLE__
- //opengl库
- #include "glew.h"
- #include "glut.h"
- //ANDROID平台
- #elif __ANDROID__
- //opengl库
- #include "glew.h"
- #include "glut.h"
- //linux
- #else
- //opengl库
- #include "glew.h"
- #include "glut.h"
- #endif
- //到处宏定义
- //windows
- #ifdef WIN32
- #define LVS_DLLEXPORT __declspec(dllexport)
- //ios
- #elif __APPLE__
- #define LVS_DLLEXPORT
- //linux
- #else
- #define LVS_DLLEXPORT
- #endif
- //着色器用的顶点属性索引 position是由3个(x,y,z)组成,
- #define ATTRIB_VERTEX 3
- //着色器用的像素,纹理属性索引 而颜色是4个(r,g,b,a)
- #define ATTRIB_TEXTURE 4
- //是否旋转图像(纹理)
- #define TEXTURE_ROTATE 0
- //显示图像(纹理)的一半
- #define TEXTURE_HALF 0
- //窗口消息函数指针
- typedef void (*WindowRepaintCK)(void);
- //回调读取数据函数指针,数据及时间戳
- typedef int (*DisplayDataCK)(void ** data,int * timer_millis);
- //接口初始化
- int lvs_opengl_interface_init(int screen_width,int screen_height,
- int window_x, int window_y,
- int yuvdata_width,int uvdata_height,
- char * shader_vsh_pathname,char * shader_fsh_pathname,
- DisplayDataCK displaydatack,
- WindowRepaintCK windowrepaintcallback);
- //接口释放
- void lvs_opengl_interface_uninit();
- //接口渲染数据(定时器,渲染时间,毫秒),数据及渲染定时时间在回调里面做处理
- void lvs_opengl_interface_write(int value);
- //接口opengl消息循环
- void lvs_opengl_interface_messageloop();
- //渲染数据(定时器,渲染时间,毫秒),数据及渲染定时时间在回调里面做处理
- void TimerFunc1(int value); //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归??
- using namespace std;
- class cclass_opengl_interface;
- class cclass_opengl_interface
- {
- public:
- cclass_opengl_interface();
- virtual ~cclass_opengl_interface();
- //初始化
- int initopengl(int screen_width,int screen_height,
- int window_x, int window_y,
- int yuvdata_width,int uvdata_height,
- char * shader_vsh_pathname,char * shader_fsh_pathname,
- DisplayDataCK displaydatack,
- WindowRepaintCK windowrepaintcallback);
- //初始化着色器,类似于告GPU当传进去数据的时候采用什么样的规则。
- void InitShaders();
- //具体显示图像的函数
- int DisplayImage(void * parm);
- //opengl消息循环
- void messageloop();
- private:
- public:
- DisplayDataCK m_displaydatack; //用于显示回调函数,参数数据及时间戳
- char * m_yuvbuf; //存放yuv数据的buf指针,申请buffer在外面
- int m_millis_realtime; //实时的时间戳,每次回调会更新
- private:
- int m_screen_width; //窗口宽
- int m_screen_height; //窗口高
- int m_window_x; //窗口的x坐标
- int m_window_y; //窗口的y坐标
- int m_yuvdata_width; //数据宽
- int m_yuvdata_height; //数据高
- WindowRepaintCK m_windowrepaintcallback; //窗口重绘的时候,例如最大化最小化窗口,缩放窗口等让窗口重绘的时候调用。//从而接收消息循环
- char m_shader_vsh_pathname[256]; //shader的vsh源码位置
- char m_shader_fsh_pathname[256]; //shader的fsh源码位置
- GLuint m_textureid_y, m_textureid_u, m_textureid_v; //纹理的名称,并且,该纹理的名称在当前的应用中不能被再次使用。
- GLuint m_textureUniformY, m_textureUniformU,m_textureUniformV; //用于纹理渲染的变量
- };
- #endif
- #include "Lvs_OpenGl_Interface.h"
- static cclass_opengl_interface * copengl_interface = NULL;
- int lvs_opengl_interface_init(int screen_width,int screen_height,
- int window_x, int window_y,
- int yuvdata_width,int uvdata_height,
- char * shader_vsh_pathname,char * shader_fsh_pathname,
- DisplayDataCK displaydatack,
- WindowRepaintCK windowrepaintcallback)
- {
- int ret = 0;
- printf("Device : lvs_opengl_interface_init\n");
- if(copengl_interface == NULL)
- {
- copengl_interface = new cclass_opengl_interface();
- //初始化
- copengl_interface->initopengl(screen_width,screen_height,
- window_x,window_y,
- yuvdata_width,uvdata_height,
- shader_vsh_pathname,shader_fsh_pathname,
- displaydatack,
- windowrepaintcallback);
- //初始化着色器,类似于告GPU当传进去数据的时候采用什么样的规则。
- copengl_interface->InitShaders();
- }
- return ret;
- }
- void lvs_opengl_interface_uninit()
- {
- printf("Device : lvs_opengl_interface_uninit\n");
- if(copengl_interface)
- {
- delete copengl_interface;
- copengl_interface = NULL;
- }
- return ;
- }
- void lvs_opengl_interface_write(int value)
- {
- //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归
- TimerFunc1(value);
- }
- void lvs_opengl_interface_messageloop()
- {
- copengl_interface->messageloop();
- }
- void TimerFunc1(int value)
- {
- int ret = 0;
- //调用回调函数获取数据
- copengl_interface->m_displaydatack((void **)&copengl_interface->m_yuvbuf,&copengl_interface->m_millis_realtime);
- //这里做具体的处理
- ret = copengl_interface->DisplayImage(NULL);
- //因为glut的定时器是调用一次才产生一次定时,所以如果要持续产生定时的话,
- //在定时函数末尾再次调用glutTimerFunc
- //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归
- glutTimerFunc(copengl_interface->m_millis_realtime, TimerFunc1, 0);
- }
- cclass_opengl_interface::cclass_opengl_interface()
- {
- m_screen_width = 0;
- m_screen_height = 0;
- m_window_x = 0;
- m_window_y = 0;
- m_yuvdata_width = 0;
- m_yuvdata_height = 0;
- memset(m_shader_vsh_pathname,0,256);
- memset(m_shader_fsh_pathname,0,256);
- m_windowrepaintcallback = NULL;
- m_textureid_y = 0;
- m_textureid_u = 0;
- m_textureid_v = 0;
- m_textureUniformY = 0;
- m_textureUniformU = 0;
- m_textureUniformV = 0;
- m_displaydatack = NULL;
- m_yuvbuf = NULL;
- m_millis_realtime = 0;
- }
- cclass_opengl_interface::~cclass_opengl_interface()
- {
- m_screen_width = 0;
- m_screen_height = 0;
- m_window_x = 0;
- m_window_y = 0;
- m_yuvdata_width = 0;
- m_yuvdata_height = 0;
- memset(m_shader_vsh_pathname,0,256);
- memset(m_shader_fsh_pathname,0,256);
- m_windowrepaintcallback = NULL;
- m_textureid_y = 0;
- m_textureid_u = 0;
- m_textureid_v = 0;
- m_textureUniformY = 0;
- m_textureUniformU = 0;
- m_textureUniformV = 0;
- m_displaydatack = NULL;
- m_yuvbuf = NULL;
- m_millis_realtime = 0;
- }
- int cclass_opengl_interface::initopengl(int screen_width,int screen_height,
- int window_x, int window_y,
- int yuvdata_width,int uvdata_height,
- char * shader_vsh_pathname,char * shader_fsh_pathname,
- DisplayDataCK displaydatack,
- WindowRepaintCK windowrepaintcallback)
- {
- int ret = 0;
- m_screen_width = screen_width;
- m_screen_height = screen_height;
- m_window_x = window_x;
- m_window_y = window_y;
- m_yuvdata_width = yuvdata_width;
- m_yuvdata_height = uvdata_height;
- sprintf(m_shader_vsh_pathname,"%s",shader_vsh_pathname);
- sprintf(m_shader_fsh_pathname,"%s",shader_fsh_pathname);
- m_windowrepaintcallback = windowrepaintcallback;
- m_displaydatack = displaydatack;
- //初始化 GLUT opengl函数库
- int zwg_argc=1;
- //添加函数库名称
- char* zwg_argv[]={"ZWG_GLUT"};
- glutInit(&zwg_argc, zwg_argv);
- //设置显示模型
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA /*| GLUT_STENCIL | GLUT_DEPTH*/);
- //设置在屏幕上的起始位置
- glutInitWindowPosition(m_window_x, m_window_y);
- //设置显示窗口大小,宽高
- glutInitWindowSize(m_screen_width, m_screen_height);
- //设置显示窗口名称
- glutCreateWindow("Lvs_OpenGl");
- //OpenGL扩展库是个简单的工具,opengl纹理程序用的着色程序初始化
- GLenum lvs_glew = glewInit();
- //输出版本号
- printf("OpenGl Version: %s\n", glGetString(GL_VERSION));
- //设置绘制窗口时候接收消息的回调函数
- glutDisplayFunc(m_windowrepaintcallback);
- ret = 1;
- return ret;
- }
- void cclass_opengl_interface::InitShaders()
- {
- GLint vertCompiled, fragCompiled; //调试 shader的返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
- GLuint p; //Program着色器程序的id
- GLint linked; //调试 param的返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
- GLint v, f; //shader的id;
- char vs[1024 *10] = {0}; //shader源码的字串vsh 是Vertex Shader(顶点着色器)
- char fs[1024 *10] = {0}; //shader源码的字串fsh 是Fragment Shader(片元着色器)
- const char * vs_buf = vs;
- const char * fs_buf = fs;
- //shader的处理类似于将OpenGL Shader Language,简称GLSL的源码编译成2进制程序
- //“vsh负责搞定像素位置,填写gl_Posizion;fsh负责搞定像素外观,填写 gl_FragColor。”
- //Shader: step1 创建两个shader 实例。
- v = glCreateShader(GL_VERTEX_SHADER);
- f = glCreateShader(GL_FRAGMENT_SHADER);
- //着色器源码
- //penGL的着色器有.fsh和.vsh两个文件。这两个文件在被编译和链接后就可以产生可执行程序与GPU交互。
- //shader.vsh 是Vertex Shader(顶点着色器),用于顶点计算,可以理解控制顶点的位置,在这个文件中我们通常会传入当前顶点的位置,和纹理的坐标。
- //shader.fsh 是Fragment Shader(片源着色器),在这里面我可以对于每一个像素点进行重新计算。
- //将Vertex Shader和Fragment Shader源码读取到字符串中。
- FILE * infile_vsh = fopen(m_shader_vsh_pathname, "rb");
- int len_vsh = fread((char *)vs, 1, 1024 *10, infile_vsh);
- fclose(infile_vsh);
- infile_vsh = NULL;
- vs[len_vsh] = 0;
- FILE * infile_fsh = fopen(m_shader_fsh_pathname, "rb");
- int len_fsh = fread((char *)fs, 1, 1024 *10, infile_fsh);
- fclose(infile_fsh);
- infile_fsh = NULL;
- vs[len_fsh] = 0;
- //Shader: step2 给Shader实例指定源码。
- glShaderSource(v, 1, &vs_buf,NULL);
- glShaderSource(f, 1, &fs_buf,NULL);
- //Shader: step3 在线编译Shader源码。
- glCompileShader(v);
- //Shader: step4 调试一个Shader
- //void glGetShaderiv( GLuint shader,GLenum pname,GLint *params);
- //params:返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
- glGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled);
- glCompileShader(f);
- glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);
- //Program有点类似于一个程序的链接器。program对象提供了把需要做的事连接在一起的机制。在一个program中,shader对象可以连接在一起。
- //Program 这个类似于运行OpenGL Shader Language,简称GLSL的源码编译成2进制程序的执行环境,链接器
- //Program: Step1 创建program
- p = glCreateProgram();
- //Program: Step2 绑定shader到program
- glAttachShader(p,v);
- glAttachShader(p,f);
- //通过glBindAttribLocation()把“顶点属性索引”绑定到“顶点属性名”
- glBindAttribLocation(p, ATTRIB_VERTEX, "vertexIn");
- //通过glBindAttribLocation()把“像素纹理属性索引”绑定到“像素纹理属性名”
- glBindAttribLocation(p, ATTRIB_TEXTURE, "textureIn");
- //Program: Step3 链接program
- glLinkProgram(p);
- //void glGetProgramiv (int program, int pname, int[] params, int offset)
- //参数含义:
- // program:一个着色器程序的id;
- // pname:GL_LINK_STATUS;
- // param:返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
- glGetProgramiv(p, GL_LINK_STATUS, &linked);
- //Program: Step4 在链接了程序以后,我们可以使用glUseProgram()函数来加载并使用链接好的程序
- glUseProgram(p);
- //获取片源着色器源码中的变量,用于纹理渲染
- m_textureUniformY = glGetUniformLocation(p, "tex_y");
- m_textureUniformU = glGetUniformLocation(p, "tex_u");
- m_textureUniformV = glGetUniformLocation(p, "tex_v");
- //顶点数组(物体表面坐标取值范围是-1到1,数组坐标:左下,右下,左上,右上)
- #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
- //像素,纹理数组(纹理坐标取值范围是0-1,坐标原点位于左下角,数组坐标:左上,右上,左下,右下,如果先左下,图像会倒过来)
- #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
- //定义顶点数组
- glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
- //启用属性数组
- glEnableVertexAttribArray(ATTRIB_VERTEX);
- //定义像素纹理数组
- glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
- //启用属性数组
- glEnableVertexAttribArray(ATTRIB_TEXTURE);
- //初始化纹理
- glGenTextures(1, &m_textureid_y);
- //绑定纹理
- glBindTexture(GL_TEXTURE_2D, m_textureid_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, &m_textureid_u);
- glBindTexture(GL_TEXTURE_2D, m_textureid_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, &m_textureid_v);
- glBindTexture(GL_TEXTURE_2D, m_textureid_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 cclass_opengl_interface::DisplayImage(void * parm)
- {
- int ret = 0;
- unsigned char * yuvplaner[3] = {0}; //存放yuv数据的分量数组(y,u,v)
- //关联到yuv数据的分量数组
- yuvplaner[0] = (unsigned char *)m_yuvbuf;
- yuvplaner[1] = yuvplaner[0] + m_yuvdata_width*m_yuvdata_height;
- yuvplaner[2] = yuvplaner[1] + m_yuvdata_width*m_yuvdata_height/4;
- //Clear
- //清除颜色设为黑色,把整个窗口清除为当前的清除颜色,glClear()的唯一参数表示需要被清除的缓冲区。
- glClearColor(0.0,0.0,0.0,0.0);
- glClear(GL_COLOR_BUFFER_BIT);
- //显卡中有N个纹理单元(具体数目依赖你的显卡能力),每个纹理单元(GL_TEXTURE0、GL_TEXTURE1等)都有GL_TEXTURE_1D、GL_TEXTURE_2D等
- //Y
- //选择当前活跃的纹理单元
- glActiveTexture(GL_TEXTURE0);
- //允许建立一个绑定到目标纹理的有名称的纹理
- glBindTexture(GL_TEXTURE_2D, m_textureid_y);
- //根据指定的参数,生成一个2D纹理(Texture)。相似的函数还有glTexImage1D、glTexImage3D。
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_yuvdata_width, m_yuvdata_height, 0, GL_RED, GL_UNSIGNED_BYTE, yuvplaner[0]);
- glUniform1i(m_textureUniformY, 0); //设置纹理,按照前面设置的规则怎样将图像或纹理贴上(参数和选择的活跃纹理单元对应,GL_TEXTURE0)
- //U
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, m_textureid_u);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_yuvdata_width/2, m_yuvdata_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, yuvplaner[1]);
- glUniform1i(m_textureUniformU, 1);
- //V
- glActiveTexture(GL_TEXTURE2);
- glBindTexture(GL_TEXTURE_2D, m_textureid_v);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_yuvdata_width/2, m_yuvdata_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, yuvplaner[2]);
- glUniform1i(m_textureUniformV, 2);
- // 绘制
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- //双缓冲显示
- glutSwapBuffers();
- //单缓冲显示
- //glFlush();
- return 1;
- }
- void cclass_opengl_interface::messageloop()
- {
- //glutMainLoop进入GLUT事件处理循环,让所有的与“事件”有关的函数调用无限循环。开始时间循环
- glutMainLoop();
- }
//main.cpp
- #include "Lvs_OpenGl_Interface.h"
- //要显示的yuv文件路径及名称
- #define YUV_STREAM_PATH_NAME "../yuv_stream/352_288_yuv420p.yuv"
- //yuv数据宽
- #define YUVDATA_WIDTH 352
- //yuv数据高
- #define YUVDATA_HEIGHT 288
- //shader的vsh源码位置
- #define SHADER_VSH_SOURCE "../opengl_win32/Shader.vsh"
- //shader的fsh源码位置
- #define SHADER_FSH_SOURCE "../opengl_win32/Shader.fsh"
- static FILE * m_inyuvfile = NULL; //yuv文件句柄
- static unsigned char m_yuvbuf[YUVDATA_WIDTH*YUVDATA_HEIGHT*3/2]; //存放yuv数据的buf
- static unsigned char * m_yuvplane[3] = {0}; //存放yuv数据的分量数组(y,u,v)
- static int m_timer_realtime = 40; //每一次回调渲染数据定时器时间,可根据时间戳变化,毫秒
- //窗口重绘的时候,例如最大化最小化窗口,缩放窗口等让窗口重绘的时候调用。
- //从而接收消息循环
- void WindowRepaintCallback();
- //回调读取数据函数,参数数据及时间戳
- int DisplayDataCallback(void * data,int * timer_millis);
- //窗口重绘的时候,例如最大化最小化窗口,缩放窗口等让窗口重绘的时候调用。
- //从而接收消息循环
- void WindowRepaintCallback()
- {
- //可以做一些处理
- printf("窗口重绘了...\n");
- }
- int DisplayDataCallback(void ** data,int * timer_millis)
- {
- int ret = 0;
- //循环读取文件
- ret = fread(m_yuvbuf, 1, YUVDATA_WIDTH*YUVDATA_HEIGHT*3/2, m_inyuvfile);
- if (ret != YUVDATA_WIDTH*YUVDATA_HEIGHT*3/2)
- {
- //seek到文件开头
- fseek(m_inyuvfile, 0, SEEK_SET);
- fread(m_yuvbuf, 1,YUVDATA_WIDTH*YUVDATA_HEIGHT*3/2, m_inyuvfile);
- }
- //将数据返回去
- *data = m_yuvbuf;
- *timer_millis = m_timer_realtime;
- return ret;
- }
- int main()
- {
- int ret = 0;
- //打开 YUV420P 文件
- if((m_inyuvfile = fopen(YUV_STREAM_PATH_NAME, "rb")) == NULL)
- {
- printf("filed open file : %s\n",YUV_STREAM_PATH_NAME);
- return getchar();
- }
- else
- {
- printf("success open file : %s\n",YUV_STREAM_PATH_NAME);
- }
- //初始化
- ret = lvs_opengl_interface_init(500,500,
- 100,100,
- YUVDATA_WIDTH,YUVDATA_HEIGHT,
- SHADER_VSH_SOURCE,SHADER_FSH_SOURCE,
- DisplayDataCallback,
- WindowRepaintCallback);
- //渲染,带定时器,数据回调,及渲染时间回调
- lvs_opengl_interface_write(m_timer_realtime);
- //glutMainLoop进入GLUT事件处理循环,让所有的与“事件”有关的函数调用无限循环。开始时间循环
- lvs_opengl_interface_messageloop();
- //关闭yuv420p文件
- if (m_inyuvfile != NULL)
- {
- fclose(m_inyuvfile);
- m_inyuvfile = NULL;
- }
- return 1;
- }
程序运行效果:
暂时不知道怎么解决类成员函数递归,以后待解决。
本demo还需完善。
如有错误请指正: