kxmovie 源码分析(2)-KxMovieGLView

KxMovieGLView

该类就是展示类. 该类的图像展示是使用openGL进行绘制的.

类结构

KxMovieGLView.png

public 方法

  • (id) initWithFrame:(CGRect)frame decoder: (KxMovieDecoder *) decoder;// 初始化
  • (void) render: (KxVideoFrame *) frame; ///渲染图片

OpenGL 基础知识

渲染层

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

想要使用openGL进行绘制,必须使用CAEAGLLayer 作为UIView的图层才可以.

使用OpenGL基本步骤

第一步:配置layer
  CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;

    eagLayer.opaque = YES; // 提高渲染质量 但会消耗内存
    eagLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(false),kEAGLColorFormatRGBA8:@(true)};

kEAGLDrawablePropertyRetainedBacking 不需要retain 已经渲染的图像
kEAGLColorFormatRGBA8 OpenGL采用的颜色空间

第二步 配置context 上下文
  self.eagContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.eagContext];
第三步 创建一个帧缓冲区
glGenFramebuffers(1, &_framebuffer); // 为帧缓存申请一个内存标示,唯一的  1.代表一个帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);// 把这个内存标示绑定到帧缓存上
第四步 创建渲染buffer
  glGenRenderbuffers(1, &_colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [self.eagContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER, _colorRenderbuffer);

第五步 规定绘制屏幕大小
 CGFloat scale = [[UIScreen mainScreen] scale]; //获取视图放大倍数,可以把scale设置为1试试
    glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale); //设置视口大小
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);
第六步 渲染顶点

设置顶点 和 shader

第七步 展示数据
    [self.eagContext  presentRenderbuffer:GL_RENDERBUFFER];
补充知识
   glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);

获取屏幕的宽高

Shader and Program编程基本概念

在OpenGL ES中,每个program对象有且仅有一个Vertex Shader对象和一个Fragment Shader对象连接到它。

Shader:类似于C编译器
Program:类似于C链接器
glLinkProgram操作产生最后的可执行程序,它包含最后可以在硬件上执行的硬件指令。

  1. 创建Shader
  • 1.)编写Vertex Shader和Fragment Shader源码。
  • 2.)创建两个shader 实例:GLuint glCreateShader(GLenum type);
  • 3)给Shader实例指定源码。 glShaderSource
  • 4)在线编译shaer源码 void glCompileShader(GLuint shader)
  1. 创建Program
  • 1)创建program GLuint glCreateProgram(void)
  • 2)绑定shader到program 。 void glAttachShader(GLuint program, GLuint shader)。每个program必须绑定一个Vertex Shader 和一个Fragment Shader。
  • 3)链接program 。 void glLinkProgram(GLuint program)
  • 4)使用porgram 。 void glUseProgram(GLuint program)
  • 对于使用独立shader编译器编译的二进制shader代码,可使用glShaderBinary来加载到一个shader实例中。

shader(着色器) 编程

着色器,是一种较为简短的程序片段,用于告诉图形软件如何计算和输出图像。shader主要分两类:Vertex Shader(顶点着色器)和Fragment Shader(片段着色器)

shader三种变量类型(uniform,attribute和varying)

uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。

uniform mat4 viewProjMatrix; //投影+视图矩阵
uniform mat4 viewMatrix; //视图矩阵
uniform vec3 lightPosition; //光源位置

attribute变量是只能在vertex shader中使用的变量。(它不能在fragment shader中声明attribute变量,也不能被fragment shader中使用)
一般用attribute变量来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等。
在application中,一般用函数glBindAttribLocation()来绑定每个attribute变量的位置,然后用函数glVertexAttribPointer()为每个attribute变量赋值。

varying变量是vertex和fragment shader之间做数据传递用的。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。因此varying变量在vertex和fragment shader二者之间的声明必须是一致的。application不能使用此变量。

内置变量

内置变量可以与固定函数功能进行交互。在使用前不需要声明。顶点着色器可用的内置变量如下表:

名称 类型 描述
gl_Color vec4 输入属性-表示顶点的主颜色
gl_SecondaryColor vec4 输入属性-表示顶点的辅助颜色
gl_Normal vec3 输入属性-表示顶点的法线值
gl_Vertex vec4 输入属性-表示物体空间的顶点位置
gl_MultiTexCoordn vec4 输入属性-表示顶点的第n个纹理的坐标
gl_FogCoord float 输入属性-表示顶点的雾坐标
gl_Position vec4 输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。
gl_ClipVertex vec4 输出坐标,用于用户裁剪平面的裁剪
gl_PointSize float 点的大小
gl_FrontColor vec4 正面的主颜色的varying输出
gl_BackColor vec4 背面主颜色的varying输出
gl_FrontSecondaryColor vec4 正面的辅助颜色的varying输出
gl_BackSecondaryColor vec4 背面的辅助颜色的varying输出
gl_TexCoord[] vec4 纹理坐标的数组varying输出
gl_FogFragCoord float 雾坐标的varying输出

片段着色器的内置变量如下表:

名称 类型 描述
gl_Color vec4 包含主颜色的插值只读输入
gl_SecondaryColor vec4 包含辅助颜色的插值只读输入
gl_TexCoord[] vec4 包含纹理坐标数组的插值只读输入
gl_FogFragCoord float 包含雾坐标的插值只读输入
gl_FragCoord vec4 只读输入,窗口的x,y,z和1/w
gl_FrontFacing bool 只读输入,如果是窗口正面图元的一部分,则这个值为true
gl_PointCoord vec2 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。
gl_FragData[] vec4 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。
gl_FragColor vec4 输出的颜色用于随后的像素操作
gl_FragDepth float 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替

gl_Position 输出的是投影矩阵

矩阵变换

OpenGL中的坐标处理过程包括模型变换、视变换、投影变换、视口变换等过程.
具体变换过程可以参考这里
这里贴两张图

image.png

image.png

这里 gl_Position = 模型变换世界坐标系转换投影转换* 顶点坐标

这里 模型变换 就是自身大小,是标准矩阵
世界坐标系转换 默认把模型放在世界坐标系的原点
投影转换 是正交矩阵

顶点

顶点是啥?
顶点就是坐标位置,不管你是画直线,三角形,正方体,球体,以及3D游戏人物等,都需要顶点来确定其形状。

顶点可可以被定为为2维或者三维,这个看你的实际情况!但是你要注意,所有的内部计算都是建立在三维数据的基础之上,比如:你定义一个点(x,y) 是二维形式,OpenGL默认把它的z设置为0,看到这里你以为三维就是(x,y,z)的形式吗?不是的,OpenGL 是根据三维投影几何的齐次方程坐标进行操作的,因此在内部计算是都是用4个浮点坐标值表示(x,y,z,w) 如果w不等于0 那么这些坐标值就对应于与欧几里德三维点(x/w,y/w,z/w)。一般情况下w默认为1.0.

可以参考这里学习顶点知识

类介绍

初始化

- (id) initWithFrame:(CGRect)frame
             decoder: (KxMovieDecoder *) decoder
{
    self = [super initWithFrame:frame];
    if (self) {
        
        _decoder = decoder;
        
        if ([decoder setupVideoFrameFormat:KxVideoFrameFormatYUV]) {
            
            _renderer = [[KxMovieGLRenderer_YUV alloc] init];
            LoggerVideo(1, @"OK use YUV GL renderer");
            
        } else {
            
            _renderer = [[KxMovieGLRenderer_RGB alloc] init];
            LoggerVideo(1, @"OK use RGB GL renderer");
        }
                
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                        nil];
        
        _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        
        if (!_context ||
            ![EAGLContext setCurrentContext:_context]) {
            
            LoggerVideo(0, @"failed to setup EAGLContext");
            self = nil;
            return nil;
        }
        
        glGenFramebuffers(1, &_framebuffer);
        glGenRenderbuffers(1, &_renderbuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
        [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);
        
        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (status != GL_FRAMEBUFFER_COMPLETE) {
            
            LoggerVideo(0, @"failed to make complete framebuffer object %x", status);
            self = nil;
            return nil;
        }
        
        GLenum glError = glGetError();
        if (GL_NO_ERROR != glError) {
            
            LoggerVideo(0, @"failed to setup GL %x", glError);
            self = nil;
            return nil;
        }
                
        if (![self loadShaders]) {
            
            self = nil;
            return nil;
        }
        
        _vertices[0] = -1.0f;  // x0
        _vertices[1] = -1.0f;  // y0
        _vertices[2] =  1.0f;  // ..
        _vertices[3] = -1.0f;
        _vertices[4] = -1.0f;
        _vertices[5] =  1.0f;
        _vertices[6] =  1.0f;  // x3
        _vertices[7] =  1.0f;  // y3
        
        LoggerVideo(1, @"OK setup GL");
    }
    
    return self;
}
  1. 设置解码器解码成yuv 格式数据流
  2. 配置layer
  3. 配置上下文,上下文不合法返回
  4. 创建缓冲区
  5. 创建渲染buffer.并且绑定渲染buffer的宽高内存地址
  6. 加载shader
  7. 设置顶点(这里采用的顶点是二维的,四个角)

加载shader

这里我们主要看看shader yuv 格式的转换

attribute vec4 position;
 attribute vec2 texcoord;
 uniform mat4 modelViewProjectionMatrix;
 varying vec2 v_texcoord;
 
 void main()
 {
     gl_Position = modelViewProjectionMatrix * position;
     v_texcoord = texcoord.xy;
 }

modelViewProjectionMatrix 是应该模型矩阵,世界标准矩阵,和正交矩阵转换好的矩阵(二维矩阵没必要使用投影.)
v_texcoord 保存纹理

vec4 texture2D(sampler2D sampler, vec2 coord)
The texture2D function returns a texel, i.e. the (color) value of the texture for the given coordinates.
第一个参数代表图片纹理,第二个参数代表纹理坐标点(代表绘制方向),通过GLSL的内建函数texture2D来获取对应位置纹理的颜色RGBA值

我们看yuv片段着色器

 varying highp vec2 v_texcoord;
 uniform sampler2D s_texture_y;
 uniform sampler2D s_texture_u;
 uniform sampler2D s_texture_v;
 
 void main()
 {
     highp float y = texture2D(s_texture_y, v_texcoord).r;
     highp float u = texture2D(s_texture_u, v_texcoord).r - 0.5;
     highp float v = texture2D(s_texture_v, v_texcoord).r - 0.5;
     
     highp float r = y +             1.402 * v;
     highp float g = y - 0.344 * u - 0.714 * v;
     highp float b = y + 1.772 * u;
     
     gl_FragColor = vec4(r,g,b,1.0);     
 }

这里我们知道我们采用的是yuv颜色空间,而渲染要rgb 颜色,因此需要yuv颜色空间转换成rgb颜色空间.

这里我们看见每个从texture2D 获取的rgbA颜色我们只是获取的r颜色空间.这主要是外界传入给我们的数据导致的

image.png

public 方法

- (void)render: (KxVideoFrame *) frame
{        
    static const GLfloat texCoords[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
    };
    
    [EAGLContext setCurrentContext:_context];
    
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glViewport(0, 0, _backingWidth, _backingHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(_program);
        
    if (frame) {
        [_renderer setFrame:frame];        
    }
    
    if ([_renderer prepareRender]) {
        
        GLfloat modelviewProj[16];
        mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, modelviewProj);
        glUniformMatrix4fv(_uniformMatrix, 1, GL_FALSE, modelviewProj);
        
        glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, 0, 0, _vertices);
        glEnableVertexAttribArray(ATTRIBUTE_VERTEX);
        glVertexAttribPointer(ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, 0, 0, texCoords);
        glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD);
        
    #if 0
        if (!validateProgram(_program))
        {
            LoggerVideo(0, @"Failed to validate program");
            return;
        }
    #endif
        
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);        
    }
    
    glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

这个方法就是将镇数据渲染到gl所在的屏幕上.

该类还是比较简单的,就是单纯的将yuv数据进行播放.

你可能感兴趣的:(kxmovie 源码分析(2)-KxMovieGLView)