赞
踩
Android OpenGL+Camera2渲染(1) —— OpenGL简单介绍
Android OpenGL+Camera2渲染(2) —— OpenGL实现Camera2图像预览
Android OpenGL+Camera2渲染(3) —— 大眼,贴纸功能实现
Android OpenGL+Camera2渲染(4) —— 美颜功能实现
Android OpenGL+Camera2渲染(5) —— 录制视频,实现快录慢录
这篇文章是在前面几篇的基础上实现的,把美颜,大眼,贴纸的效果,使用MediaCodec编码后录制成视频,支持快录和慢录的功能。
首先 我们进过处理之后的数据是存在纹理当中的,而MediaCodec 编码的输入队列支持把surface作为输入源,那么这两者如何关联呢? 方案是这样的。 自己配置一个EGL环境,把这个EGL环境中的EGLSurface和mediacodec的surface绑定,把当前的EGL环境和我们处理美颜滤镜的EGL绑定,这样就可以在当前的EGL环境中操作美颜滤镜的EGL环境中的纹理了。然后把数据输出的MediaCodec的surface中。
在开始录制的时候,取初始化编码器MediaCodec、MediaMuxer 和 配置EGL环境进行绑定。
- public void start(float speed, String savePath) {
- mSavePath = savePath;
- mSpeed = speed;
-
- try {
- MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
-
- mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-
- mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mWidth * mHeight * fps / 5);
-
- mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
-
- mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, fps);
-
- mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
-
- mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-
- inputSurface = mediaCodec.createInputSurface();
-
- mediaMuxer = new MediaMuxer(mSavePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-
-
- //-------------配置EGL环境----------------
-
- HandlerThread handlerThread = new HandlerThread("elgCodec");
- handlerThread.start();
-
- mHandler = new Handler(handlerThread.getLooper());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- eglConfigBase = new EglConfigBase(mContext, mWidth, mHeight, inputSurface, eglContext);
- mediaCodec.start();
- isPlaying = true;
- }
- });
-
- } catch (IOException e) {
- e.printStackTrace();
- }
-
-
- }
这里传入一个speed,这个就和快录慢录有关系了,这个之后在讲。这里创建MediaCodec,通过
mediaCodec.createInputSurface()拿到了一个输入的Surface,使用 HandlerThread 创建了一个在子线程中处理的Handler,在这个线程中创建EGL环境。
这里传入的 eglContext 其实就是美颜大眼的EGLContext,通过它产生关联。
- /**
- * @param context
- * @param width
- * @param height
- * @param surface MediaCodec创建的surface 我们需要将其贴到我们的虚拟屏幕上去
- * @param eglContext GLThread的EGL上下文
- */
- public EglConfigBase(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.");
- }
- }
这里的代码可以参考GlSurfaceView中的配置去写,发生关系就是在
mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, attrib_list, 0);
这句产生的关联。
这里还创建了 ScreenFilter,用它 把外部的纹理信息(美颜滤镜后的纹理)写入到MediaCodec的Surface中。
- public void draw(int textureId, long timestamp) {
- if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, mCurrentEglContext)) {
- throw new RuntimeException("eglMakeCurrent 失败!");
- }
-
-
- screenFilter.onDrawFrame(textureId);
-
- EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, timestamp);
- //交换数据,输出到mediacodec InputSurface中
- EGL14.eglSwapBuffers(eglDisplay, eglSurface);
-
- }
在写之前,需要绑定当前的EGL 环境,这里需要传入时间戳,在调用了screenFilter.onDrawFrame(textureId); 方法之后,需要刷新eglSurface的时间戳, eglSwapBuffers 交换数据, EGL的工作模式是双缓存模式, 内部有两个frame buffer (fb),当EGL将一个fb 显示屏幕上,另一个就在后台等待opengl进行交换。所以draw完之后,需要把后台的buffer显示到前台。
在 GlRenderWrapper 的 onDrawFrame
- @Override
- public void onDrawFrame(GL10 gl) {
- int textureId;
- // 配置屏幕
- //清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
- GLES20.glClearColor(0, 0, 0, 0);
- //执行上一个:glClearColor配置的屏幕颜色
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-
- //更新获取一张图
- mSurfaceTexture.updateTexImage();
-
- mSurfaceTexture.getTransformMatrix(mtx);
- //cameraFiler需要一个矩阵,是Surface和我们手机屏幕的一个坐标之间的关系
- cameraFilter.setMatrix(mtx);
-
- textureId = cameraFilter.onDrawFrame(mTextures[0]);
- if (bigEyeEnable) {
- bigeyeFilter.setFace(tracker.mFace);
- textureId = bigeyeFilter.onDrawFrame(textureId);
- }
-
- if (beautyEnable) {
- textureId = beaytyFilter.onDrawFrame(textureId);
- }
-
- if (stickEnable) {
- stickerFilter.setFace(tracker.mFace);
- textureId = stickerFilter.onDrawFrame(textureId);
- }
-
- int id = screenFilter.onDrawFrame(textureId);
- //进行录制
- avcRecorder.encodeFrame(id, mSurfaceTexture.getTimestamp());
-
- }
最后就是把纹理ID,拿去录制。
- public void encodeFrame(final int textureId, final long timeStamp) {
-
- if (!isPlaying) return;
-
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- eglConfigBase.draw(textureId, timeStamp);
- getCodec(false);
- }
- });
-
- }
这里的Handler 就是刚刚创建EGL环境的Handler,所有此EGL环境中的处理都需要在此Handler线程中进行。执行eglConfigBase.draw 去写入到MediaCodec的Surface中。
- private void getCodec(boolean endOfStream) {
- if (endOfStream) {
- mediaCodec.signalEndOfInputStream();
- }
-
- MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-
- int status = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
- //表示请求超时,10_000毫秒内没有数据到来
- if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
-
- } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- //编码格式改变 ,第一次start 都会调用一次
- MediaFormat outputFormat = mediaCodec.getOutputFormat();
- //设置mediaMuxer 的视频轨
- avcIndex = mediaMuxer.addTrack(outputFormat);
- mediaMuxer.start();
- } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- //Outputbuffer 改变了
- } else {
- ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(status);
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
- bufferInfo.size = 0;
- }
-
-
- if (bufferInfo.size > 0) {
- bufferInfo.presentationTimeUs = (long) (bufferInfo.presentationTimeUs / mSpeed);
- outputBuffer.position(bufferInfo.offset);
-
- outputBuffer.limit(bufferInfo.size - bufferInfo.offset);
- //交给mediaMuxer 保存
- mediaMuxer.writeSampleData(avcIndex, outputBuffer, bufferInfo);
-
- }
-
- mediaCodec.releaseOutputBuffer(status, false);
-
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-
- }
- }
-
- }
在第28行, bufferInfo.presentationTimeUs = (long) (bufferInfo.presentationTimeUs / mSpeed); 把正常的pts时间戳做一个转换,这里的pts是一个时间段的相对时间戳,如果要实现快录,speed设置一个小于1的值,慢录就设置一个大于1的值。在这里上面提到startRecord的时候会传递进来一个mSpeed,是这样设置的。
- public void startRecord() {
- float speed = 1.f;
- switch (mSpeed) {
- case MODE_EXTRA_SLOW:
- speed = 0.3f;
- break;
- case MODE_SLOW:
- speed = 0.5f;
- break;
- case MODE_NORMAL:
- speed = 1.f;
- break;
- case MODE_FAST:
- speed = 1.5f;
- break;
- case MODE_EXTRA_FAST:
- speed = 3.f;
- break;
- }
- glRender.startRecord(speed, savePath);
- }
- public void stop() {
- isPlaying = false;
-
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- getCodec(true);
- mediaCodec.stop();
- mediaCodec.release();
- mediaCodec = null;
- mediaMuxer.stop();
- mediaMuxer.release();
- mediaMuxer = null;
- eglConfigBase.release();
- eglConfigBase = null;
- inputSurface.release();
- inputSurface = null;
- mHandler.getLooper().quitSafely();
- mHandler = null;
- if (onRecordListener != null) {
- onRecordListener.recordFinish(mSavePath);
- }
- }
- });
- }
github项目地址:https://github.com/wangchao0837/OpenGlCameraRender
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。