美文网首页音视频从入门到放弃
kxmovie 源码分析(2)-KxMovieGLView

kxmovie 源码分析(2)-KxMovieGLView

作者: 充满活力的早晨 | 来源:发表于2019-06-24 11:13 被阅读0次

    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

        本文链接:https://www.haomeiwen.com/subject/uiduqctx.html