Android 相机库CameraView源码解析 (四) : 带滤镜预览

1. 前言

这段时间,在使用 natario1/CameraView 来实现带滤镜的预览拍照录像功能。
由于CameraView封装的比较到位,在项目前期,的确为我们节省了不少时间。
但随着项目持续深入,对于CameraView的使用进入深水区,逐渐出现满足不了我们需求的情况。
Github中的issues中,有些BUG作者一直没有修复。

那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。
上篇文章,我们对带滤镜拍照的相关类有了大致的了解,这篇文章我们来看下CameraView是怎么实现带滤镜预览的。

以下源码解析基于CameraView 2.7.2

implementation("com.otaliastudios:cameraview:2.7.2")

为了在博客上更好的展示,本文贴出的代码进行了部分精简

在这里插入图片描述

2. 初始化CameraEngine

这部分逻辑和普通的预览一样 : Android 相机库CameraView源码解析 (一) : 预览 ,这里就略过了。

protected CameraEngine instantiateCameraEngine(Engine engine, CameraEngine.Callback callback) {if (mExperimental && engine == Engine.CAMERA2) {return new Camera2Engine(callback);} else {mEngine = Engine.CAMERA1;return new Camera1Engine(callback);}
}

3. 初始化CameraPreview

这里和不同预览不同的地方,是普通的预览创建的是SurfaceCameraPreview,而使用OpenGL的预览使用的是GlCameraPreview

protected CameraPreview instantiatePreview(@NonNull Preview preview,@NonNull Context context,@NonNull ViewGroup container) {switch (preview) {case SURFACE:return new SurfaceCameraPreview(context, container);case TEXTURE: {if (isHardwareAccelerated()) {// TextureView is not supported without hardware acceleration.return new TextureCameraPreview(context, container);}}case GL_SURFACE:default: {mPreview = Preview.GL_SURFACE;return new GlCameraPreview(context, container);}}
}

4. 初始化GLSurfaceView

GlCameraPreviewonCreateView()方法中,初始化了GLSurfaceView

4.1 初始化布局

在初始化布局中,通过findViewById获得了GLSurfaceView

protected GLSurfaceView onCreateView(@NonNull Context context, @NonNull ViewGroup parent) {ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.cameraview_gl_view, parent, false);final GLSurfaceView glView = root.findViewById(R.id.gl_surface_view);//...省略了代码...在下文中详细说明parent.addView(root, 0);mRootView = root;return glView;
}

4.2 初始化Renderer

这里创建了Renderer类,Renderer是我们这里的关键,下文会详细再讲

final Renderer renderer = instantiateRenderer();
protected Renderer instantiateRenderer() {return new Renderer();
}

4.3 将GlCameraPreview和Renderer建立关联

这里调用了glView.setRenderer,将GlCameraPreviewRenderer建立了关联

glView.setEGLContextClientVersion(2);
glView.setRenderer(renderer);
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

5. Renderer类

Renderer类继承自GLSurfaceView.Renderer,有3个实现方法onSurfaceCreatedonSurfaceChangedonDrawFrame

public interface Renderer {void onSurfaceCreated(GL10 gl, EGLConfig config);void onSurfaceChanged(GL10 gl, int width, int height);void onDrawFrame(GL10 gl);
}

5.1 onSurfaceCreated

onSurfaceCreated里,我们会初始化GlTextureDrawer,并将Filter赋值给GlTextureDrawerGlTextureDrawer是负责绘制的类。
接着,由于我们使用的是GLSurfaceView.RENDERMODE_WHEN_DIRTY,所以要在合适的时机去调用requestRender来通知OpenGL渲染。

public void onSurfaceCreated(GL10 gl, EGLConfig config) {if (mCurrentFilter == null) {mCurrentFilter = new NoFilter();}mOutputTextureDrawer = new GlTextureDrawer();mOutputTextureDrawer.setFilter(mCurrentFilter);final int textureId = mOutputTextureDrawer.getTexture().getId();mInputSurfaceTexture = new SurfaceTexture(textureId);getView().queueEvent(new Runnable() {@Overridepublic void run() {for (RendererFrameCallback callback : mRendererFrameCallbacks) {callback.onRendererTextureCreated(textureId);}}});// Since we are using GLSurfaceView.RENDERMODE_WHEN_DIRTY, we must notify// the SurfaceView of dirtyness, so that it draws again. This is how it's done.mInputSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {getView().requestRender(); // requestRender is thread-safe.}});
}

还有一点,会分发RendererFrameCallback回调的onRendererTextureCreated(),带滤镜拍照、录像都实现了RendererFrameCallback回调,从而来现实拍照和录像的功能。

  • SnapshotGlPictureRecordertake()的时候会添加该回调 : 是用来拍照的。
  • SnapshotVideoRecorder : 是用来录制视频的。

这两个我们后面的文章会讲,这里先略过。

5.2 onSurfaceChanged

5.2.1 设置尺寸

onSurfaceChanged方法中,会调用gl.glViewport,从而确定OpenGL窗口中显示的区域。
然后会调用Filter.setSize(),从而设置滤镜的尺寸。

public void onSurfaceChanged(GL10 gl, final int width, final int height) {gl.glViewport(0, 0, width, height);mCurrentFilter.setSize(width, height);if (!mDispatched) {dispatchOnSurfaceAvailable(width, height);mDispatched = true;} else if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight) {dispatchOnSurfaceSizeChanged(width, height);}
}
5.2.2 裁剪缩放计算

dispatchOnSurfaceAvailable()中,会将宽高赋值给mOutputSurfaceWidthmOutputSurfaceHeight

protected final void dispatchOnSurfaceAvailable(int width, int height) {mOutputSurfaceWidth = width;mOutputSurfaceHeight = height;if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) {crop(mCropCallback);}if (mSurfaceCallback != null) {mSurfaceCallback.onSurfaceAvailable();}
}

并调用crop进行裁剪缩放的计算,这里的mCroppingmCropScaleXmCropScaleY 都会在后面绘制的时候用到。

protected void crop(@Nullable final CropCallback callback) {if (mInputStreamWidth > 0 && mInputStreamHeight > 0 && mOutputSurfaceWidth > 0&& mOutputSurfaceHeight > 0) {float scaleX = 1f, scaleY = 1f;AspectRatio current = AspectRatio.of(mOutputSurfaceWidth, mOutputSurfaceHeight);AspectRatio target = AspectRatio.of(mInputStreamWidth, mInputStreamHeight);if (current.toFloat() >= target.toFloat()) {// We are too short. Must increase height.scaleY = current.toFloat() / target.toFloat();} else {// We must increase width.scaleX = target.toFloat() / current.toFloat();}mCropping = scaleX > 1.02f || scaleY > 1.02f;mCropScaleX = 1F / scaleX;mCropScaleY = 1F / scaleY;getView().requestRender();}if (callback != null) callback.onCrop();
}

5.3 onDrawFrame

在我们调用requestRender()后,就会触发onDrawFrame
onDrawFrame中,会操作OpenGL进行重新的绘制,并渲染到GlSurfaceView上,从而达到预览的效果。

5.3.1 进行裁剪、旋转等操作

这部分获取了transform 矩阵,然后根据之前计算出来的mCroppingmCropScaleXmCropScaleY 等参数进行裁剪和旋转的操作

final float[] transform = mOutputTextureDrawer.getTextureTransform();
mInputSurfaceTexture.updateTexImage();
mInputSurfaceTexture.getTransformMatrix(transform);
// LOG.v("onDrawFrame:", "timestamp:", mInputSurfaceTexture.getTimestamp());// For Camera2, apply the draw rotation.
// See TextureCameraPreview.setDrawRotation() for info.
if (mDrawRotation != 0) {Matrix.translateM(transform, 0, 0.5F, 0.5F, 0);Matrix.rotateM(transform, 0, mDrawRotation, 0, 0, 1);Matrix.translateM(transform, 0, -0.5F, -0.5F, 0);
}if (isCropping()) {// Scaling is easy, but we must also translate before:// If the view is 10x1000 (very tall), it will show only the left strip// of the preview (not the center one).// If the view is 1000x10 (very large), it will show only the bottom strip// of the preview (not the center one).float translX = (1F - mCropScaleX) / 2F;float translY = (1F - mCropScaleY) / 2F;Matrix.translateM(transform, 0, translX, translY, 0);Matrix.scaleM(transform, 0, mCropScaleX, mCropScaleY, 1);
}
5.3.2 进行绘制

接着,调用mOutputTextureDrawer.draw()从而重新进行绘制,并渲染到GlSurfaceView上,从而达到了预览的效果。

 mOutputTextureDrawer.draw(mInputSurfaceTexture.getTimestamp() / 1000L);
5.3.3 分发回调

最后会调用RendererFrameCallback.onRendererFrameRendererFrameCallback我们刚才已经说过了,带滤镜拍照、录像都实现了这个RendererFrameCallback回调,从而来现实拍照和录像的功能,这不是本文的重点,这里我们也先略过,后续文章中会详细讲解。

for (RendererFrameCallback callback : mRendererFrameCallbacks) {callback.onRendererFrame(mInputSurfaceTexture, mDrawRotation, mCropScaleX, mCropScaleY);
}

6. 其他

6.1 CameraView源码解析系列

Android 相机库CameraView源码解析 (一) : 预览-CSDN博客
Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客
Android 相机库CameraView源码解析 (三) : 滤镜相关类说明-CSDN博客
Android 相机库CameraView源码解析 (四) : 带滤镜预览-CSDN博客
Android 相机库CameraView源码解析 (五) : 带滤镜拍照-CSDN博客
Android 相机库CameraView源码解析 (六) : 保存滤镜效果-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/313330.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【十一】【C++\动态规划】1218. 最长定差子序列、873. 最长的斐波那契子序列的长度、1027. 最长等差数列,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利…

【ROS2】MOMO的鱼香ROS2(三)ROS2入门篇——ROS2第一个节点

ROS2第一个节点 引言1 认识ROS2节点1.1 节点之间的交互1.2 节点的命令行指令1.3 工作空间1.4 功能包1.4.1 功能包获取安装1.4.2 功能包相关的指令 ros2 pkg 2 ROS2构建工具—Colcon2.1 安装Colcon2.2 测试编译2.3 Colcon其他指令 3 使用RCLPY编写节点3.1 创建Python功能包3.2 编…

gem5学习(8):创建一个简单的缓存对象--Creating a simple cache object

目录 一、SimpleCache SimObject 二、Implementing the SimpleCache 1、getSlavePort() 2、handleRequest() 3、AccessEvent() 4、accessTiming() (1)缓存命中:sendResponse() (2)缓存未命中: 三、…

idea部署javaSE项目(awt+swing项目)_idea导入eclipse的javaSE项目

一.idea打开项目 选择需要部署的项目 二、设置JDK 三、引入数据库驱动包 四、执行sql脚本 四、修改项目的数据库连接 找到数据库连接文件 五.其他系统实现 JavaSwing实现学生选课管理系统 JavaSwing实现学校教务管理系统 JavaSwingsqlserver学生成绩管理系统 JavaSwing用…

文件分片上传(模拟网盘效果)

文件分片上传(模拟网盘效果) 文章说明简单模拟拖拽文件夹和选择文件的进度条效果效果展示结合后端实现文件上传效果展示加上分片的效果效果展示加上MD5的校验,实现秒传和分片的效果后续开发说明源码下载 文章说明 文章主要为了学习文件上传&a…

Selenium教程04:鼠标+键盘网页的模拟操作

在webdriver 中,鼠标操作都封装在ActionChains类中,使用的时候需要导入这个包。 from selenium.webdriver import ActionChainsActionChains方法列表如下: click(on_elementNone) ——单击鼠标左键click_and_hold(on_elementNone) ——点击…

matlab概率论例子

高斯概率模型: [f,xi] ksdensity(x): returns a probability density estimate, f, for the sample in the vector x. The estimate is based on a normal kernel function, and is evaluated at 100 equally spaced points, xi, that cover the range of the da…

基于grpc从零开始搭建一个准生产分布式应用(8) - 01 - 附:GRPC公共库源码

开始前必读&#xff1a;​​基于grpc从零开始搭建一个准生产分布式应用(0) - quickStart​​ common包中的源码&#xff0c;因后续要用所以一次性全建好了。 一、common工程完整结构 二、引入依赖包 <?xml version"1.0" encoding"UTF-8"?> <p…

【Java 数组解析:探索数组的奇妙世界】

数组的引入 我们先通过一段简单的代码引入数组的概念。 import java.util.Scanner; public class TestArray01{public static void main(String[] args){//功能&#xff1a;键盘录入十个学生的成绩&#xff0c;求和&#xff0c;求平均数&#xff1a;//定义一个求和的变量&…

电机(一):直流有刷电机和舵机

声明&#xff1a;以下图片来自于正点原子&#xff0c;仅做学习笔记使用 电机专题&#xff1a; 直流电机&#xff1a;直流有刷BDC&#xff08;内含电刷&#xff09;&#xff0c;直流无刷BLDC&#xff08;大疆的M3508和M2006&#xff09;,无刷电机有以下三种形式&#xff1a;&a…

软件测试卷王在2024年初开卷。。。

前言 转眼就到了2024年了&#xff0c;工作这几年我的薪资也从12k涨到了18k&#xff0c;对于工作只有3年多的我来说&#xff0c;还是比较满意的&#xff0c;毕竟一些工作4、5年的可能还没我高。 我可能就是大家说的卷王&#xff0c;感觉自己年轻&#xff0c;所以从早干到晚&am…

常用的 MySQL 可视化客户端

数据库可视化客户端&#xff08;GUI&#xff09;让用户在和数据库进行交互时&#xff0c;能直观地查看、创建和修改对象&#xff0c;如&#xff1a;表、行和列。让数据库操作变得更方便了。 今天&#xff0c;我们来了解下目前市场上最常用的 MySQL 可视化客户端。 官方&#x…