基于Android P版本分析
在分析使用OpenGL ES进行预览&录像之前,首先先区分一下GLSurfaceView和TextureView;
GLSurfaceView
一般情况下,OpenGL ES都是配合GLSurfaceView来实现渲染的。因为GLSurfaceView内部自带了EGL的管理以及渲染线程。同时还定义了用于需要实现的Reader接口,其中EglHelper和GLThread分别实现了上面提及到的管理EGL环境和渲染线程的工作;
但是这种方式也存在一定的缺点:
GLSurfaceView将OpenGL绑定在一起,所以一旦GLSurfaceView被销毁,伴随的OpenGL也一起销毁了,一个OpenGL只能渲染一个GLSurfaceView;
TextureView
这个空间是Android 4.0中引入的,和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为一个普通view,因此可以和其他普通View一样进行移动、旋转、缩放、动画等变化;
缺点:使用这个空间需要自定义实现EGL管理和渲染线程,比较麻烦,但是较为灵活;
TextureView本身内置了一个SurfaceTexture,用来配合EGL来将图像显示到屏幕上;
OpenGL ES 预览
- 获取 EGL Display 对象:eglGetDisplay();
- 初始化与 EGLDisplay 之间的连接:eglInitialize();
- 获取 EGLConfig 对象:eglChooseConfig();
- 创建 EGLContext 实例:eglCreateContext();
- 创建 EGLSurface 实例:eglCreateWindowSurface();
- 连接 EGLContext 和 EGLSurface:eglMakeCurrent();
- 使用 OpenGL ES API 绘制图形:gl_*();
- 切换 front buffer 和 back buffer 送显:eglSwapBuffer();
- 断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease();
- 删除 EGLSurface 对象;
- 删除 EGLContext 对象;
- 终止与 EGLDisplay 之间的连接;
获取 EGL Display 对象、初始化与 EGLDisplay 之间的连接
Display代表显示器,在有些系统上可以有多个显示器,也就会有多个Display。获得Display要调用 eglGetDisplay(NativeDisplay display_id),参数一般为 EGL_DEFAULT_DISPLAY ;
调用eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。
首先在主线程中创建一个新的子线程:
csharp
public void start() {
synchronized (lock) {
if (!mGlThreadFlag) {
if (mTextureProvider == null) {
return;
}
mGlThreadFlag = true;
Thread mglThread = new Thread(new Runnable() {
@Override
public void run() {
openGlRun();
}
});
mglThread.start();
try {
lock.wait();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
}
}
ini
private void openGlRun() {
EglHelper eglHelper = new EglHelper();
boolean ret = eglHelper.createGlWithSurface(new EglConfigAttrs(), new EglContextAttrs(),
new SurfaceTexture(1));
if (!ret) {
LogManager.loge(tag, "createGlWithSurface error");
return;
}
LogManager.logd(tag, "Provider Opened . data size (x,y)=" + mPreviewSize.getHeight()
+ "/" + mPreviewSize.getWidth());
if (mPreviewSize.getHeight() <= 0 || mPreviewSize.getWidth() <= 0) {
LogManager.loge(tag, "mPreviewSize error");
destroyEgl(eglHelper);
synchronized (lock) {
lock.notifyAll();
}
return;
}
final int sourceWidth = mPreviewSize.getHeight();
final int sourceHeight = mPreviewSize.getWidth();
synchronized (lock) {
lock.notifyAll();
}
if (mVideoRenderer == null) {
mVideoRenderer = new WrapRenderer(null);
}
FrameBufferUtil frameBufferUtil = new FrameBufferUtil();
mVideoRenderer.create();
mVideoRenderer.sizeChanged(sourceWidth, sourceHeight);
mVideoRenderer.setFlag(mTextureProvider.isLandscape() ? WrapRenderer.TYPE_CAMERA : WrapRenderer.TYPE_MOVE);
RenderBean renderBeanForVideo = new RenderBean();
renderBeanForVideo.eglHelper = eglHelper;
renderBeanForVideo.sourceWidth = sourceWidth;
renderBeanForVideo.sourceHeight = sourceHeight;
renderBeanForVideo.endFlag = false;
LogManager.logd(tag, "Processor While Loop Entry");
while (!mTextureProvider.frame() && mGlThreadFlag) {
mInputSurfaceTexture.updateTexImage();
mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
mVideoRenderer.draw(mInputSurfaceTextureId);
frameBufferUtil.unBindFrameBuffer();
renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
observable.notify(renderBeanForVideo);
}
LogManager.logd(tag, "out of gl thread loop");
synchronized (lock) {
renderBeanForVideo.endFlag = true;
observable.notify(renderBeanForVideo);
mVideoRenderer.destroy();
destroyEgl(eglHelper);
lock.notifyAll();
LogManager.logd(tag, "gl thread exit");
}
}
这样,OpenGL所在的子线程就可以工作了;
我们需要看一下EGL Display对象的逻辑;
在openGlRun()方法中,首先先new EglHelper对象:
arduino
public class EglHelper {
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
private static final int MAJOR_OFFSET = 0;
private static final int MINOR_OFFSET = 1;
private static final int OFFSET_0 = 0;
private static final int CONFIG_SIZE = 1;
private static final int CONFIGS_LENGTH = 1;
private static String TAG = "EglHelper";
public EglHelper() {
this(EGL14.EGL_DEFAULT_DISPLAY);
}
public EglHelper(int display) {
changeDisplay(display);
}
private void changeDisplay(int key) {
// 获取显示设备
mEglDisplay = EGL14.eglGetDisplay(key);
int[] versions = new int[2];
// version中存放EGL 版本号,int[0]为主版本号,int[1]为子版本号
EGL14.eglInitialize(mEglDisplay, versions, MAJOR_OFFSET, versions, MINOR_OFFSET);
}
..................
}
eglGetDisplay()方法中传入了一个参数:EGL14.EGL_DEFAULT_DISPLAY,默认值为0;
arduino
public class EGL14 {
..................
public static final int EGL_DEFAULT_DISPLAY = 0;
..................
}
在EglHelper构造方法中,初始化了mEglDisplay,通过调用EGL14.eglInitialize()方法;
获取 EGLConfig 对象
所为Config实际指的是FrameBuffer的参数。
一般用eglChooseConfig(EGLDisplay dpy, const EGLint * attr_list, EGLConfig * config, EGLint config_size, EGLint *num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id、value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig * config, EGLint config_size, EGLint *num_config) 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数;
Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改;
在new EglHelper()执行完成之后,紧接着会执行eglHelper.createGlWithSurface()方法:
ini
public boolean createGlWithSurface(EglConfigAttrs attrs, EglContextAttrs ctxAttrs, Object surface) {
EGLConfig config = getConfig(attrs.surfaceType(EGL14.EGL_WINDOW_BIT).makeDefault(true));
if (config == null) {
LogManager.loge(TAG, "getConfig failed : " + EGL14.eglGetError());
return false;
}
mEglContext = createContext(config, EGL14.EGL_NO_CONTEXT, ctxAttrs.makeDefault(true));
if (mEglContext == EGL14.EGL_NO_CONTEXT) {
LogManager.loge(TAG, "createContext failed : " + EGL14.eglGetError());
return false;
}
mEglSurface = createWindowSurface(surface);
if (mEglSurface == EGL14.EGL_NO_SURFACE) {
LogManager.loge(TAG, "createWindowSurface failed : " + EGL14.eglGetError());
return false;
}
if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
LogManager.loge(TAG, "eglMakeCurrent failed : " + EGL14.eglGetError());
return false;
}
return true;
}
首先在该方法中调用了getConfig()方法获取EGLConfig对象:
ini
private EGLConfig getConfig(EglConfigAttrs attrs) {
EGLConfig[] configs = new EGLConfig[1];
int[] configNum = new int[1];
EGL14.eglChooseConfig(mEglDisplay, attrs.build(), 0, configs, 0,
1, configNum, 0);
// 判断是否选择到符合传入参数的配置信息
if (configNum[0] > 0) {
if (attrs.isDefault()) {
// 从系统中获取对应属性的配置
mEglConfig = configs[0];
}
return configs[0];
}
return null;
}
在getConfig()方法中,执行了EGL14.eglChooseConfig()方法,将eglChooseConfig获取到的config赋值给mEglConfig;
创建 EGLContext 实例
OpenGL的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、绚染模式等一大堆状态,这些状态作用于程序提交的顶点 坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状 态,偶尔也从Context里获取一些信息;
在createGlWithSurface()方法中执行完getConfig()方法之后,紧接着调用mEglContext = createContext(config, EGL14.EGL_NO_CONTEXT, ctxAttrs.makeDefault(true))方法:
ini
private EGLContext createContext(EGLConfig config, EGLContext share, EglContextAttrs attrs) {
// native 层方法
EGLContext context = EGL14.eglCreateContext(mEglDisplay, config, share, attrs.build(), OFFSET_0);
if (attrs.isDefault()) {
mEglContext = context;
}
return context;
}
这个方法中执行了EGL14.eglCreateContext方法;
创建 EGLSurface 实例
Surface实际上就是一个FrameBuffer,通过 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 来创建一个可实际显示的Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不 是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧;
Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取;
在createGlWithSurface()方法中执行完createContext()方法之后,紧接着调用mEglSurface = createWindowSurface(surface)方法:
typescript
public EGLSurface createWindowSurface(Object surface) {
mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface,
new int[]{EGL14.EGL_NONE}, OFFSET_0);
return mEglSurface;
}
在EGL14.eglCreateWindowSurface()方法中,传入了mEglDisplay、mEglConfig和surface,surface其实是SurfaceTexture,在openGlRun()中执行createGlWithSurface方法时传入了new SurfaceTexture(1)就是该处的surface;
分析EGL14类中的eglCreateWindowSurface()方法:
ini
public static EGLSurface eglCreateWindowSurface(EGLDisplay dpy,
EGLConfig config,
Object win,
int[] attrib_list,
int offset
){
Surface sur = null;
if (win instanceof SurfaceView) {
SurfaceView surfaceView = (SurfaceView)win;
sur = surfaceView.getHolder().getSurface();
} else if (win instanceof SurfaceHolder) {
SurfaceHolder holder = (SurfaceHolder)win;
sur = holder.getSurface();
} else if (win instanceof Surface) {
sur = (Surface) win;
}
EGLSurface surface;
if (sur != null) {
surface = _eglCreateWindowSurface(dpy, config, sur, attrib_list, offset);
} else if (win instanceof SurfaceTexture) {
// _eglCreateWindowSurfaceTexture为native层方法
surface = _eglCreateWindowSurfaceTexture(dpy, config,
win, attrib_list, offset);
} else {
throw new java.lang.UnsupportedOperationException(
"eglCreateWindowSurface() can only be called with an instance of " +
"Surface, SurfaceView, SurfaceTexture or SurfaceHolder at the moment, " +
"this will be fixed later.");
}
return surface;
}
在上面传入了SurfaceTexture实例,在eglCreateWindowSurface方法中会执行_eglCreateWindowSurfaceTexture方法,至此通过SurfaceTexture创建出了对应的Surface;
连接 EGLContext 和 EGLSurface
通过上述流程逻辑,已经将EGLDisplay、EGLContext以及EGLSurface初始化好了;
在createGlWithSurface方法中执行完createWindowSurface方法之后,紧接着执行EGL14.eglMakeCurrent方法:
kotlin
if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
LogManager.loge(TAG, "eglMakeCurrent failed : " + EGL14.eglGetError());
return false;
}
arduino
public static native boolean eglMakeCurrent(
EGLDisplay dpy,
EGLSurface draw,
EGLSurface read,
EGLContext ctx
);
通过eglMakeCurrent()方法绑定EGLDisplay和EGLContext;
使用 OpenGL ES API 绘制图形
当我们绑定完成之后,我们就可以进行RenderLoop循环了,使用OpenGL ES 绘制图像:gl_*();
在openGlRun()方法中紧接着执行循环遍历:
ini
while (!mTextureProvider.frame() && mGlThreadFlag) {
mInputSurfaceTexture.updateTexImage();
mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
mVideoRenderer.draw(mInputSurfaceTextureId);
frameBufferUtil.unBindFrameBuffer();
renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
observable.notify(renderBeanForVideo);
}
其中通过!mTextureProvider.frame() && mGlThreadFlag的组合条件来判断是否需要轮训:
mGlThreadFlag条件
这个变量在mglThread线程开启之前就设置为了true,即在start()方法调用之后,就会赋值为true:
csharp
public void start() {
synchronized (lock) {
if (!mGlThreadFlag) {
if (mTextureProvider == null) {
return;
}
mGlThreadFlag = true;
Thread mglThread = new Thread(new Runnable() {
@Override
public void run() {
openGlRun();
}
});
mglThread.start();
try {
lock.wait();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
}
}
赋值为false的情况有两种:
- 调用stop()方法:
csharp
public void stop() {
synchronized (lock) {
if (mGlThreadFlag) {
mGlThreadFlag = false;
mTextureProvider.close();
try {
lock.wait();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
}
}
stop()方法和start()方法是对应的,代表停止opengl线程;
- 调用destroyEgl()方法:
ini
private void destroyEgl(EglHelper egl) {
mGlThreadFlag = false;
EGL14.eglMakeCurrent(egl.getDisplay(), EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(egl.getDisplay(), egl.getDefaultContext());
EGL14.eglTerminate(egl.getDisplay());
}
在断开Display和Context以及销毁Context的时候,也需要将mGlThreadFlag变量赋值为false,表示while循环不需要再轮训了;
mTextureProvider.frame()
这个判断条件其实是在创建用于绑定到CameraCaptureSession的Surface的时候生成的一个条件;
因为opengl在预览下的工作其实就是将Surface中保存的camera数据进行二次渲染处理然后将处理后的数据填充到 preview Surface中,最终显示到窗口中;
那么就需要创建一个Surface绑定到Camera,用于获取camera原始数据。创建逻辑如下:
ini
public Surface getVideoSurface() {
if (videoSurface == null) {
mInputSurfaceTextureId = createTextureId();
mInputSurfaceTexture = new SurfaceTexture(mInputSurfaceTextureId);
mInputSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
videoSurface = new Surface(mInputSurfaceTexture);
mTextureProvider.setOnFrameAvailableListener(mInputSurfaceTexture);
}
return videoSurface;
}
在该方法中首先先调用createTextureId()方法,获取一个纹理:
ini
protected int createTextureId() {
int target = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(target, texture[0]);
GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
纹理获取完成后,根据纹理创建一个SurfaceTexture对象,然后再根据SurfaceTexture对象创建一个Surface用于绑定CameraCaptureSession;
然后调用mTextureProvider.setOnFrameAvailableListener(mInputSurfaceTexture)方法将一个实现了SurfaceTexture.OnFrameAvailableListener接口的类实例设置到SurfaceTexture对象中:
java
private SurfaceTexture.OnFrameAvailableListener frameListener = new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mFrameSem.drainPermits();
mFrameSem.release();
}
};
/**
* set FrameAvailable Listener.
*
* @param surfaceTexture SurfaceTexture
*/
public void setOnFrameAvailableListener(final SurfaceTexture surfaceTexture) {
surfaceTexture.setOnFrameAvailableListener(frameListener);
}
在SurfaceTexture.OnFrameAvailableListener的onFrameAvailable回调中可以监听到回调结果;
mTextureProvider.frame()方法:
scss
private Semaphore mFrameSem;
TextureProvider() {
mFrameSem = new Semaphore(0);
}
boolean frame() {
try {
mFrameSem.acquire();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
return false;
}
在frame()方法中执行了mFrameSem.acquire()方法,acquire()和监听中的release()方法是对应的,acquire()方法用于获取一个令牌,而release()方法用于释放一个令牌;
综上,while循环通过上述两个判断条件的组合,确定是否继续轮训;
在while循环中,最后执行了observable.notify(renderBeanForVideo)方法:
ini
while (!mTextureProvider.frame() && mGlThreadFlag) {
mInputSurfaceTexture.updateTexImage();
mInputSurfaceTexture.getTransformMatrix(mVideoRenderer.getTextureMatrix());
frameBufferUtil.bindFrameBuffer(sourceWidth, sourceHeight);
GLES20.glViewport(0, 0, sourceWidth, sourceHeight);
mVideoRenderer.draw(mInputSurfaceTextureId);
frameBufferUtil.unBindFrameBuffer();
renderBeanForVideo.textureId = frameBufferUtil.getCacheTextureId();
renderBeanForVideo.timeStamp = mTextureProvider.getTimeStamp();
renderBeanForVideo.textureTime = mInputSurfaceTexture.getTimestamp();
observable.notify(renderBeanForVideo);
}
在while循环中,首先先执行了mVideoRenderer.draw(mInputSurfaceTextureId),作用就是用于定义图像显示区域,可以理解为图元组装逻辑;
执行完成之后,最后执行observable.notify(renderBeanForVideo)方法:
ini
@Override
public void onCall(RenderBean rb) {
if (rb.endFlag && mShowSurface != null) {
rb.eglHelper.destroySurface(mShowSurface);
mShowSurface = null;
} else if (isShow && mSurface != null) {
replaceSurface(rb);
if (mShowSurface == null) {
mShowSurface = rb.eglHelper.createWindowSurface(mSurface);
mFilter = new OriginalFilter();
mFilter.create();
int[] sideLength = new int[]{rb.sourceWidth, rb.sourceHeight, mWidth, mHeight};
MatrixUtils.getMatrix(mFilter.getVertexMatrix(), MatrixUtils.TYPE_FIT_XY, sideLength);
MatrixUtils.flip(mFilter.getVertexMatrix(), false, true);
}
rb.eglHelper.makeCurrent(mShowSurface);
GLES20.glViewport(0, 0, mWidth, mHeight);
mFilter.draw(rb.textureId);
dispatch(rb);
rb.eglHelper.swapBuffers(mShowSurface);
}
}
onCall()方法中的逻辑其实和上面的逻辑类似,同样是执行了mFilter.draw(rb.textureId),将图像纹理绘制到Surface中;
front buffer 和 back buffer
在onCall()方法中,执行完mFilter.draw(rb.textureId)之后,紧接着执行rb.eglHelper.swapBuffers(mShowSurface)逻辑:
typescript
public void swapBuffers(EGLSurface surface) {
EGL14.eglSwapBuffers(mEglDisplay, surface);
}
swapBuffers主要用于刷新数据;
这里需要重点注意的是surface。如果这里的surface是一个像素缓冲(pixel buffer)Surface,那什么都不会发生,调用将正确的返回,不报任何错误;
但如果surface是一个双重缓冲surface(大多数情况),这个方法将会交换surface内部的前端缓冲(front-buffer)和后端缓冲(back-surface)。后端缓冲用于存储渲染结果,前端缓冲则用于底层窗口系统,底层窗口系统将缓冲中的颜色信息显示到设备上;
断开并释放与 EGLSurface 关联的 EGLContext 对象
与显示设备解绑,销毁eglSurface
ini
EGL14.eglMakeCurrent(egl.getDisplay(), EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
在该逻辑中,同样是执行了EGL14.eglMakeCurrent方法,和EGLSurface 关联 EGLContext所调用的方法一样,只不过是方法中传入的参数不同(二、三、四参数不同);
删除 EGLSurface 对象&删除 EGLContext 对象
arduino
public void destroySurface(EGLDisplay display, EGLSurface surface) {
EGL14.eglDestroySurface(display, surface);
}
public void destroyContext(EGLDisplay display, EGLContext context) {
EGL14.eglDestroyContext(display, context);
}
终止与 EGLDisplay 之间的连接
ini
EGL14.eglTerminate(egl.getDisplay());
销毁显示设备;
OpenGL ES 录像
其实预览和录像,在获取camera数据的逻辑上,其实是一致的,只是预览过程中camera数据渲染完成后是显示到窗口中,录像过程中camera数据经过渲染之后,使用MediaCodec进行编码,然后进行落盘生成视频文件;
其实差别就在于使用 OpenGL ES API 绘制图形这个过程中;
在预览过程中,通过preview Surface创建EGLSurface实例,这个preview Surface是从TextureView中获取的;同时会将纹理绘制到preview Surface中;
在录像过程中,通过MediaCodec创建的Surface创建的EGLSurface实例,然后MediaCodec通过Surface获取camera数据,然后进行编码、混合和落盘;
从使用什么类型的surface创建的EGLSurface可以区别预览 和录像;
我们主要看一下录像的onCall()方法逻辑:
scss
public void onCall(RenderBean rb) {
if (isEncodeStarted) {
..................
MediaEncoderManager.MediaEncoder currentMediaEncoder = mMediaEncoderManager.getCurrentMediaEncoder();
if (currentMediaEncoder != null && !currentMediaEncoder.videoCodecStopSingle) {
EGLSurface currentSurface = currentMediaEncoder.eglSurface;
if (currentSurface != null) {
rb.eglHelper.makeCurrent(currentSurface);
GLES20.glViewport(0, 0, mConfig.width, mConfig.height);
mFilter.draw(rb.textureId);
if (mListener != null) {
mListener.onDrawEnd(currentSurface, rb);
}
rb.eglHelper.swapBuffers(currentSurface);
}
} else {
if (currentMediaEncoder != null) {
LogManager.loge(TAG, "onCall: The frame is lost because it has been marked as ended");
}
}
}
}
在该方法中,通过EGLSurface currentSurface = currentMediaEncoder.eglSurface方式获取的EGLSurface实例,我们看一下currentMediaEncoder.eglSurface的定义:
ini
static class MediaEncoder {
MediaCodec videoCodec;
MediaCodec audioCodec;
MediaMp4Muxer mediaMuxer;
EGLSurface eglSurface;
Surface inputSurface;
..................
}
自定义了一个类,其中包含了MediaCodec videoCodec、EGLSurface eglSurface以及Surface inputSurface;
其中MediaCodec 是Android原生提供的编解码机制;
Surface inputSurface是录像使用的Surface,类同于预览使用的Surface;
主要看一下inputSurface是如何定义的:
ini
if (mediaencoder.inputSurface == null) {
mediaencoder.inputSurface = MediaCodec.createPersistentInputSurface();
}
mediaencoder.videoCodec.setInputSurface(mediaencoder.inputSurface);
if (mediaencoder.eglSurface == null) {
mediaencoder.eglSurface = mEglHelper.createWindowSurface(mediaencoder.inputSurface);
}
inputSurface是通过MediaCodec.createPersistentInputSurface()获取的;
然后调用EGL14.eglCreateWindowSurface方法创建对应的EGLSurface;
在onCall()方法中将EGLSurface创建完成之后,会调用mListener.onDrawEnd(currentSurface, rb)方法,进行编码逻辑执行,这里就不展开叙述编码模块流程了;
相当于将预览逻辑中的mFilter.draw(rb.textureId)替换为mListener.onDrawEnd(currentSurface, rb),就可以从预览逻辑转变为录像逻辑;