OpenGL.ES在Android上的简单实践:17-水印录制(认识Android的EGL)

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

OpenGL.ES在Android上的简单实践:

17-水印录制(认识Android的EGL)

 

-1、先吹下水,装下*

距离上一个主题有一段时间了,最近都在忙工作,因为需要在Android上写C++,所以就复习了一些NDK的知识。打算之后也会整理并写下这一块的博客,把一些复杂的code记录在案方便学习(其中包括webrtc,简单的ffmpeg,AAC,H264都会涉及介绍)正所谓好记性不如烂笔头嘛。。。言归正传,OpenGL.ES在Android上的简单实践专栏,经历过两个课题项目之后,我相信大家已经有一定的基础,有朋友已经不满足这些基础知识了,最近提出要来干货猛料了!所以这里拟定下一个主题吧,就是时下最火的 短视频!顺带一些滤镜效果?顺带视频的录制?方便的话使用硬件编解码?反正就是结合手机摄像头开发项目了,希望自己能成功做好吧。曾经某位牛人如是说道:“不强迫一下自己,怎么知道自己有这么犀利呢?”

好了,逼差不多也该装完了,既然定了下个项目的课题,这里就来分析一下重点和难点,顺带引出这次文章的内容。短视频+滤镜,我们需要什么?

1、打开摄像头,并能实时的渲染,并呈现出现用户的视图界面上。

2、增加滤镜,各种滤镜效果切换并不影响渲染的输出。

3、最后滤镜和视频的合成要高效,低耗。最好还能转成成熟的编码(h264/h265)方式方便传输。

以上分析后,大家看见“编码”“渲染视频”可能会立马想到ffmpeg等成熟的视频库。但,这里真的是需要使用ffmpeg吗?滤镜是通过shader着色器,最后经过OpenGL渲染出来的!所以我们还是要在Android的OpenGL.ES写Code啊~ 那么Android的OpenGL.ES是怎么做到实时的摄像头预览?还有各种滤镜效果的切换? 在深入这些问题之前,我们不妨花点时间来搞懂Android的OpenGL.ES,接下来要详细介绍的 EGL!

 

 

0、认清Android的一些概念

在开始之前,我想大家先搞清Android平台上的一些概念。以下三个大问题,如果看官确切自己很清楚的搞懂明白,那就调整这部分吧。

第一个比较简单:Android中的Activity、Window、View之间的关系。

第二个稍微复杂:GLSurfaceView, SurfaceView, TextureView三者的区别,SurfaceTexture, Surface两者的区别。

第三个是最玄学:第一个问题 和 第二个问题之间的对象,有啥关联?

我这里就写出大白话,不清楚的详细请参阅超连接(超链接都是深读好文)

1、Activity包含着PhoneWindow(1:1),然后PhoneWindow再包含着DecorView(1:1),DecorView包裹着ViewGroup附着我们setContentview上的所有子view(1:N)

2、SurfaceView是一个穿了个洞到Window的View,GLSurfaceview=OpenGL+SurfaceView,TextureView把SurfaceView的洞补上了,不再直穿到PhoneWindow,而是用OpenGL纹理的概念,把内容流直接投影到View。

2.2、Surface就是那个洞,SurfaceTexture就是那个OpenGL概念纹理的对象。

3、经过1~2的介绍之后,有什么关联,我想大家已经有个概念了。在SurfaceView、GLSurfaceview上,我们实际是用Surface来渲染画面;在TextureView上,我们是用SurfaceTexture。

 

1、认识EGL

所以什么是EGL呢?其实在第一篇文章:OpenGL.ES在Android上的简单实践:1-曲棍球,我们就接触过其概念(注意下部分的灰色字体段落)我们现在再来认真看看OpenGL团队的官方介绍:

EGL™ is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system. It handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2D and 3D rendering using other Khronos APIs. EGL also provides interop capability between Khronos to enable efficient transfer of data between APIs – for example between a video subsystem running OpenMAX AL and a GPU running OpenGL ES.

下划线是重点,翻译出来意思就是:EGL是KHONOS公司的渲染API(如OpenGL ES或OpenVG)与底层窗口系统之间的通信接口。它处理图形上下文管理、设备显示/缓冲器绑定和渲染同步,并使用其他KHRONOS API实现高性能、加速、混合模式的2D和3D渲染。还记得之前介绍的OpenGL.ES的跨平台?结合这里通俗一点讲就是:在各种窗口系统上,只要符合定义的标准,并实现其运行所需要的环境EGL,我们就能愉快的使用渲染API了!

那么问题又来了,为什么我们之前没有什么创建EGL的步骤? 还记得第一篇文章介绍基本环境的时候,调用GLSurfaceView.setEGLContextClientVersion(2)设置运行版本为2。其实Android已经帮我们把EGL的环境准备好,并封装在GLSurfaceView内部了,可以这么理解GLSurfaceView = SurfaceView + EGL + GL渲染线程。接下来带大家一并通读一下GLSurfaceView的源代码,如果希望能查阅详尽的源代码,可以参照这位作者前部分的代码分析,至于后部分的工具提取就不敢恭维了,博主自己都说有问题,往后我也会提供一个更完善更科学的工具类。废话不多说,上GLSurfaceView的主要构成代码

GLSurfaceView主要构成 extends SurfaceView (主要提供渲染Surface)
{
    //调试用的
    public void setDebugFlags(int debugFlags);
    public int getDebugFlags();

    //设置暂停的时候是否保持EglContext
    public void setPreserveEGLContextOnPause(boolean preserveOnPause);
    public boolean getPreserveEGLContextOnPause();

    //设置渲染器,这个非常重要,渲染工作就依靠渲染器了
    //调用此方法会开启一个新的线程,即GL线程
    public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (mEGLConfigChooser == null) {
            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (mEGLContextFactory == null) {
            mEGLContextFactory = new DefaultContextFactory();
        }
        if (mEGLWindowSurfaceFactory == null) {
            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        mRenderer = renderer;
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }

    //设置EGLContext工厂,不设置就用默认的
    public void setEGLContextFactory(EGLContextFactory factory);

    //设置EGLSurface工厂,不设置就用默认的
    public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory);

    //设置EglConfig,一般颜色深度等等,利用此方法设置。不设置就用默认的
    public void setEGLConfigChooser(EGLConfigChooser configChooser);

    //内部调用setEGLConfigChooser
    public void setEGLConfigChooser(boolean needDepth);

    //内部调用setEGLConfigChooser
    public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
            int alphaSize, int depthSize, int stencilSize);

    //设置EGLContextVersion,比如2,即OpenGLES2.0
    public void setEGLContextClientVersion(int version);

    //设置渲染方式,有RENDERMODE_CONTINUOUSLY表示不断渲染
    //以及RENDERMODE_WHEN_DIRTY表示在需要的时候才会渲染
    public final static int RENDERMODE_WHEN_DIRTY = 0;
    public final static int RENDERMODE_CONTINUOUSLY = 1;
    //渲染的时候要求调用requestRender,必须在setRenderer后调用
    public void setRenderMode(int renderMode);
    public int getRenderMode();
    public void requestRender();

    public void surfaceCreated(SurfaceHolder holder);
    public void surfaceDestroyed(SurfaceHolder holder);
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h);

    @Override
    public void surfaceRedrawNeeded(SurfaceHolder holder) {
        if (mGLThread != null) {
            mGLThread.requestRenderAndWait();
        }
    }

    //生命周期,一般在Activity、Fragment的onPause中调用
    public void onPause();
    //生命周期,一般在Activity、Fragment的onResume中调用
    public void onResume();
    //向GL线程发送一个任务
    public void queueEvent(Runnable r);
    //附加到Window上时被调用,外部不可调用
    protected void onAttachedToWindow();
    //从Window上被移除时调用,外部不可调用
    protected void onDetachedFromWindow();

    //渲染器接口
    public interface Renderer {
        //Surface被创建时被调用,通常在此进行渲染的初始化
        void onSurfaceCreated(GL10 gl, EGLConfig config);
        //Surface大小被改变时被调用
        void onSurfaceChanged(GL10 gl, int width, int height);
        //执行渲染时被调用,以完成用户渲染工作
        void onDrawFrame(GL10 gl);
    }


    //非常重要的一个EGL帮助类,GL环境的建立依靠此类
    private static class EglHelper {
        public EglHelper(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) {
            mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
        }
        private WeakReference<GLEnvironment> mGLSurfaceViewWeakRef;
        EGL10 mEgl; // EGL 对象
        EGLDisplay mEglDisplay;// 实际显示物理设备的抽象。
        EGLSurface mEglSurface;// 显示设备用来存储图像的内存区域 抽象
        EGLConfig mEglConfig;  // EGL配置 抽象
        EGLContext mEglContext;// EGL-GL绘制API 管理上下文

        public void start() //主要创建Egl,EglDisplay,EglContext,还有EglConfig
        public boolean createSurface()//创建EglSurface
        GL createGL() //通过EGL得到GL,然后用户设置了Wrapper的话会给得到的GL做个包装
        //同时也会解析一下用户的Debug意图,看看要不要debug
        public int swap(); //gl双缓冲机制接口,每一次swap,显示缓冲区 和 显示缓冲区就会互换
        public void destroySurface();//销毁Surface的方法,具体实现在destroySurfaceImp方法中
        private void destroySurfaceImp()//使用GLEnvironment的EGLWindowSurfaceFactory进行销毁
        public void finish();//销毁GL环境
    }


    //GL线程,此类中存在的方法,GLSurfaceView中有同名的,
    //基本都是提供给GLSurfaceView作为真正的实现调用
    static class GLThread extends Thread {
        //销毁EglSurface
        private void stopEglSurfaceLocked();
        //销毁EglContext
        private void stopEglContextLocked();

        //GL线程的主要逻辑都在这个方法里面,这个方法比较复杂
        //GLSurfaceView的核心就在这个里面了,最后在单独分析这个里面的逻辑
        private void guardedRun() throws InterruptedException;

        public boolean ableToDraw() {
            return mHaveEglContext && mHaveEglSurface && readyToDraw();
        }
        private boolean readyToDraw() {
            return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
                && (mWidth > 0) && (mHeight > 0)
                && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
        }

       //GLSurfaceView对应实际方法
       public void setRenderMode(int renderMode);
       public int getRenderMode();
       public void requestRender();
       public void surfaceCreated();
       public void surfaceDestroyed();
       public void onPause();
       public void onResume();
       //请求一次渲染,并等待渲染完成
       public void requestRenderAndWait();
       //Surface的大小被改变时调用
       public void onWindowResize(int w, int h);
       //请求退出渲染线程,并等待退出
       public void requestExitAndWait();
       //请求是否EglContext
       public void requestReleaseEglContextLocked();
       //向GL线程发送一个任务
       public void queueEvent(Runnable r);
    }

    //debug使用的
    static class LogWriter extends Writer { 。。。}

    //很多方法都会调用此方法,会检查mGLThread不为null
    //即保证调用此方法的方法,必须在setRenderer之前调用
    private void checkRenderThreadState();

    //主要就是用来做同步用的,利用Object的wait和notifyAll
    private static class GLThreadManager {。。。}

}//End GLSurfaceView基本结构如上

看完这串结构注释,肯定是还不太能理解究竟是怎么回事?!大家可以使用编译器打开GLSurfaceview,跟着注释分析。

1、首先,我们要记住几个关键的组成部分:GLThread,EglHelper,和提供渲染介质的SurfaceView。辅助类GLThreadManager。主要就是用来渲染线程和SurfaceView做同步用的。
2、然后我们再从使用步骤入手,我们得到一个GLSurfaceview对象后,最紧要的步骤是什么?就是setRenderer。我们看看setRenderer做了什么?构建EGLConfigChooser,DefaultContextFactory,DefaultWindowSurfaceFactory。从名字我们就知道了都是为了创建EGL相关的东西做前期的准备。并启动了GL的渲染线程GLThread。
3、继续下去,GLThread的run中的关键 guardedRun(); 由于篇幅的关系,就不把guardedRun的代码贴上来了,大家可以开着源码进入这个方法跟着一起分析。进入guardedRun第一件事就是new EglHelper,初始各种标志位,然后进入while(true)渲染死循环中,所以我们的setRenderer方法只能调用一次就是这个原因了;然后经过一轮的同步标志位判断后,正戏终于要到->mEglHelper.start();
4、终于涉及EGL的部分了,mEglHelper.start();的代码不多,我们这里贴上:

        public void start() {
            // Pre1、说是EGLImpl,其实就是硬件设备相关的jni接口
            mEgl = (EGL10) EGLContext.getEGL();  // new com.google.android.gles_jni.EGLImpl()
            // 1、获取EGLDisplay对象
            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
                throw new RuntimeException("eglGetDisplay failed");
            }
            // 2、初始化与EGLDisplay 之间的连接。
            int[] version = new int[2];
            if(!mEgl.eglInitialize(mEglDisplay, version)) {
                throw new RuntimeException("eglInitialize failed");
            }
            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view == null) {
                mEglConfig = null;
                mEglContext = null;
            } else {
                // 3、获取EGLConfig对象
                mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
                // 4、创建EGLContext 实例
                mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
            }
            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
                mEglContext = null;
                throwEglException("createContext");
            }
            // Pre5、等候EGLSurface实例的创建
            mEglSurface = null;
        }

注意注释的顺序步骤,在sGLThreadManager的同步下,我们完成了mEglHelper.start(); 经过start后,我们已经初步建立了EGL,但是这个EGL还没能正常的工作,为啥子呢?因为它还不知道要与那个渲染面绑定工作啊!(注释Pre5步骤)我们回到guardedRun(); 结束了EGL.start之后,我们继续往下不难发现createEglSurface的标志部分代码,其中里面就是调用mEglHelper.createSurface()!!!我们赶紧看看。
5、代码不多,贴上源码和注释,注意跟上EglHelper.start的顺序

        public boolean createSurface() {
            if (mEgl == null) {
                // in start Pre1
                throw new RuntimeException("egl not initialized");
            }
            if (mEglDisplay == null) {
                // in start 1 2
                throw new RuntimeException("eglDisplay not initialized");
            }
            if (mEglConfig == null) {
                // in start 3
                throw new RuntimeException("mEglConfig not initialized");
            }
            // 如果渲染界面大小发生变化,我们也必须要重新创建一个新的EGLSurface,
            // 所以不管什么情况,都先尝试销毁之前的EGLSurface,保证其唯一性
            destroySurfaceImp();

            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
            if (view != null) {
                // 5、EGLSurface实例的创建
                mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
                        mEglDisplay, mEglConfig, view.getHolder());
            } else {
                mEglSurface = null;
            }
            // 检查 EGLSurface是否创建成功
            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
                int error = mEgl.eglGetError();
                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
                }
                return false;
            }

            // 把EGLContext和EGLSurface连接起来. 
            // EGLContext在start的时候已经和EGLDisplay、EGL绑定了,所以这里EGLContext把EGL EGLSurface绑定在一起
            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
                logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
                return false;
            }

            return true;
        }

在这里我们可以看到步骤Pre5的后续操作,EGLSurface是靠EGLWindowSurfaceFactory.createWindowSurface创建出来的,而EGLWindowSurfaceFactory是在setRenderer的时候new出来的,所以我们要是想自定义渲染面,就可以从这一步入手。
6、我们继续回到guardedRun,经过createEglSurface的操作后,创建GL的包装对象用于回调的回传,然后开始的按条件陆续回调renderer的三大接口onSurfaceCreated/onSurfaceChanged/onDrawFrame,注意每次onDrawFrame后,EglHelper都会swap()切换渲染面和显示面。 就这样,guardedRun的渲染循环基本分析完毕。
7、在guardedRun跳出渲染循环之后,我们在trycatch的finally部分可以看到,就是销毁EGLSurface、EGLContext和断开EGLDisplay的关联。

 

 

请允许我为以上内容做一下下总结
          EGLDisplay :(是对实际显示设备的抽象)
          EGLSurface :(是对用来存储图像的内存区域的抽象,包括Color Buffer, Stencil Buffer ,Depth Buffer)
          EGLContext :(存储OpenGL.ES绘图的一些状态信息。管理上述两者关联的状态)

创建EGL过程,开始正常绘图的流程步骤:
          1、获取EGLDisplay对象
          2、初始化与EGLDisplay之间的关联。
          3、获取EGLConfig对象
          4、创建EGLContext 实例
          5、创建EGLSurface实例
          6、连接EGLContext和EGLSurface.
(以上封装在GLSurfaceview,对使用者透明)
          7、使用GL指令绘制图形         <—— renderer三大回调 渲染死循环。
(以下也是在GLSurfaceview,对使用者透明)
          8、断开并释放与EGLSurface关联的EGLContext对象
          9、删除EGLSurface对象
          10、删除EGLContext对象
          11、终止与EGLDisplay之间的连接。

 

 

The End .

 

展开阅读全文

没有更多推荐了,返回首页