Android音视频 - EGL源码解析以及C++实现

PS
我们在前面的文章中就说过关于EGL的出现原因以及其作用

OpenGL 是一个跨平台的API,而不同的操作系统(Windows,Android,IOS)各有自己的屏幕渲染实现。所以OpenGL定义了一个中间接口层EGL(Embedded Graphics Library)标准,具体实现交给各个操作系统本身

EGL

简单来说EGL是一个中间接口层,是一个规范,由于OpenGL的跨平台性,所以说这个规范显得尤其重要,不管各个操作系统如何蹦跶,都不能脱离我所定义的规范。

EGL的一些基础知识

  • EGLDisplay

EGL定义的一个抽象的系统显示类,用于操作设备窗口。

  • EGLConfig

EGL配置,如rgba位数

  • EGLSurface

渲染缓存,一块内存空间,所有要渲染到屏幕上的图像数据,都要先缓存在EGLSurface上。

  • EGLContext

OpenGL上下文,用于存储OpenGL的绘制状态信息、数据。

初始化EGL的过程可以说是对上面几个信息进行配置的过程

OpenGL ES绘图完整流程

我们在使用Java GLSurfaceView的时候其实只是自定义了Render,该Render实现了GLsurfaceView.Renderer接口,然后自定义的Render中的3个方法就会得到回调,Android 系统其实帮我省掉了其中的很多步骤。所以我们这里来看一下完整流程
(1). 获取显示设备(对应于上面的EGLDisplay)

/*
 * Get an EGL instance */
 mEgl = (EGL10) EGLContext.getEGL();
 
/*
 * Get to the default display. */
 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

(2). 初始化EGL

int[] version = new int[2];
//初始化屏幕
if(!mEgl.eglInitialize(mEglDisplay, version)) {
    throw new RuntimeException("eglInitialize failed");
}

(3). 选择Config(用EGLConfig配置参数)

//这段代码的作用是选择EGL配置, 即可以自己先设定好一个你希望的EGL配置,比如说RGB三种颜色各占几位,你可以随便配,而EGL可能不能满足你所有的要求,于是它会返回一些与你的要求最接近的配置供你选择。
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
 num_config)) {
    throw new IllegalArgumentException("eglChooseConfig#2 failed");
}

(4). 创建EGLContext

//从上一步EGL返回的配置列表中选择一种配置,用来创建EGL Context。
egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
 mEGLContextClientVersion != 0 ? attrib_list : null);

(5). 获取EGLSurface

//创建一个窗口Surface,可以看成屏幕所对应的内存
 egl.eglCreateWindowSurface(display, config, nativeWindow, null)

PS
这里的nativeWindow是GLSurfaceView的surfaceHolder

(6). 绑定渲染环境到当前线程

/*
 * Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */
 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
    /*
    * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ 
     logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
     return false;
 }

循环绘制

loop:{
    //绘制中....
    //(7).交换缓冲区
    mEglHelper.swap();
}

public int swap() {
    if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
        return mEgl.eglGetError();
    }
    return EGL10.EGL_SUCCESS;
}

Java - GLSurfaceView/GLTextureView

上面我们介绍了EGL的一些基础知识,接着我们来看在GLSurfaceView/GLTextureView中EGL的具体实现,我们来从源码上剖析Android系统EGL及GL线程

GLThread

我们来看一下GLThread,GLThread也是从普通的Thread类继承而来,理论上就是一个普通的线程,为什么它拥有OpenGL绘图能力?继续往下看,里面最重要的部分就是guardedRun()方法

static class GLThread extends Thread {
    ...
    @Override
    public void run() {
      
        try {
                guardedRun();
         } catch (InterruptedException e) {
                // fall thru and exit normally
         } finally {
                sGLThreadManager.threadExiting(this);
         }
    }
}

让我们来看一下guardedRun()方法里有什么东西,guardedRun()里大致做的事情:

private void guardedRun() throws InterruptedException {
    while(true){
        //if ready to draw
        ...
        mEglHelper.start();//对应于上面完整流程中的(1)(2)(3)(4)
        
        ...
        mEglHelper.createSurface()//对应于上面完整流程中的(5)(6)
        
        ...
        回调GLSurfaceView.Renderer的onSurfaceCreated();
        ...
        回调GLSurfaceView.Renderer的onSurfaceChanged();
        ...
        回调GLSurfaceView.Renderer的onDrawFrame();
        
        ...
         mEglHelper.swap();//对应于上面完整流程中的(5)(7)
    }
}

从上面我们的分析得知GLSurfaceView 中的GLThread就是一个普通的线程,只不过它按照了OpenGL绘图的完整流程正确地操作了下来,因此它有OpenGL的绘图能力。那么,如果我们自己创建一个线程,也按这样的操作方法,那我们也可以在自己创建的线程里绘图吗?答案是肯定的(这不正是EGL的接口意义),下面我会给出EGL在Native C/C++中的实现。

Native - EGL

Android Native环境中并不存在现成的EGL环境,所以我们在进行OpenGL的NDK开发时就必须自己实现EGL环境,那么如何实现呢,我们只需要参照GLSurfaceView中的GLThread的写法就能实现Native中的EGL

PS
一下的内容可能需要你对C/C++以及NDK 有一定熟悉

第1步实现类似于Java GLSurfaceView中的GLThread的功能

gl_render.h

class GLRender {
    private:
         const char *TAG = "GLRender";
         //OpenGL渲染状态
         enum STATE {
             NO_SURFACE, //没有有效的surface
             FRESH_SURFACE, //持有一个为初始化的新的surface
             RENDERING, //初始化完毕,可以开始渲染
             SURFACE_DESTROY, //surface销毁
             STOP //停止绘制
         };
         JNIEnv *m_env = NULL;
         //线程依附的jvm环境
         JavaVM *m_jvm_for_thread = NULL;
         //Surface引用,必须要使用引用,否则无法在线程中操作
         jobject m_surface_ref = NULL;
         //本地屏幕
         ANativeWindow *m_native_window = NULL;
         //EGL显示表面
         EglSurface *m_egl_surface = NULL;
         int m_window_width = 0;
         int m_window_height = 0;
         
         // 绘制代理器
         ImageRender *pImageRender;
         
         //OpenGL渲染状态
         STATE m_state = NO_SURFACE;
         // 初始化相关的方法
         void InitRenderThread();
         bool InitEGL();
         void InitDspWindow(JNIEnv *env);
         // 创建/销毁 Surface void CreateSurface();
         void DestroySurface();
         // 渲染方法
         void Render();
         void ReleaseSurface();
         void ReleaseWindow();
         // 渲染线程回调方法
         static void sRenderThread(std::shared_ptr<GLRender> that);
    public:
         GLRender(JNIEnv *env);
         ~GLRender();
         //外部传入Surface
         void SetSurface(jobject surface);
      
         void Stop();
         void SetBitmapRender(ImageRender *bitmapRender);
        // 释放资源相关方法
         void ReleaseRender();
         
         ImageRender *GetImageRender();
};

gl_render.cpp

//构造函数
GLRender::GLRender(JNIEnv *env) {
     this->m_env = env;
     //获取JVM虚拟机,为创建线程作准备
     env->GetJavaVM(&m_jvm_for_thread);
     InitRenderThread();
}
//析构函数
GLRender::~GLRender() {
    delete m_egl_surface;
}

//初始化渲染线程
void GLRender::InitRenderThread() {
    // 使用智能指针,线程结束时,自动删除本类指针
     std::shared_ptr<GLRender> that(this);
     std::thread t(sRenderThread, that);
     t.detach();
}

//线程回调函数
void GLRender::sRenderThread(std::shared_ptr<GLRender> that) {
    JNIEnv *env;
     //(1) 将线程附加到虚拟机,并获取env
     if (that->m_jvm_for_thread->AttachCurrentThread(&env, NULL) != JNI_OK) {
            LOGE(that->TAG, "线程初始化异常");
            return; 
     }
     // (2) 初始化 EGL 
    if (!that->InitEGL()) {
         //解除线程和jvm关联
         that->m_jvm_for_thread->DetachCurrentThread();
         return; 
     }
     
     //进入循环
    while (true) {
            //根据OpenGL渲染状态进入不同的处理
            switch (that->m_state) {
                //刷新Surface,从外面设置Surface后m_state置为该状态,说明已经从外部(java层)获得Surface的对象了
                case FRESH_SURFACE:
                     LOGI(that->TAG, "Loop Render FRESH_SURFACE")
                     // (3) 初始化Window
                     that->InitDspWindow(env);
                     // (4) 创建EglSurface
                     that->CreateSurface();
                     // m_state置为RENDERING状态进入渲染
                     that->m_state = RENDERING;
                     break; 
                 case RENDERING:
                    LOGI(that->TAG, "Loop Render RENDERING")
                    // (5) 渲染
                    that->Render();
                    break; 
               
                 case STOP:
                    LOGI(that->TAG, "Loop Render STOP")
                    //(6) 解除线程和jvm关联
                     that->ReleaseRender();
                     that->m_jvm_for_thread->DetachCurrentThread();
                     return; 
                case SURFACE_DESTROY:
                    LOGI(that->TAG, "Loop Render SURFACE_DESTROY")
                    //(7) 释放资源
                    that->DestroySurface();
                    that->m_state = NO_SURFACE;
                    break; 
                case NO_SURFACE:
                default:
                    break;
     }
    usleep(20000);
 }
}

我们定义的GLRender各个流程代码里已经标注了步骤,虽然代码量比较多,但是我们的c++ class分析也是跟java类似,
在这里插入图片描述

PS
上图中的(3)(4)等步骤对应于代码中的步骤注释

小结

本篇文章我们介绍了EGL,以及分析了Java 中GLSurfaceView中的EGL实现,然后我们试着参照Java端的代码实现Native中的EGL环境,关于代码以及流程图中的细节,我们下篇再来分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
To compile Wayland-EGL in Yocto, you can follow these steps: 1. Add the necessary layers to your Yocto build. This may include the meta-openembedded layer, which contains the recipes for Wayland and other related packages. 2. Configure your build to include the Wayland-EGL packages. You can do this by adding the following lines to your local.conf file: ``` IMAGE_INSTALL_append = " wayland-protocols" IMAGE_INSTALL_append = " wayland-egl" ``` This will ensure that the Wayland-Protocols and Wayland-EGL packages are included in your Yocto image. 3. Build your Yocto image. You can do this by running the `bitbake` command with the name of your image, such as: ``` bitbake core-image-minimal ``` This will compile the necessary packages and dependencies, and generate a Yocto image that includes Wayland-EGL. 4. Configure your system to use Wayland-EGL as the default display server. You can do this by modifying the `weston.ini` file in your image's root filesystem. Add the following lines to the file: ``` [core] modules=eglbackend.so,desktop-shell.so [shell] background-image=/usr/share/backgrounds/gnome/Aqua.jpg panel-location=none ``` This will configure the Weston compositor to use EGL as the backend for rendering graphics. 5. Boot your Yocto image and test Wayland-EGL. You can do this by running a Wayland client application, such as `weston-terminal` or `weston-flower`. If everything is configured correctly, you should see the application window rendered using EGL. These are the basic steps for compiling Wayland-EGL in Yocto. However, the exact steps may vary depending on your specific Yocto setup and hardware platform. It's recommended to consult the Yocto documentation and community resources for more detailed instructions and troubleshooting tips.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值