Android 音视频之openGL特效 android opengles
xsobi 2024-12-25 16:14 1 浏览
今天浅谈:
- openGL
- openSL ES
代码有点长
一丶openGL特效(1)
1. MediaCodec
MediaCodec是Android 4.1.2(API 16)提供的一套编解码API。它的使用非常简单,它存在一个输入缓冲区与一个输出缓冲区,在编码时我们将数据塞入输入缓冲区,然后从输出缓冲区取出编码完成后的数据就可以了。
除了直接操作输入缓冲区之外,还有另一种方式来告知MediaCodec需要编码的数据,那就是:
使用此接口创建一个Surface,然后我们在这个Surface中"作画",MediaCodec就能够自动的编码 Surface中的“画作”,我们只需要从输出缓冲区取出编码完成之后的数据即可。
此前,我们使用OpenGL进行绘画显示在屏幕上,然而想要复制屏幕图像到cpu内存中却不是一件非常 轻松的事情。所以我们可以直接将OpenGL显示到屏幕中的图像,同时绘制到MediaCodec#createInputSurface当中去。
PBO(Pixel Buffer Object,像素缓冲对象)通过直接的内存访问(Direct Memory Access,DMA)高速的复制屏幕图像像素数据到CPU内存,但这里我们直接使用createInputSurface更简单…
录制我们在另外一个线程中进行(录制现场),所以录制的EGL环境和显示的EGL环境(GLSurfaceView,显示线程)是两个独立的工作环境,他们又能够共享上下文资源:显示线程中使用的texture等,需要能够在录制线程中操作(通过录制线程中使用OpenGL绘制到MediaCodec的Surface)。
在这个线程中我们需要自己来: 1、配置录制使用的EGL环境(参照GLSurfaceView是怎么配置的)
2、完成将显示的图像绘制到MediaCodec的Surface中
3、编码(H.264)与复用(封装mp4)的工作
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
2.极快、极慢模式视频录制
2.1.创摄像头预览
NDK51_OpenGL:FBO
定义一个DouyinView 继承GLSurfaceView, 并setRenderer(douyinRenderer); DouyinRenderer负责渲染 DouyinRenderer中创建画布,设置效果 CameraFilter写入fbo (帧缓存),ScreenFilter 负责往屏幕上渲染
AbstractFilter
public abstract class AbstractFilter {
protected FloatBuffer mGLVertexBuffer;
protected FloatBuffer mGLTextureBuffer;
//顶点着色
protected int mVertexShaderId;
//片段着色
protected int mFragmentShaderId;
protected int mGLProgramId;
/**
* 顶点着色器
* attribute vec4 position;
* 赋值给gl_Position(顶点)
*/
protected int vPosition;
/**
* varying vec2 textureCoordinate;
*/
protected int vCoord;
/**
* uniform mat4 vMatrix;
*/
protected int vMatrix;
/**
* 片元着色器
* Samlpe2D 扩展 samplerExternalOES
*/
protected int vTexture;
protected int mOutputWidth;
protected int mOutputHeight;
public AbstractFilter(Context context, int vertexShaderId, int fragmentShaderId) {
this.mVertexShaderId = vertexShaderId;
this.mFragmentShaderId = fragmentShaderId;
// 4个点 x,y = 4*2 float 4字节 所以 4*2*4
mGLVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLVertexBuffer.clear();
float[] VERTEX = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
mGLVertexBuffer.put(VERTEX);
mGLTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLTextureBuffer.clear();
float[] TEXTURE = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
mGLTextureBuffer.put(TEXTURE);
initilize(context);
initCoordinate();
}
protected void initilize(Context context) {
String vertexSharder = OpenGLUtils.readRawTextFile(context,mVertexShaderId);
String framentShader = OpenGLUtils.readRawTextFile(context,mFragmentShaderId);
mGLProgramId = OpenGLUtils.loadProgram(vertexSharder,framentShader);
// 获得着色器中的 attribute 变量 position 的索引值
vPosition = GLES20.glGetAttribLocation(mGLProgramId, "vPosition");
vCoord = GLES20.glGetAttribLocation(mGLProgramId,"vCoord");
vMatrix = GLES20.glGetUniformLocation(mGLProgramId,"vMatrix");
// 获得Uniform变量的索引值
vTexture = GLES20.glGetUniformLocation(mGLProgramId,"vTexture");
}
public void onReady(int width, int height) {
mOutputWidth = width;
mOutputHeight = height;
}
public void release() {
GLES20.glDeleteProgram(mGLProgramId);
}
public int onDrawFrame(int textureId) {
//设置显示窗口
GLES20.glViewport(0, 0, mOutputWidth, mOutputHeight);
//使用着色器
GLES20.glUseProgram(mGLProgramId);
//传递坐标
mGLVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0,mGLVertexBuffer);
GLES20.glEnableVertexAttribArray(vPosition);
mGLTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0,mGLTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
return textureId;
}
//修改坐标
protected void initCoordinate() {
}
}
ScreenFilter
/**
* 负责往屏幕上渲染
*/
public class ScreenFilter extends AbstractFilter{
public ScreenFilter(Context context) {
super(context,R.raw.base_vertex, R.raw.base_frag);
}
}
CameraFilter
public class CameraFilter extends AbstractFilter{
private int[] mFrameBuffers;
private int[] mFrameBufferTextures;
private float[] matrix;
public CameraFilter(Context context) {
super(context, R.raw.camera_vertex2, R.raw.camera_frag2);
}
@Override
protected void initCoordinate() {
mGLTextureBuffer.clear();
//摄像头是颠倒的
// float[] TEXTURE = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
//调整好了镜像
// float[] TEXTURE = {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
//修复旋转 逆时针旋转90度
float[] TEXTURE = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
mGLTextureBuffer.put(TEXTURE);
}
@Override
public void release() {
super.release();
destroyFrameBuffers();
}
public void destroyFrameBuffers() {
//删除fbo的纹理
if (mFrameBufferTextures != null) {
GLES20.glDeleteTextures(1, mFrameBufferTextures, 0);
mFrameBufferTextures = null;
}
//删除fbo
if (mFrameBuffers != null) {
GLES20.glDeleteFramebuffers(1,mFrameBuffers, 0);
mFrameBuffers = null;
}
}
@Override
public void onReady(int width, int height) {
super.onReady(width, height);
if (mFrameBuffers != null) {
destroyFrameBuffers();
}
//fbo的创建 (缓存)
//1、创建fbo (离屏屏幕)
mFrameBuffers = new int[1];
// 1、创建几个fbo 2、保存fbo id的数据 3、从这个数组的第几个开始保存
GLES20.glGenFramebuffers(mFrameBuffers.length,mFrameBuffers,0);
//2、创建属于fbo的纹理
mFrameBufferTextures = new int[1]; //用来记录纹理id
//创建纹理
OpenGLUtils.glGenTextures(mFrameBufferTextures);
//让fbo与 纹理发生关系
//创建一个 2d的图像
// 目标 2d纹理+等级 + 格式 +宽、高+ 格式 + 数据类型(byte) + 像素数据
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mFrameBufferTextures[0]);GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_RGBA,mOutputWidth,mOutputHeight,0,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE, null);
// 让fbo与纹理绑定起来 , 后续的操作就是在操作fbo与这个纹理上了
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,mFrameBuffers[0]);GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0], 0);
//解绑
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
}
@Override
public int onDrawFrame(int textureId) {
//设置显示窗口
GLES20.glViewport(0, 0, mOutputWidth, mOutputHeight);
//不调用的话就是默认的操作glsurfaceview中的纹理了。显示到屏幕上了
//这里我们还只是把它画到fbo中(缓存)
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,mFrameBuffers[0]);
//使用着色器
GLES20.glUseProgram(mGLProgramId);
//传递坐标
mGLVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0,mGLVertexBuffer);
GLES20.glEnableVertexAttribArray(vPosition);
mGLTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0,mGLTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
//变换矩阵
GLES20.glUniformMatrix4fv(vMatrix,1,false,matrix,0); GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//因为这一层是摄像头后的第一层,所以需要使用扩展的 GL_TEXTURE_EXTERNAL_OES
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
//返回fbo的纹理id
return mFrameBufferTextures[0];
}
public void setMatrix(float[] matrix) {
this.matrix = matrix;
}
}
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
2.渲染时定义一个录制类MediaRecorder
public class DouyinRenderer implements GLSurfaceView.Renderer,
SurfaceTexture.OnFrameAvailableListener {
private ScreenFilter mScreenFilter;
private DouyinView mView;
private CameraHelper mCameraHelper;
private SurfaceTexture mSurfaceTexture;
private float[] mtx = new float[16];
private int[] mTextures;
private CameraFilter mCameraFilter;
private MediaRecorder mMediaRecorder;
public DouyinRenderer(DouyinView douyinView) {
mView = douyinView;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化的操作
mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_BACK);
//准备好摄像头绘制的画布
//通过opengl创建一个纹理id
mTextures = new int[1];
// 这里可以不配置 (当然 配置了也可以)
GLES20.glGenTextures(mTextures.length, mTextures, 0);
mSurfaceTexture = new SurfaceTexture(mTextures[0]);
mSurfaceTexture.setOnFrameAvailableListener(this);
//注意:必须在gl线程操作opengl
mCameraFilter = new CameraFilter(mView.getContext());
mScreenFilter = new ScreenFilter(mView.getContext());
//渲染线程的EGL上下文
EGLContext eglContext = EGL14.eglGetCurrentContext();
mMediaRecorder = new MediaRecorder(mView.getContext(), "/sdcard/a.mp4",CameraHelper.HEIGHT, CameraHelper.WIDTH, eglContext);
}
/**
* 画布发生了改变
*
* @param gl
* @param width
* @param height
*/
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//开启预览
mCameraHelper.startPreview(mSurfaceTexture);
mCameraFilter.onReady(width, height);
mScreenFilter.onReady(width, height);
}
/**
* 开始画画吧
*
* @param gl
*/
@Override
public void onDrawFrame(GL10 gl) {
// 配置屏幕
//清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
GLES20.glClearColor(0, 0, 0, 0);
//执行上一个:glClearColor配置的屏幕颜色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 把摄像头的数据先输出来
// 更新纹理,然后我们才能够使用opengl从
SurfaceTexure当中获得数据 进行渲染
mSurfaceTexture.updateTexImage();
//surfaceTexture 比较特殊,在opengl当中 使用的是特殊的采样器 samplerExternalOES(不是sampler2D)
//获得变换矩阵
mSurfaceTexture.getTransformMatrix(mtx);
mCameraFilter.setMatrix(mtx);
//责任链
int id = mCameraFilter.onDrawFrame(mTextures[0]);
//加效果滤镜
// id = 效果1.onDrawFrame(id);
// id = 效果2.onDrawFrame(id);
//....
//加完之后再显示到屏幕中去
mScreenFilter.onDrawFrame(id);
//进行录制
mMediaRecorder.encodeFrame(id, mSurfaceTexture.getTimestamp());
}
public void onSurfaceDestroyed() {
mCameraHelper.stopPreview();
}
public void startRecord(float speed) {
try {
mMediaRecorder.start(speed);
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopRecord() {
mMediaRecorder.stop();
}
/**
* surfaceTexture 有一个有效的新数据的时候回调
*
* @param surfaceTexture
*/
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mView.requestRender();
}
}
3.录制类MediaRecorder
MediaRecorder
/**
* 录制类
*/
public class MediaRecorder {
private final Context mContext;
private final String mPath;
private final int mWidth;
private final int mHeight;
private final EGLContext mEglContext;
private MediaCodec mMediaCodec;
private Surface mInputSurface;
private MediaMuxer mMediaMuxer;
private Handler mHandler;
private EGLBase mEglBase;
private boolean isStart;
private int index;
private float mSpeed;
/**
* @param context 上下文
* @param path 保存视频的地址
* @param width 视频宽
* @param height 视频高
* 还可以让人家传递帧率 fps、码率等参数
*/
public MediaRecorder(Context context, String path, int width, int height,EGLContext eglContext) {
mContext = context.getApplicationContext();
mPath = path;
mWidth = width;
mHeight = height;
mEglContext = eglContext;
}
/**
* 开始录制视频
* @param speed
*/
public void start(float speed) throws IOException {
mSpeed = speed;
/**
* 配置MediaCodec 编码器
*/
//视频格式
// 类型(avc高级编码 h264) 编码出的宽、高
MediaFormat mediaFormat =MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
//参数配置
// 1500kbs码率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1500_000);
//帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
//关键帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 20);
//颜色格式(RGB\YUV)
//从surface当中回去
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//编码器
mMediaCodec =MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//将参数配置给编码器
mMediaCodec.configure(mediaFormat, null, null,MediaCodec.CONFIGURE_FLAG_ENCODE);
//交给虚拟屏幕 通过opengl 将预览的纹理 绘制到这一个虚拟屏幕中
//这样MediaCodec 就会自动编码 inputSurface 中的图像
mInputSurface = mMediaCodec.createInputSurface();
// H.264
// 播放:
// MP4 -> 解复用 (解封装) -> 解码 -> 绘制
//封装器 复用器
// 一个 mp4 的封装器 将h.264 通过它写出到文件就可以了
mMediaMuxer = new MediaMuxer(mPath,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
/**
* 配置EGL环境
*/
//Handler : 线程通信
// Handler: 子线程通知主线程
// Looper.loop();
HandlerThread handlerThread = new
HandlerThread("VideoCodec");
handlerThread.start();
Looper looper = handlerThread.getLooper();
// 用于其他线程 通知子线程
mHandler = new Handler(looper);
//子线程: EGL的绑定线程 ,对我们自己创建的EGL环境的opengl操作都在这个线程当中执行
mHandler.post(new Runnable() {
@Override
public void run() {
//创建我们的EGL环境 (虚拟设备、EGL上下文等)
mEglBase = new EGLBase(mContext, mWidth, mHeight, mInputSurface,mEglContext);
//启动编码器
mMediaCodec.start();
isStart = true;
}
});
}
/**
* 传递 纹理进来
* 相当于调用一次就有一个新的图像需要编码
*/
public void encodeFrame(final int textureId, final long timestamp) {
if (!isStart) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
//把图像画到虚拟屏幕
mEglBase.draw(textureId, timestamp);
//从编码器的输出缓冲区获取编码后的数据就ok了
getCodec(false);
}
});
}
/**
* 获取编码后 的数据
*
* @param endOfStream 标记是否结束录制
*/
private void getCodec(boolean endOfStream) {
//不录了, 给mediacodec一个标记
if (endOfStream) {
mMediaCodec.signalEndOfInputStream();
}
//输出缓冲区
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 希望将已经编码完的数据都 获取到 然后写出到mp4文件
while (true) {
//等待10 ms
int status = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
//让我们重试 1、需要更多数据 2、可能还没编码为完(需要更多时间)
if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
// 如果是停止 我继续循环
// 继续循环 就表示不会接收到新的等待编码的图像
// 相当于保证mediacodec中所有的待编码的数据都编码完成了,不断地重试 取出编码器中的编码好的数据
// 标记不是停止 ,我们退出 ,下一轮接收到更多数据再来取输出编码后的数据
if (!endOfStream) {
//不写这个 会卡太久了,没有必要 你还是在继续录制的,还能调用这个方法的!
break;
}
//否则继续
} else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
//开始编码 就会调用一次
MediaFormat outputFormat = mMediaCodec.getOutputFormat();
//配置封装器
// 增加一路指定格式的媒体流 视频
index = mMediaMuxer.addTrack(outputFormat);
mMediaMuxer.start();
} else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//忽略
} else {
//成功 取出一个有效的输出
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);
//如果获取的ByteBuffer 是配置信息 ,不需要写出到mp4
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) !=0) {
bufferInfo.size = 0;
}
if (bufferInfo.size != 0) {
bufferInfo.presentationTimeUs = (long)(bufferInfo.presentationTimeUs / mSpeed);
//写到mp4
//根据偏移定位
outputBuffer.position(bufferInfo.offset);
//ByteBuffer 可读写总长度
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
//写出
mMediaMuxer.writeSampleData(index, outputBuffer,bufferInfo);
}
//输出缓冲区 我们就使用完了,可以回收了,让mediacodec继续使用
mMediaCodec.releaseOutputBuffer(status, false);
//结束
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) !=0) {
break;
}
}
}
}
public void stop() {
isStart = false;
mHandler.post(new Runnable() {
@Override
public void run() {
getCodec(true);
mMediaCodec.stop();
mMediaCodec.release();
mMediaCodec = null;
mMediaMuxer.stop();
mMediaMuxer.release();
mMediaMuxer = null;
mEglBase.release();
mEglBase = null;
mInputSurface = null;
mHandler.getLooper().quitSafely();
mHandler = null;
}
});
}
EGLBase
/**
* EGL配置 与 录制的opengl操作 工具类
*/
public class EGLBase {
private ScreenFilter mScreenFilter;
private EGLSurface mEglSurface;
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
/**
* @param context
* @param width
* @param height
* @param surface MediaCodec创建的surface 我们需要将其贴到我们的虚拟屏幕上去
* @param eglContext GLThread的EGL上下文
*/
public EGLBase(Context context, int width, int height, Surface surface,EGLContext eglContext) {
//配置EGL环境
createEGL(eglContext);
//把Surface贴到 mEglDisplay ,发生关系
int[] attrib_list = {
EGL14.EGL_NONE
};
// 绘制线程中的图像 就是往这个mEglSurface 上面去画
mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig,surface, attrib_list, 0);
// 绑定当前线程的显示设备及上下文, 之后操作opengl,就是在这个虚拟显示上操作
if(!EGL14.eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)) {
throw new RuntimeException("eglMakeCurrent 失败!");
}
//像虚拟屏幕画
mScreenFilter = new ScreenFilter(context);
mScreenFilter.onReady(width,height);
}
private void createEGL(EGLContext eglContext) {
//创建 虚拟显示器
mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
//初始化显示器
int[] version = new int[2];
// 12.1020203
//major:主版本 记录在 version[0]
//minor : 子版本 记录在 version[1]
if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
throw new RuntimeException("eglInitialize failed");
}
// egl 根据我们配置的属性 选择一个配置
int[] attrib_list = {
EGL14.EGL_RED_SIZE, 8, // 缓冲区中 红分量 位数
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT, //egl版本 2
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] num_config = new int[1];
// attrib_list:属性列表+属性列表的第几个开始
// configs:获取的配置 (输出参数)
//num_config: 长度和 configs 一样就行了
if (!EGL14.eglChooseConfig(mEglDisplay, attrib_list, 0,configs, 0, configs.length, num_config, 0)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
mEglConfig = configs[0];
int[] ctx_attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, //egl版本 2
EGL14.EGL_NONE
};
//创建EGL上下文
// 3 share_context: 共享上下文 传绘制线程(GLThread)中的EGL上下文 达到共享资源的目的 发生关系
mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, eglContext,ctx_attrib_list, 0);
// 创建失败
if (mEglContext == EGL14.EGL_NO_CONTEXT) {
throw new RuntimeException("EGL Context Error.");
}
}
/**
*
* @param textureId 纹理id 代表一个图片
* @param timestamp 时间戳
*/
public void draw(int textureId,long timestamp){
// 绑定当前线程的显示设备及上下文, 之后操作opengl,就是在这个虚拟显示上操作
if(!EGL14.eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)) {
throw new RuntimeException("eglMakeCurrent 失败!");
}
//画画 画到虚拟屏幕上
mScreenFilter.onDrawFrame(textureId);
//刷新eglsurface的时间戳
EGLExt.eglPresentationTimeANDROID(mEglDisplay,mEglSurface,timestamp);
//交换数据
//EGL的工作模式是双缓存模式, 内部有两个frame buffer (fb)
//当EGL将一个fb 显示屏幕上,另一个就在后台等待opengl进行交换
EGL14.eglSwapBuffers(mEglDisplay,mEglSurface);
}
/**
* 回收
*/
public void release(){
EGL14.eglDestroySurface(mEglDisplay, mEglSurface);
EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE,EGL14.EGL_NO_SURFACE,EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(mEglDisplay, mEglContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEglDisplay);
}
}
相关推荐
- 【互联网那些事】高效开发Android App的10个建议
-
假如要GooglePlay上做一个最失败的案例,那最好的秘诀就是界面奇慢无比、耗电、耗内存。接下来就会得到用户的消极评论,最后名声也就臭了。即使你的应用设计精良、创意无限也没用。 耗电或者内存...
- 手机APP开发方式有哪些? 手机app的开发模式有哪三种?
-
微信小程序开发定制_软件开发_APP开发_网站制作-优软软件开发...
- Android开发入门(一):Android系统简介
-
Android系统是Google公司在2008年推出的一款智能移动设备操作系统,通过不断地版本迭代,目前已经推出到Android11版本了。Android系统广泛应用在手机、平板、电视等各种电子设...
- 物联网app开发流程 物联网app开发工具
-
现在随着科技的发展,很多产品都想用一个手机app去显示他的参数数据或者通过手机app去控制它。但是很多人不知道他的流程。今天我就来说下物联网app开发流程。首先需要把物联网app开发流程分2个步骤,一...
- Android开发进阶 | 如何学习 Android Framework?
-
大部分有“如何学习Framework源码”这个疑问的,应该大都是应用层开发。应用层是被Framework层调用执行的,知道自己的代码是怎么被调用的,才能理解程序的本质,理解本质有助于解决遇到的...
- 快速实现APP混合开发(Hybrid App开发)攻略
-
前言:...
- 三个阶段带你了解一款app开发的完整流程
-
第一个阶段需求阶段:1.需求讨论--开发类型、开发平台、具体的产品功能需求、项目预计完成时间、预算2.需求评估--确认合作后评估具体的预算3.界面设计--设计部门进行产品界面设计,形成效果图...
- Android 开发中文引导-应用小部件
-
应用小部件是可以嵌入其它应用(例如主屏幕)并收到定期更新的微型应用视图。这些视图在用户界面中被叫做小部件,并可以用应用小部件提供者发布。可以容纳其他应用部件的应用组件叫做应用部件的宿主(1)。下面的截...
- 手机软件开发从零开始【Android第2篇Hello】
-
Hello,朋友们我们又见面了。上一篇我们讲到了《Android开发环境搭建【Android基础第1篇】》,错过的朋友可以点击文章末尾的“阅读原文”查看。另外需要下载JDK和ADT-bundle工具的...
- 「全栈工程师之梦的开始--安卓开发(二)」开发安卓app
-
在配置好jdk开发环境、安装好开发工具Androidstudio后,我们就可以开始开发安卓app了。首先,我们需要先了解下android的术语。...
- 二、Android界面开发 android 开发
-
学习目标了解Android常用布局了解Android常用控件...
- 如何开发一款APP既快捷也简便 开发一款app的步骤
-
具体较为简单的步骤可以选择用androidstudio开发app1、打开软件,在菜单中选择file-》newproject打开创建向导。2、配置项目,确定各个名称和存放项目的存放路径;Applic...
- 安卓开发中的“Android高手”,需要具备哪些技术?
-
前言成为一名安卓开发者很容易,但是要成为一名“Android高手”却不那么容易;...
- 移动开发(一):使用.NET MAUI开发第一个安卓APP
-
对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NETMAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是使用VisualStudio开发工具,使用起来也...
- 微软推出PowerApps:零基础开发Win10/iOS/安卓企业应用
-
IT之家讯微软今天面向企业宣布了全新的应用开发解决方案PowerApps,让Windows(包括Win10)、iOS以及安卓应用的开发和分发变得更加简单。PowerApps的用户界面与Office办...
- 一周热门
- 最近发表
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)
- android studio 3 0 (34)
- android开发视频 (30)
- android应用开发 (31)