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

news2025/1/10 11:54:15

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() {
        @Override
        public 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() {
        @Override
        public 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.coloradmin.cn/o/1359446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Javaweb之Mybatis的基础操作之新增和更新操作的详细解析

1.4 新增 功能:新增员工信息 1.4.1 基本新增 员工表结构: SQL语句: insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (songyuanqiao,宋远桥,1,1.jpg,2,2012-10-09,2,2022-10-…

十年磨一剑

随着不停的优化和改进,JRT开发已经接近尾声,计划过年时候低调发布JRT1.0,框架目标:只做信创下的医疗龙头而不是信创下的苟活着。 十年前,我从南京踏上去沈阳的火车,去东北参加三方协议的启航计划&#xff…

电极箔,预计到2025年市场规模将达到35亿美元

电极箔是一种关键性材料,广泛应用于太阳能电池、电动汽车电池、储能电池、5G基站电池等领域。随着新能源产业的迅猛发展,电极箔市场也在逐步壮大。下面将从全球市场和中国市场进行分析其发展趋势。全球市场分析: 在全球范围内,随着…

HarmonyOS4.0系统性深入开发14AbilityStage组件容器

AbilityStage组件容器 AbilityStage是一个Module级别的组件容器,应用的HAP在首次加载时会创建一个AbilityStage实例,可以对该Module进行初始化等操作。 AbilityStage与Module一一对应,即一个Module拥有一个AbilityStage。 DevEco Studio默…

深入理解Python中的二分查找与bisect模块

💗💗💗欢迎来到我的博客,你将找到有关如何使用技术解决问题的文章,也会找到某个技术的学习路线。无论你是何种职业,我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章,也欢…

PCIe 6.0生态业内进展分析总结-2

3.PCIe 6.0协议分析仪 (1)Keysight Keysight是德科技在2023年6月份对外宣布,第一款支持PCIe 6.0协议验证调试工具。 Keysight PCIe 6.0架构解决方案具备以下特点: 分析PCIe 6.0技术设计的数据链路/事务层 支持所有PCIe技术速率——从2.5 GT/s至64 GT/…

一篇文章认识微服务中Eureka的原理和服务注册与发现

目录 1、认识Eureka 2、Eureka原理 2.1 和Dubbo架构对比: 2.2 三大角色 3、微服务常见的注册中心 3.1 Zookeeper 3.2 Eureka 3.3 Consul 3.4 Nacos 3.5 区别 Netflix 在设计Eureka 时,遵循的就是AP原则。 CAP原则又称CAP定理,指的…

STM32使用中断方式进行USART数据收发以及printf函数的重写

时间记录:2024/1/5 一、USART/UART介绍 协议介绍 (1)起始位,一位逻辑电平0表示 (2)数据位,8-9位,逻辑高低电平,一般使用8位 (3)校验位&#xff…

5分钟搞懂AI的可解释性

大家好啊,我是董董灿。 想象一下,如果有一天,有人跑过来突然告诉你,他搞懂了人类大脑记忆的运行机制,你会是什么反应? 你可能会和我一样,把他当做疯子。 因为我觉得这个课题太深奥了&#xf…

kali-Linux安装ARL灯塔教程以及timeout of 20000ms exceeded 的解决方法

FLAG:别和妈妈诉苦,她帮不上,也睡不着。 专研方向: docker,ARL资产灯塔系统 每日emo:天冷了,你还在坚持吗? 欢迎各位与我这个菜鸟交流学习 kali安装ARL灯塔教程 1.安装docker环境,…

【智慧零售】东胜物联蓝牙网关硬件解决方案,促进零售门店数字化管理

依托物联网(IoT)、大数据、人工智能(AI)等快速发展,数字化和智能化已成为零售企业的核心竞争力。更多的企业通过引入人工智能、大数据等先进技术手段,提高门店运营效率和服务质量。 某连锁咖啡企业牢牢抓住…

位运算 (运算符)

文章目录 位运算位运算概述位运算概览& 按位与&#xff08;AND&#xff09;| 按位或&#xff08;bitwise OR&#xff09;^ 按位异或&#xff08;bitwise XOR&#xff09;~ 按位非&#xff08;bitwise NOT&#xff09;<< 左移&#xff08;bitwise shift left&#xff…

zookeeper应用场景之分布式的ID生成器

1. 分布式ID生成器的使用场景 在分布式系统中&#xff0c;分布式ID生成器的使用场景非常之多&#xff1a; 大量的数据记录&#xff0c;需要分布式ID。大量的系统消息&#xff0c;需要分布式ID。大量的请求日志&#xff0c;如restful的操作记录&#xff0c;需要唯一标识&#x…

分布式(7)

目录 31.基于Zookeeper如何实现分布式锁&#xff1f; 32.什么是ACID&#xff1f; 33.什么是分布式的XA协议&#xff1f; 34.什么是2PC&#xff1f; 35.什么是3PC&#xff1f; 31.基于Zookeeper如何实现分布式锁&#xff1f; 顺序节点 创建一个用于发号的节点“/test/lock…

Mybatis缓存实现方式

文章目录 装饰器模式Cache 接口及核心实现Cache 接口装饰器1. BlockingCache2. FifoCache3. LruCache4. SoftCache5. WeakCache 小结 缓存是优化数据库性能的常用手段之一&#xff0c;我们在实践中经常使用的是 Memcached、Redis 等外部缓存组件&#xff0c;很多持久化框架提供…

Unity3D Shader 之透视效果XRay

1、 Shader "Unlit/XRay" {Properties{_MainTex("Texture", 2D) "white" {}// 漫反射_Diffuse("Diffuse", COLOR) (1,1,1,1)// XRay 效果_XRayColor("XRay Color", COLOR) (0,1,1,1)_XRayPower("XRay Power",…

python识别验证码+灰度图片base64转换图片

一、为后面识别验证码准备 1、base64转换为图片&#xff0c;保存本地、并且置灰 上文中的base64,后面的就是包含Base64编码的PNG图像的字符串复制下来 import base64 from PIL import Image import io# 这里是你的Base64编码的字符串 base64_data "iVBORw0KGgoAAAANSUhE…

记录汇川:水塔指令解释-ST

可以通过帮助查看指令手册 PLC的IO地址映射-两种方法 第一种&#xff1a; 新建一个全局变量表&#xff0c;按照如图所示建立IO地址 第二种&#xff1a; 直接如图所示位置定义名字 注意&#xff1a;IW和QB这两个前面一个有蓝色M一个没有。 蓝色的M表示模块发生变化的时候地址不会…

不用愁企业内部知识库搭建啦,照着这样做轻松解决

在现代企业中&#xff0c;知识是一项宝贵的资源。拥有一个完善的内部知识库可以帮助企业有效地管理和分享知识&#xff0c;提高团队的协作效率&#xff0c;促进创新和发展。然而&#xff0c;对于很多企业来说&#xff0c;搭建一个高效的知识库可能会成为一项具有挑战性的任务。…

【docker】cgroups资源限制

目录 一、cpu资源控制 1、 设置cpu使用率上限 2、设置cpu资源占用比&#xff08;设置多个容器时才有效&#xff09;Docker通过–cpu-shares指定cpu份额&#xff0c;默认为1024&#xff0c;值为1024的倍数。 3、设置容器绑定指定的CPU 三、内存资源控制 四、磁盘IO配额控制…