OpenGL播放yuv数据流(着色器SHADER)-IOS(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhuweigangzwg/article/details/53927750

OpenGL播放yuv数据流(着色器SHADER)-IOS(一)


和windows平台的类似,只是用object-c编写的,着色器语言shader,rgb转yuv有些不同,具体看代码注释。


//.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 <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import <OpenGLES/EAGL.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//着色器用的顶点属性索引 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

//shader源码用的宏
#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(text) @ STRINGIZE2(text)

//shader的vsh源码字符串
/*
static NSString *const vertexShaderString = SHADER_STRING(
        attribute vec4 position;
        attribute vec2 TexCoordIn;
        varying vec2 TexCoordOut;
        void main(void)
        {
            gl_Position = position;
            TexCoordOut = TexCoordIn;
        });
 */
static NSString *const vertexShaderString = SHADER_STRING(
        attribute vec4 vertexIn;
        attribute vec2 textureIn;
        varying vec2 textureOut;
        void main(void)
        {
            gl_Position = vertexIn;
            textureOut = textureIn;
        });


//shader的fsh源码字符串
static NSString *const yuvFragmentShaderString = SHADER_STRING(
        varying lowp vec2 textureOut;
        uniform sampler2D tex_y;
        uniform sampler2D tex_u;
        uniform sampler2D tex_v;
        void main(void)
        {
            mediump vec3 yuv;
            lowp vec3 rgb;
            yuv.x = texture2D(tex_y, textureOut).r;
            yuv.y = texture2D(tex_u, textureOut).r - 0.5;
            yuv.z = texture2D(tex_v, textureOut).r - 0.5;
            rgb = mat3(1,1,1,0,-0.39465,2.03211,1.13983, -0.58060,  0) * yuv;
            gl_FragColor = vec4(rgb, 1);
        });
/*
static NSString *const yuvFragmentShaderString = SHADER_STRING(
        varying vec2 textureOut;
        uniform sampler2D tex_y;
        uniform sampler2D tex_u;
        uniform sampler2D tex_v;
        void main(void)
        {
            vec3 yuv;
            vec3 rgb;
            yuv.x = texture2D(tex_y, textureOut).r;
            yuv.y = texture2D(tex_u, textureOut).r - 0.5;
            yuv.z = texture2D(tex_v, textureOut).r - 0.5;
            rgb = mat3( 1,       1,         1,
               0,       -0.39465,  2.03211,
               1.13983, -0.58060,  0) * yuv;
            gl_FragColor = vec4(rgb, 1);
        });
*/

//回调读取数据函数指针,数据及时间戳
typedef int (*DisplayDataCK)(void ** data,int * timer_millis);


//接口初始化
int lvs_opengl_interface_init(int yuvdata_width,int yuvdata_height,
                              DisplayDataCK displaydatack,void* pview);
//接口释放
void lvs_opengl_interface_uninit();

//接口渲染数据(定时器,渲染时间,毫秒),数据及渲染定时时间在回调里面做处理
void lvs_opengl_interface_write(int value);

//渲染数据(定时器,渲染时间,毫秒),数据及渲染定时时间在回调里面做处理
void TimerFunc1(int value);   //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归??

@interface OpenGL_Display_Interface : UIView
{
@public
    EAGLContext  *m_eaglContext;                                     //opengl的view用的context,用于OpenGL 的窗口和ios的view窗口关联,很重要
    
    DisplayDataCK m_displaydatack;                                   //用于显示回调函数,参数数据及时间戳
    char * m_yuvbuf;												 //存放yuv数据的buf指针,申请buffer在外面
    int m_millis_realtime;                                           //实时的时间戳,每次回调会更新
    int m_yuvdata_width;											 //数据宽
    int m_yuvdata_height;											 //数据高
@private
    GLuint m_frameBuffer;                                            //framebuffer
    GLuint m_renderBuffer;                                           //renderbuffer
    GLuint m_textureid_y, m_textureid_u, m_textureid_v;              //纹理的名称,并且,该纹理的名称在当前的应用中不能被再次使用。
    GLuint m_textureUniformY, m_textureUniformU,m_textureUniformV;   //用于纹理渲染的变量
}

//创建用于显示的buffer
- (int)createFrameAndRenderBuffer;
//删除用于显示的buffer
- (void)destoryFrameAndRenderBuffer;

//初始化
- (int)initopengl: (int)yuvdata_width andyuvdata_height: (int)yuvdata_height anddisplaycallback: (DisplayDataCK)displaydatack;

//初始化着色器,类似于告GPU当传进去数据的时候采用什么样的规则。
- (void)InitShaders;

//具体显示图像的函数
- (int)DisplayImage: (void*)parm;

@end;


#endif

//.mm

//
//  Lvs_OpenGl_Interface_Ios.cpp
//  LvsIos_Play
//
//  Created by mx on 2016/12/7.
//  Copyright © 2016年 lvs. All rights reserved.
//

#include "Lvs_OpenGl_Interface_Ios.h"

static OpenGL_Display_Interface * copengl_interface = NULL;

int lvs_opengl_interface_init(int yuvdata_width,int yuvdata_height,
                              DisplayDataCK displaydatack,void* pview)
{
    int ret = 0;
    printf("lvs_opengl_interface_init\n");
    
    UIImageView * ppview = (UIImageView*)pview;
    
    if (copengl_interface == NULL)
    {
        copengl_interface = [[OpenGL_Display_Interface alloc]initWithFrame:CGRectMake(0, 0, ppview.frame.size.width, ppview.frame.size.height)];
        [ppview addSubview:copengl_interface];
        
        
        //初始化
        ret =[copengl_interface initopengl:yuvdata_width andyuvdata_height: yuvdata_height anddisplaycallback: displaydatack];
        if (ret != 1)
        {
            return -1;
        }
        //初始化着色器,类似于告GPU当传进去数据的时候采用什么样的规则。
        [copengl_interface InitShaders];
    }
    
    ret = 1;
    return ret;
}

void lvs_opengl_interface_uninit()
{
    printf("lvs_opengl_interface_uninit\n");
    
    if(copengl_interface != NULL)
    {
        [copengl_interface destoryFrameAndRenderBuffer];
        
        [copengl_interface release];
        copengl_interface = NULL;
    }
    return ;
}

void lvs_opengl_interface_write(int value)
{
    //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归
    TimerFunc1(value);
}

void TimerFunc1(int value)
{
    int ret = 0;
    
    //因为glut的定时器是调用一次才产生一次定时,所以如果要持续产生定时的话,
    //在定时函数末尾再次调用glutTimerFunc
    //这里如果有可能则调成类的成员函数,以后处理,暂时不知道怎么解决类成员函数递归
    
    //调用回调函数获取数据
    ret= copengl_interface->m_displaydatack((void **)&copengl_interface->m_yuvbuf,&copengl_interface->m_millis_realtime);
    if (ret > 0)
    {
        //这里做具体的处理
        [copengl_interface DisplayImage: (NULL)];
        struct timeval delay;
        delay.tv_sec = 0;
        delay.tv_usec = copengl_interface->m_millis_realtime* 1000;
        select(0, NULL, NULL, NULL, &delay);
    }
    else
    {
        struct timeval delay;
        delay.tv_sec = 0;
        delay.tv_usec = 1* 1000; // 1 ms
        select(0, NULL, NULL, NULL, &delay);
    }
    //释放yuv队列的节点buffer
    if (copengl_interface->m_yuvbuf != NULL)
    {
        free(copengl_interface->m_yuvbuf);
        copengl_interface->m_yuvbuf = NULL;
    }
    
    //递归调用自身
    TimerFunc1(0);
}


@implementation OpenGL_Display_Interface

//设置layer class 为 CAEAGLLayer 想要显示OpenGL的内容,你需要把它缺省的layer设置为一个特殊的layer。(CAEAGLLayer)。这里通过直接复写layerClass的方法。
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

//用于view初始化时候设置宽高(系统自带函数,重写)
- (id)initWithFrame:(CGRect)frame
{
    int ret = 0;
    
    if (self = [super initWithFrame:frame])
    {
        //oepngl变量
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
        //设置layer为不透明(Opaque)
        eaglLayer.opaque = YES;
        //填写渲染所需的参数
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                        //[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
                                        nil];

        m_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; //使用2.0版初始化EAGLContext
        
        if(!m_eaglContext || ![EAGLContext setCurrentContext:m_eaglContext])
        {
            return NULL;
        }
        
        //调用glViewPort函数来决定视见区域,告诉OpenGL应把渲染之后的图形绘制在窗体的哪个部位。当视见区域是整个窗体时,OpenGL将把渲染结果绘制到整个窗口。
        //glViewport(0, 0,frame.size.width,frame.size.height);//定义视口大小,说白了就是OpenGL ES窗口大小
        //[[UIScreen main] scale] == 1; //代表320 x 480 的分辨率[[UIScreen main] scale] == 2; //代表640 x 960 的分辨率[[UIScreen main] scale] == 3; //代表1242 x 2208 的分辨率
        self.contentScaleFactor = [UIScreen mainScreen].scale;
        glViewport(0, 0, frame.size.width * self.contentScaleFactor ,frame.size.height * self.contentScaleFactor); //这里不清楚为什么要乘以分辨率
    }
    return self;
}

- (int)createFrameAndRenderBuffer
{
    glGenFramebuffers(1, &m_frameBuffer);   //创建一个帧染缓冲区对象
    glGenRenderbuffers(1, &m_renderBuffer); //创建一个渲染缓冲区对象
    
    glBindFramebuffer(GL_FRAMEBUFFER, m_frameBuffer);    ////将该帧染缓冲区对象绑定到管线上
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderBuffer); //将该渲染缓冲区对象绑定到管线上
    
    
    if (![m_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer])
    {
        NSLog(@"attach渲染缓冲区失败");
    }
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderBuffer); //将创建的渲染缓冲区绑定到帧缓冲区上,并使用颜色填充
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        NSLog(@"创建缓冲区错误 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return 0;
    }
    
    return 1;
}

- (void)destoryFrameAndRenderBuffer
{
    if (m_frameBuffer){
        glDeleteFramebuffers(1, &m_frameBuffer);
    }
    
    if (m_renderBuffer){
        glDeleteRenderbuffers(1, &m_renderBuffer);
    }
    
    m_frameBuffer = 0;
    m_renderBuffer = 0;
}

- (int)initopengl: (int)yuvdata_width andyuvdata_height: (int)yuvdata_height anddisplaycallback: (DisplayDataCK)displaydatack
{

    int ret = 0;
    
    m_yuvdata_width = yuvdata_width;
    m_yuvdata_height = yuvdata_height;
    m_displaydatack = displaydatack;
    
    [self createFrameAndRenderBuffer];

    return 1;
}

- (void)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源码读取到字符串中。
    sprintf(vs,"%s",[vertexShaderString UTF8String]);
    sprintf(fs,"%s",[yuvFragmentShaderString UTF8String]);
    //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);
    if (vertCompiled == GL_FALSE)
    {
        GLchar messages[256];
        glGetShaderInfoLog(v, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"vertCompiled : %@", messageString);
        return;
    }
    glCompileShader(f);
    glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);
    if (fragCompiled == GL_FALSE)
    {
        GLchar messages[256];
        glGetShaderInfoLog(f, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"fragCompiled : %@", messageString);
        return;
    }
    
    //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)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()的唯一参数表示需要被清除的缓冲区。
    [EAGLContext setCurrentContext:m_eaglContext];
    glClearColor(0.0,0.0,0.0,1.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_LUMINANCE, m_yuvdata_width, m_yuvdata_height, 0, GL_LUMINANCE, 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_LUMINANCE, m_yuvdata_width/2, m_yuvdata_height/2, 0, GL_LUMINANCE, 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_LUMINANCE, m_yuvdata_width/2, m_yuvdata_height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, yuvplaner[2]);
    glUniform1i(m_textureUniformV, 2);
    
    // Draw
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderBuffer);
    [m_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
    
    return 1;
}

@end;


本demo还需完善。

如有错误请指正:

交流请加QQ群:62054820
QQ:379969650.



猜你喜欢

转载自blog.csdn.net/zhuweigangzwg/article/details/53927750