美文网首页程序员Open GL
opengl es 绘制视频 使用原生代码

opengl es 绘制视频 使用原生代码

作者: 子雷 | 来源:发表于2017-04-17 23:17 被阅读823次

    前言

    像正真做这种关于opengl 的开发,一般建议都是直接使用原生代码,而不是使用android已经封装好的opengl代码, 对于像opengl这么偏底层的图形接口是很有必要去深入到native层去一趟究竟的。
    写作本文的目的也只是与大家一起去学习与交流,还是那句话(talk is cheap,show me the code),如果不想听我在这里瞎bb,尽可将我已经写好的DEMO copy下来,然后运行起来,像看看效果, 这比说一大堆废话管用。此代码主要参考了oculus mobile sdk, 我只是将其中一部分代码裁剪出来,再自己加以修改,来作为案例学习之用,仅供大家参考。

    环境

    • 构建环境

    简单说说我的开发环境,首先NDK开发工具包必不可少,目前还是采取通用的Android.mk 与 Application.mk 这样的Mikefile 文件进行构建,这种方式网上有大量教程,建议去google developer 去查阅用法, ** 有一点需要注意下**,在Application.mk 中的NDK_TOOLCHAIN_VERSION 的版本是随你的ndk版本而定的,比如我ndk版本是r10e,这里用的toolchan版本是4.8,如果你用的其它ndk版本,那么你需要改一下toolchan的版本号。 google的新的ndk demo中,已经采取直接将ndk的配置写在gradle脚本里面来代替现在这种传统的配置了。

    • 绘制环境
      我这里是通过调用opengl es api函数向java层提供的GLSurfaceView输出并渲染图像。也可以使用NDK提供的原生window api 去进行渲染图像。
    1. 1 执行下面步骤以在原生代码使用opengl es 2.0/3.0 的API:
    • 修改Android.mk构建文件。添加 LOCAL_LDLIBS += -lGLESv2 -lGLESv3
    • 代码中包含opengl es头文件。 #include<GLES3/gl3.h> #include<GLES2/gl2ext.h> , 我是用的gles 3.0的库,因为2.0 是没有我需要用到的 glGenVertexArrays API的, 不过绘制视频需要binding使用gl2ext库提供的GL_TEXTURE_EXTERNAL_OES 作为图像映射的纹理目标有的人可能会问,为什么不是一般常用的GL_TEXTURE_2D 呢? 其实原因在于SurfaceTexture,我查了下SurfaceTexture源码, 发现它内部使用的是eglCreateImageKHR去创建的image, 而这个api必须使用TEXTURE_EXTERNAL_OES,并且用了TEXTURE_EXTERNAL_OES 就不能使用TexImage2D等系列接口去生成纹理image,所以说,SurfaceTexture类的功能是起到替代opengl 的TexImageXX接口的作用, 因此视频图像的捕获与图像更新还是用的java层封装好的SurfaceTexture , 那么这就涉及到了naive代码与java代码的交互。
    1. 2 Native代码与Java的交互
      一个简单的步骤:
      2.1 通过随意一个JNIEnv指针去获取一个JavaVM在jni层的一个指针,注意,一个应用代表一个javaVM。但JNIEnv指针在不同的线程中有不同JNIEnv
      JavaVM *gJavaVM; //// 通常是设置的一个全局的对象 env->GetJavaVM(&gJavaVM);
      2.2 获取JavaVM在opengl的代表JNIEnv指针。由于我们需要在opengl 绘制线程 的上下文 中与javaVM交互,因此JNIEnv指针的获取必须在opengl上下文中。
      具体代码:
    JNIEnv *AttachJava()
    {      
            JavaVMAttachArgs args = {
            JNI_VERSION_1_4,   // 表示jni版本号,还有1_1,1_2, 1_6这几种
            0,    /* NULL or name of thread as modified UTF-8 str */
            0    /* global ref of a ThreadGroup object, or NULL */
    }; 
        JNIEnv* jni;
        int status = gJavaVM->AttachCurrentThread( &jni, &args);
        if (status < 0) {
            LOG_ERROR("<SurfaceTexture> faild to attach current thread!");
            return NULL;
        }
        return jni;
    }
    

    2.3 在native代码中,通过jni创建一个运行在javaVM环境中的SurfaceTexture的实例 jobject

    const char *stClassPath = "android/graphics/SurfaceTexture";
        const jclass surfaceTextureClass = jni->FindClass(stClassPath);
        if (surfaceTextureClass == 0) {
            LOG_ERROR("FindClass (%s) failed", stClassPath);
        }
    
    //    // find the constructor that takes an int
        const jmethodID constructor = jni->GetMethodID( surfaceTextureClass, "<init>", "(I)V" );
        if (constructor == 0) {
            LOG_ERROR("GetMethonID(<init>) failed");
        }
    
        jobject  obj = jni->NewObject(surfaceTextureClass, constructor, texId);
        if (obj == 0) {
            LOG_ERROR("NewObject() failed");
        }
    
        javaSurfaceTextureObj = jni->NewGlobalRef(obj);
        if (javaSurfaceTextureObj == 0) {
            LOG_ERROR("NewGlobalRef() failed");
        }
    
        //Now that we have a globalRef, we can free the localRef
        jni->DeleteLocalRef(obj);
    

    这个过程等价于在java代码中new 一个SurfaceTexture':surfaceTexture = new SurfaceTexture(textId)。其实,现在我们只是将SurfaceTexture的创建工作从Java层搬到了Native层,其余的操作莫过于下面这个几个动作了:
    在native中实现 像在java代码做的下面这个几个动作,实现视频帧的捕获与更新:

      surfaceTexture .setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                @Override
                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                    frameAvailable = ture;
                }
            });
    
    // 在绘制线程循环中执行下面操作
       if (frameAvailable) {
                    surfaceTexture .updateTexImage();
                    surfaceTexture .getTransformMatrix(textureTransform);
                    frameAvailable = false;
                }
    

    在native中

    1. 获取updateTexImage方法的MethodID,updateTexImageMethodId = jni->GetMethodID( surfaceTextureClass, "updateTexImage", "()V")
    2. 然后 在需要调用这个方法时,通过这个MethodID,找到这个方法,使用jni->CallVoidMethod(javaSurfaceTextureObj, updateTexImageMethodId), 从而实现了native调用java的非静态方法。
      ok, opengl进行纹理映射绑定的的texture已经有了,那么下面就是图像的渲染工作了。

    图形渲染

    看过我在DEMO 中的关于java代码中使用opengl的步骤可以看出绘制的套路来:

    1. 创建一个与shader代码链接的shader program 对象。

    program = createProgram(vextexShader, fragmentShader);

    1. 获取vertex shader代码中声明的属性变量的 handle。

    因为顶点带有传输到图形管线上的属性。要绘制一个对象,我们需要指定它的顶点,以及顶点如何定义平面。这些属性包括顶点,法线,纹理坐标以及其它属性。
    positionHandle = glGetAttribLocation(shaderProgram, "aPosition");
    textureParamHandle = glGetUniformLocation(shaderProgram, "texture");

    1. 初始化顶点数据。

    (包括顶点坐标,纹理坐标,以及数组的一个索引(因为这里使用了glDrawElements)。

    // 声明一个结构体,保存顶点数据
     struct Vertices
        {
            float positions[4][4];
            float texCoords[4][4];
        };
    // 指定顶点连接顺序
     unsigned short indices[] = {
          0, 1, 2,  0, 2 ,3
        };
    
    1. 通过这些顶点数据来填充vertex shader中的属性。

    这里用到了VBO去缓存顶点数据(也就是将主存储客户端(CPU端)中保存的顶点数据,缓存到GPU中, 提高了GPU渲染效率), 每一组缓冲区就是 OpenGL 的顶点数组对象,叫做VAO。

    glGenVertexArrays( 1, &vao );
    glBindVertexArray( vao );
    glGenBuffers( 1, &vb );
    glBindBuffer( GL_ARRAY_BUFFER, vb );
    glBufferData( GL_ARRAY_BUFFER, sizeof( vertices ), &vertices, GL_STATIC_DRAW );
    glGenBuffers( 1, &ib ) ;
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ib );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( indices ), indices, GL_STATIC_DRAW );
    
    glEnableVertexAttribArray ( positionHandle );
    glVertexAttribPointer ( positionHandle, 3, GL_FLOAT, false, 0, (const GLvoid *)offsetof( Vertices, positions ));
    glEnableVertexAttribArray ( textureCoordHandle );
    glVertexAttribPointer ( textureCoordHandle, 4, GL_FLOAT, false, 0, (const GLvoid *)offsetof( Vertices, texCoords ));
    
    1. OpenGL 渲染缓冲区中的数据。

    我们可以使用下面的代码来让 VAO 包含索引缓冲区:

    glUseProgram(program);
    glBindVertexArray(vao);
    glDrawElements(GL_TRIANGLES, index.size(), GL_UNSIGNED_INT, NULL); 
    

    ok, opengl 绘制图形的大致套路就是这样。详情请参考源码!

    总结

    由于才疏学浅,本文只起到一个抛砖引玉的作用, 简单的描述了一下native代码中使用opengl的列子,只在场景中画出了一个简单的长方行贴图来显示图像。如果 深入一点,做一个球形贴图,然后再通过一连串的矩阵运算(MVP),就可以做个全景视频了。说实话,在工作中是很难有机会写到这么原始opengl代码,我都已经很久没有写过opengl相关代码了,现在基本就是在别人已经封装好的代码基础上写写业务逻辑,要不是因为一年前写的几篇文字,引起这么多人的关注,可能我都很难再把以前撸过的代码再重温一遍来跟大家一起分享,感谢大家的关注!

    相关文章

      网友评论

        本文标题:opengl es 绘制视频 使用原生代码

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