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

2024-01-01 13:52

本文主要是介绍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博客

这篇关于Android 相机库CameraView源码解析 (四) : 带滤镜预览的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/559448

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄