-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 .