上一节提到画中画实现,实际上就是将两个视频纹理绘制到 FBO,那么预览、录制,其实就是将 FBO 绘制到预览界面,并共享 egl 将数据同时绘制到编码器 Surface 一份就可以了。
下图是整个架构的大体流程。

先来建立一个用来绘制预览和录制的 Drawer,重点看片段着色器的代码,输入的纹理类型是 sampler2D,这是 FBO 输出的纹理类型。其中录制 surface 是 MediaCodec 提供的,录制界面绘制共享了预览用的 egl(mPreviewEGLContext)。
/**
* author : liuhongwei
* e-mail :
* date : 2021/7/26 18:41
* desc : 屏幕绘制
* version: 1.0
*/
public class ScreenDrawer {
private static final String TAG = "ScreenDrawer";
private final float[] mVertexCoors = new float[]{
-1.0F, -1.0F,
1.0F, -1.0F,
-1.0F, 1.0F,
1.0F, 1.0F};
private final float[] mTextureCoors = new float[]{
0.0F, 1.0F,
1.0F, 1.0F,
0.0F, 0.0F,
1.0F, 0.0F};
private int mProgram = -1;
private int mVertexPosHandler = -1;
private int mVertexMatrixHandler = -1;
private int mTexturePosHandler = -1;
private int mTextureHandler = -1;
private FloatBuffer mVertexBuffer;
private FloatBuffer mTextureBuffer;
private final float[] mMatrix = new float[16];
private int mViewPortWidth, mViewPortHeight;
public ScreenDrawer(){
initPos();
Matrix.setIdentityM(mMatrix, 0);
// Y轴翻转
Matrix.scaleM(mMatrix, 0, 1, -1, 1);
}
private void initPos() {
ByteBuffer bb = ByteBuffer.allocateDirect(mVertexCoors.length * 4);
bb.order(ByteOrder.nativeOrder());
//将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
mVertexBuffer = bb.asFloatBuffer();
mVertexBuffer.put(mVertexCoors);
mVertexBuffer.position(0);
ByteBuffer cc = ByteBuffer.allocateDirect(mTextureCoors.length * 4);
cc.order(ByteOrder.nativeOrder());
mTextureBuffer = cc.asFloatBuffer();
mTextureBuffer.put(mTextureCoors);
mTextureBuffer.position(0);
}
private void createGLPrg() {
if (mProgram == -1) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexShader());
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShader());
//创建OpenGL ES程序,注意:需要在OpenGL渲染线程中创建,否则无法渲染
mProgram = GLES20.glCreateProgram();
//将顶点着色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader);
//将片元着色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
//连接到着色器程序
GLES20.glLinkProgram(mProgram);
mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition");
mVertexMatrixHandler = GLES20.glGetUniformLocation(mProgram, "uMatrix");
mTextureHandler = GLES20.glGetUniformLocation(mProgram, "uTexture");
mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate");
}
//使用OpenGL程序
GLES20.glUseProgram(mProgram);
}
private void activateTexture(int textureId) {
//激活指定纹理单元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//绑定纹理ID到纹理单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//将激活的纹理单元传递到着色器里面
GLES20.glUniform1i(mTextureHandler, 0);
//配置边缘过渡参数
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR * 1.0f);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR * 1.0f);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
}
public void setViewWH(int viewPortWidth, int viewPortHeight) {
mViewPortWidth = viewPortWidth;
mViewPortHeight = viewPortHeight;
}
public void doDraw(int textureId) {
GLES20.glViewport(0, 0, mViewPortWidth, mViewPortHeight);
createGLPrg();
activateTexture(textureId);
//启用顶点的句柄
GLES20.glEnableVertexAttribArray(mVertexPosHandler);
GLES20.glEnableVertexAttribArray(mTexturePosHandler);
GLES20.glUniformMatrix4fv(mVertexMatrixHandler, 1, false, mMatrix, 0);
//设置着色器参数, 第二个参数表示一个顶点包含的数据数量,这里为xy,所以为2
GLES20.glVertexAttribPointer(mVertexPosHandler, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
GLES20.glVertexAttribPointer(mTexturePosHandler, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
//开始绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
public void release() {
GLES20.glDisableVertexAttribArray(mVertexPosHandler);
GLES20.glDisableVertexAttribArray(mTexturePosHandler);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glDeleteProgram(mProgram);
}
private String getVertexShader() {
return "attribute vec4 aPosition;" +
"precision mediump float;" +
"uniform mat4 uMatrix;" +
"attribute vec2 aCoordinate;" +
"varying vec2 vCoordinate;" +
"void main() {" +
" gl_Position = uMatrix*aPosition;" +
" vCoordinate = vec2(aCoordinate.x, aCoordinate.y);" +
"}";
}
private String getFragmentShader() {
return "precision mediump float;" +
"varying vec2 vCoordinate;" +
"uniform sampler2D uTexture;" +
"void main() {" +
" vec4 color = texture2D(uTexture, vCoordinate);" +
" gl_FragColor = color;" +
"}";
}
private int loadShader(int type, String shaderCode) {
//根据type创建顶点着色器或者片元着色器
int shader = GLES20.glCreateShader(type);
//将资源加入到着色器中,并编译
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
所有的 Drawer 开发完毕,现在是时候构建预览+录制封装类了。
package com.ty.media.opengl.egl;
import android.graphics.SurfaceTexture;
import android.opengl.EGLContext;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;
import com.ty.media.opengl.drawer.FBODrawer;
import com.ty.media.opengl.drawer.ScreenDrawer;
/**
* author : liuhongwei
* e-mail :
* date : 2021/7/22 18:56
* desc : 自定义的OpenGL渲染器,包含EGL的初始化,线程与OpenGL上下文绑定、渲染循环、资源销毁等
* version: 1.0
*/
public class PreviewGLRenderer {
private static final String TAG = "PreviewGLRenderer";
private static final int MSG_ID_INIT_PREVIEW_SURFACE = 1;
private static final int MSG_ID_DE_INIT_PREVIEW_SURFACE = MSG_ID_INIT_PREVIEW_SURFACE + 1;
private static final int MSG_ID_RENDERING = MSG_ID_DE_INIT_PREVIEW_SURFACE + 1;
private RenderHandler mRenderHandler;
private RenderThread mRenderThread;
private Looper mRenderLooper;
private volatile boolean isRenderThreadRunning;
private volatile boolean isSurfaceDestroyed = true;
private RenderEngine mRenderEngine;
private EGLSurfaceHolder mPreviewEGLSurfaceHolder;
private EGLContext mPreviewEGLContext;
private FBODrawer mFBODrawer;
private ScreenDrawer mScreenDrawer;
private RenderThreadCb mRenderThreadCb;
private RecordGLRenderer mRecordGLRenderer;
public PreviewGLRenderer() {
}
/**
* 开启预览渲染线程
*/
public void startRenderThread() {
if (isRenderThreadRunning) {
return;
}
mRenderThread = new RenderThread();
mRenderThread.start();
mRecordGLRenderer = new RecordGLRenderer();
mRecordGLRenderer.startRenderThread();
}
/**
* 关闭预览渲染线程
*/
public void stopRenderThread() {
if (mRenderLooper != null) {
mRenderLooper.quitSafely();
mRenderHandler = null;
isRenderThreadRunning = false;
}
mRecordGLRenderer.stopRenderThread();
}
/**
* 设置渲染线程回调
*
* @param renderThreadCb
*/
public void setRenderThreadCb(RenderThreadCb renderThreadCb) {
mRenderThreadCb = renderThreadCb;
}
/**
* 设置单画面或画中画模式
*
* @param isPip 是否画中画
*/
public void setPipMode(boolean isPip) {
if (mFBODrawer != null) {
mFBODrawer.setMode(isPip ? FBODrawer.Mode.PIP : FBODrawer.Mode.SINGLE);
}
}
/**
* 创建预览窗口
*
* @param surface 窗口
* @param w 宽
* @param h 高
*/
public void createPreview(Surface surface, int w, int h) {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_INIT_PREVIEW_SURFACE;
m.obj = surface;
m.arg1 = w;
m.arg2 = h;
mRenderHandler.sendMessage(m);
}
}
/**
* 销毁预览窗口
*/
public void destroyPreview() {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_DE_INIT_PREVIEW_SURFACE;
mRenderHandler.sendMessage(m);
}
}
/**
* 创建录制窗口
*/
public void createRecord(Surface surface, int w, int h) {
mRecordGLRenderer.createRecord(surface, mPreviewEGLContext, w, h);
}
/**
* 销毁录制窗口
*/
public void destroyRecord() {
mRecordGLRenderer.destroyRecord();
}
private void render() {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_RENDERING;
mRenderHandler.sendMessage(m);
}
}
private void createPreviewEGLSurface(Surface surface) {
mPreviewEGLSurfaceHolder = new EGLSurfaceHolder();
mPreviewEGLContext = mPreviewEGLSurfaceHolder.init(null, EGLCore.EGL_RECORDABLE_ANDROID);
mPreviewEGLSurfaceHolder.createEGLSurface(surface, -1, -1);
mPreviewEGLSurfaceHolder.makeCurrent();
}
private void render(long timeMs) {
if (null != mPreviewEGLSurfaceHolder) {
mFBODrawer.getBgSurfaceTexture().updateTexImage();
if (mFBODrawer.getMode() == FBODrawer.Mode.PIP) {
mFBODrawer.getFgSurfaceTexture().updateTexImage();
}
int textureId = mFBODrawer.doDraw();
mScreenDrawer.doDraw(textureId);
mPreviewEGLSurfaceHolder.setTimestamp(timeMs);
mPreviewEGLSurfaceHolder.swapBuffers();
if (mRecordGLRenderer.isRecording()) {
mRecordGLRenderer.render(textureId);
}
}
}
private void destroyPreviewEGLSurface() {
if (null != mPreviewEGLSurfaceHolder) {
mPreviewEGLSurfaceHolder.destroyEGLSurface();
mPreviewEGLSurfaceHolder.release();
mPreviewEGLSurfaceHolder = null;
}
}
private class RenderHandler extends Handler {
public RenderHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID_INIT_PREVIEW_SURFACE:
createPreviewEGLSurface((Surface) (msg.obj));
mFBODrawer = new FBODrawer();
mScreenDrawer = new ScreenDrawer();
mFBODrawer.prepareDraw(1920, 1080);
mScreenDrawer.setViewWH(msg.arg1, msg.arg2);
if (mRenderThreadCb != null) {
mRenderThreadCb.bgTextureAvail(mFBODrawer.getBgSurfaceTexture());
mRenderThreadCb.fgTextureAvail(mFBODrawer.getFgSurfaceTexture());
}
isSurfaceDestroyed = false;
mRenderEngine = new RenderEngine();
mRenderEngine.start();
break;
case MSG_ID_DE_INIT_PREVIEW_SURFACE:
isSurfaceDestroyed = true;
destroyPreviewEGLSurface();
mFBODrawer.destroyDraw();
mScreenDrawer.release();
mRenderEngine.setRunning(false);
mRenderEngine.interrupt();
try {
mRenderEngine.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
case MSG_ID_RENDERING:
if (!isSurfaceDestroyed) {
render(System.currentTimeMillis());
}
break;
default:
}
}
}
private class RenderThread extends Thread {
@Override
public void run() {
Looper.prepare();//必须在Looper.myLooper()之前
mRenderLooper = Looper.myLooper();
mRenderHandler = new RenderHandler(mRenderLooper);
isRenderThreadRunning = true;
Looper.loop();
}
}
/**
* 驱动绘制 25 Hz 1000 / 25 = 40 ms
*/
private class RenderEngine extends Thread {
private volatile boolean isRunning = true;
@Override
public void run() {
while (isRunning) {
render();
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setRunning(boolean running) {
isRunning = running;
}
}
public interface RenderThreadCb {
public void fgTextureAvail(SurfaceTexture textureId);
public void bgTextureAvail(SurfaceTexture textureId);
}
}
下面是录制 MediaCodec OpenGL 渲染器代码。
/**
* author : liuhongwei
* e-mail :
* date : 2021/7/27 08:50
* desc : 录制 MediaCodec OpenGL 渲染器
* version: 1.0
*/
public class RecordGLRenderer {
private static final String TAG = "RecordGLRenderer";
private static final int MSG_ID_RENDERING = 1;
private static final int MSG_ID_INIT_RECORD_SURFACE = MSG_ID_RENDERING + 1;
private static final int MSG_ID_DE_INIT_RECORD_SURFACE = MSG_ID_INIT_RECORD_SURFACE + 1;
private static final String KEY_SURFACE = "SURFACE";
private RenderHandler mRenderHandler;
private RenderThread mRenderThread;
private Looper mRenderLooper;
private volatile boolean isRenderThreadRunning;
private EGLSurfaceHolder mRecordEGLSurfaceHolder;
private ScreenDrawer mRecordDrawer;
private volatile boolean isRecording = false;
public RecordGLRenderer(){
}
public void startRenderThread() {
if (isRenderThreadRunning) {
return;
}
mRenderThread = new RenderThread();
mRenderThread.start();
}
public void stopRenderThread() {
if (mRenderLooper != null) {
mRenderLooper.quitSafely();
mRenderHandler = null;
isRenderThreadRunning = false;
}
}
public boolean isRecording() {
return isRecording;
}
public void createRecord(Surface surface, EGLContext eglContext, int w, int h) {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_INIT_RECORD_SURFACE;
m.obj = eglContext;
Bundle b = new Bundle();
b.putParcelable(KEY_SURFACE, surface);
m.setData(b);
m.arg1 = w;
m.arg2 = h;
mRenderHandler.sendMessage(m);
}
}
public void destroyRecord() {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_DE_INIT_RECORD_SURFACE;
mRenderHandler.sendMessage(m);
}
}
public void render(int textureId) {
if (null != mRenderHandler) {
Message m = new Message();
m.what = MSG_ID_RENDERING;
m.arg1 = textureId;
mRenderHandler.sendMessage(m);
}
}
private void createRecordEGLSurface(Surface surface, EGLContext eglContext) {
mRecordEGLSurfaceHolder = new EGLSurfaceHolder();
mRecordEGLSurfaceHolder.init(eglContext, EGLCore.EGL_RECORDABLE_ANDROID);
mRecordEGLSurfaceHolder.createEGLSurface(surface, -1, -1);
mRecordEGLSurfaceHolder.makeCurrent();
}
private void renderInner(int textureId) {
if (null != mRecordEGLSurfaceHolder) {
mRecordDrawer.doDraw(textureId);
//这句代码会导致无法在 MediaCodec 的 InputSurface 上绘制
//mRecordEGLSurfaceHolder.setTimestamp(System.currentTimeMillis());
mRecordEGLSurfaceHolder.swapBuffers();
}
}
private void destroyRecordEGLSurface() {
if (null != mRecordEGLSurfaceHolder) {
mRecordEGLSurfaceHolder.destroyEGLSurface();
mRecordEGLSurfaceHolder.release();
}
}
private class RenderHandler extends Handler {
public RenderHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID_RENDERING:
renderInner(msg.arg1);
break;
case MSG_ID_INIT_RECORD_SURFACE:
Bundle b = msg.getData();
createRecordEGLSurface(b.getParcelable(KEY_SURFACE), (EGLContext) (msg.obj));
mRecordDrawer = new ScreenDrawer();
mRecordDrawer.setViewWH(msg.arg1, msg.arg2);
isRecording = true;
break;
case MSG_ID_DE_INIT_RECORD_SURFACE:
destroyRecordEGLSurface();
mRecordDrawer.release();
isRecording = false;
break;
default:
}
}
}
private class RenderThread extends Thread {
@Override
public void run() {
Looper.prepare();//必须在Looper.myLooper()之前
mRenderLooper = Looper.myLooper();
mRenderHandler = new RenderHandler(mRenderLooper);
isRenderThreadRunning = true;
Looper.loop();
}
}
}
最后是 egl 封装类,用于方便调用 egl 接口。
/**
* author : liuhongwei
* e-mail :
* date : 2021/7/22 17:16
* desc : EGL基础封装
* version: 1.0
*/
public class EGLCore {
private static final String TAG = "EGLCore";
private static final int FLAG_RECORDABLE = 0x01;
/**
* Android 指定的标志
* 告诉EGL它创建的surface必须和视频编解码器兼容。
* 没有这个标志,EGL可能会使用一个MediaCodec不能理解的Buffer
* 这个变量在api26以后系统才自带有,为了兼容,我们自己写好这个值0x3142
*/
public static final int EGL_RECORDABLE_ANDROID = 0x3142;
// EGL相关变量
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig mEGLConfig;
/**
* 初始化EGLDisplay
*
* @param eglContext 共享上下文
*/
public EGLContext init(EGLContext eglContext, int flags) {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGL already set up");
}
EGLContext sharedContext = EGL14.EGL_NO_CONTEXT;
if (null != eglContext) {
sharedContext = eglContext;
}
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("Unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
throw new RuntimeException("unable to initialize EGL14");
}
if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
EGLConfig config = getConfig(flags, 2);
if (config == null) {
throw new RuntimeException("Unable to find a suitable EGLConfig");
}
int[] attr2List = new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
EGLContext context = EGL14.eglCreateContext(
mEGLDisplay, config, sharedContext,
attr2List, 0
);
mEGLConfig = config;
mEGLContext = context;
}
return mEGLContext;
}
/**
* 获取EGL配置信息
*
* @param flags 初始化标记
* @param version EGL版本
*/
private EGLConfig getConfig(int flags, int version) {
int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
if (version >= 3) {
// 配置EGL 3
renderableType = renderableType | EGLExt.EGL_OPENGL_ES3_BIT_KHR;
}
// The actual surface is generally RGBA or RGBX, so situationally omitting alpha
// doesn't really help. It can also lead to a huge performance hit on glReadPixels()
// when reading into a GL_RGBA buffer.
int[] attrList = new int[]{
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_SURFACE_TYPE,
EGL14.EGL_WINDOW_BIT,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
EGL14.EGL_NONE
};
//配置Android指定的标记
if ((flags & FLAG_RECORDABLE) != 0) {
attrList[attrList.length - 3] = EGL_RECORDABLE_ANDROID;
attrList[attrList.length - 2] = 1;
}
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
//获取EGL配置
if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
configs, 0, configs.length,
numConfigs, 0)) {
Log.w(TAG, "Unable to find RGB8888 EGLConfig");
return null;
}
//使用系统推荐的第一个配置
return configs[0];
}
/**
* 创建可显示的渲染缓存
*
* @param surface 渲染窗口的surface
*/
public EGLSurface createWindowSurface(Object surface) {
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
throw new RuntimeException("Invalid surface");
}
int[] surfaceAttr = new int[]{EGL14.EGL_NONE};
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(
mEGLDisplay, mEGLConfig, surface,
surfaceAttr, 0);
if (eglSurface == null) {
throw new RuntimeException("Surface was null");
}
return eglSurface;
}
/**
* 创建离屏渲染缓存
*
* @param width 缓存窗口宽
* @param height 缓存窗口高
*/
public EGLSurface createOffscreenSurface(int width, int height) {
int[] surfaceAttr = new int[]{EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE};
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(
mEGLDisplay, mEGLConfig,
surfaceAttr, 0);
if (eglSurface == null) {
throw new RuntimeException("Surface was null");
}
return eglSurface;
}
/**
* 将当前线程与上下文进行绑定
*/
public void makeCurrent(EGLSurface eglSurface) {
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGLDisplay is null, call init first");
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
throw new RuntimeException("makeCurrent(eglSurface) failed");
}
}
/**
* 将当前线程与上下文进行绑定
*/
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGLDisplay is null, call init first");
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent(draw,read) failed");
}
}
/**
* 将缓存图像数据发送到设备进行显示
*/
public boolean swapBuffers(EGLSurface eglSurface) {
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
}
/**
* 设置当前帧的时间,单位:纳秒
*/
public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
}
/**
* 销毁EGLSurface,并解除上下文绑定
*/
public void destroySurface(EGLSurface elg_surface) {
EGL14.eglMakeCurrent(
mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
);
EGL14.eglDestroySurface(mEGLDisplay, elg_surface);
}
/**
* 释放资源
*/
public void release() {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
// Android is unusual in that it uses a reference-counted EGLDisplay. So for
// every eglInitialize() we need an eglTerminate().
EGL14.eglMakeCurrent(
mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
);
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
}
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
mEGLContext = EGL14.EGL_NO_CONTEXT;
mEGLConfig = null;
}
}
下面是 EGLSurfaceHolder 详细实现。
/**
* author : liuhongwei
* e-mail :
* date : 2021/7/22 18:45
* desc : EGLSurface 持有者
* version: 1.0
*/
public class EGLSurfaceHolder {
private static final String TAG = "EGLSurfaceHolder";
private EGLCore mEGLCore;
private EGLSurface mEGLSurface;
public EGLContext init(EGLContext shareContext, int flags) {
mEGLCore = new EGLCore();
return mEGLCore.init(shareContext, flags);
}
public void createEGLSurface(Object surface, int width, int height) {
if (surface != null) {
mEGLSurface = mEGLCore.createWindowSurface(surface);
} else {
mEGLSurface = mEGLCore.createOffscreenSurface(width, height);
}
}
public void makeCurrent() {
if (mEGLSurface != null) {
mEGLCore.makeCurrent(mEGLSurface);
}
}
public void swapBuffers() {
if (mEGLSurface != null) {
mEGLCore.swapBuffers(mEGLSurface);
}
}
public void setTimestamp(long timeMs) {
if (mEGLSurface != null) {
mEGLCore.setPresentationTime(mEGLSurface, timeMs * 1000);
}
}
public void destroyEGLSurface() {
if (mEGLSurface != null) {
mEGLCore.destroySurface(mEGLSurface);
mEGLSurface = null;
}
}
public void release() {
mEGLCore.release();
}
}
Android OpenGL ES 实现画中画及MediaCodec录制
该博客详细介绍了如何在Android中利用OpenGL ES实现画中画效果以及结合MediaCodec进行视频录制。通过创建FBO Drawer和Screen Drawer类,将两个视频纹理绘制到FBO,然后在预览界面和编码器Surface上共享数据。整个流程包括EGL上下文的初始化、线程绑定、渲染循环和资源销毁。此外,还提供了RecordGLRenderer类用于MediaCodec的OpenGL渲染。
1986

被折叠的 条评论
为什么被折叠?



