Android 相机库CameraView源码解析 (四) : 带滤镜预览 太过爱你忘了你带给我的痛 2024-04-25 20:24 80阅读 0赞 ### 1. 前言 ### 这段时间,在使用 [natario1/CameraView][natario1_CameraView] 来实现带滤镜的`预览`、`拍照`、`录像`功能。 由于`CameraView`封装的比较到位,在项目前期,的确为我们节省了不少时间。 但随着项目持续深入,对于`CameraView`的使用进入深水区,逐渐出现满足不了我们需求的情况。 `Github`中的`issues`中,有些`BUG`作者一直没有修复。 那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。 [上篇文章][Link 1],我们对带滤镜拍照的相关类有了大致的了解,这篇文章我们来看下`CameraView`是怎么实现带滤镜预览的。 以下源码解析基于`CameraView 2.7.2` implementation("com.otaliastudios:cameraview:2.7.2") > 为了在博客上更好的展示,本文贴出的代码进行了部分精简 ![在这里插入图片描述][c92f5c122cb6428ba2fa01c2f4421049.png] ### 2. 初始化CameraEngine ### 这部分逻辑和普通的预览一样 : [Android 相机库CameraView源码解析 (一) : 预览][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 ### 在`GlCameraPreview`的`onCreateView()`方法中,初始化了`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`,将`GlCameraPreview`和`Renderer`建立了关联 glView.setEGLContextClientVersion(2); glView.setRenderer(renderer); glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); ### 5. Renderer类 ### `Renderer`类继承自`GLSurfaceView.Renderer`,有`3`个实现方法`onSurfaceCreated`、`onSurfaceChanged`、`onDrawFrame` 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`赋值给`GlTextureDrawer`,`GlTextureDrawer`是负责绘制的类。 接着,由于我们使用的是`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`回调,从而来现实拍照和录像的功能。 * `SnapshotGlPictureRecorder`中`take()`的时候会添加该回调 : 是用来拍照的。 * `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()`中,会将宽高赋值给`mOutputSurfaceWidth`和`mOutputSurfaceHeight` 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`进行裁剪缩放的计算,这里的`mCropping`、`mCropScaleX`、`mCropScaleY` 都会在后面绘制的时候用到。 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`矩阵,然后根据之前计算出来的`mCropping`、`mCropScaleX`、`mCropScaleY` 等参数进行裁剪和旋转的操作 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.onRendererFrame`,`RendererFrameCallback`我们刚才已经说过了,带滤镜拍照、录像都实现了这个`RendererFrameCallback`回调,从而来现实拍照和录像的功能,这不是本文的重点,这里我们也先略过,后续文章中会详细讲解。 for (RendererFrameCallback callback : mRendererFrameCallbacks) { callback.onRendererFrame(mInputSurfaceTexture, mDrawRotation, mCropScaleX, mCropScaleY); } ### 6. 其他 ### #### 6.1 CameraView源码解析系列 #### [Android 相机库CameraView源码解析 (一) : 预览-CSDN博客][Android _CameraView_ _ _] [Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN] [Android 相机库CameraView源码解析 (三) : 滤镜相关类说明-CSDN博客][Link 1] [Android 相机库CameraView源码解析 (四) : 带滤镜预览-CSDN博客][Android _CameraView_ _ _ _-CSDN 1] [Android 相机库CameraView源码解析 (五) : 带滤镜拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN 2] [Android 相机库CameraView源码解析 (六) : 保存滤镜效果-CSDN博客][Android _CameraView_ _ _ _-CSDN 3] [natario1_CameraView]: https://github.com/natario1/CameraView [Link 1]: https://blog.csdn.net/EthanCo/article/details/134517249 [c92f5c122cb6428ba2fa01c2f4421049.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/5507fa6ba7a14dc1b7c7d063ba012618.png [Android _CameraView_ _ _]: https://blog.csdn.net/EthanCo/article/details/134511622 [Android _CameraView_ _ _ _-CSDN]: https://blog.csdn.net/EthanCo/article/details/134545086 [Android _CameraView_ _ _ _-CSDN 1]: https://blog.csdn.net/EthanCo/article/details/135202176 [Android _CameraView_ _ _ _-CSDN 2]: https://blog.csdn.net/EthanCo/article/details/134517154 [Android _CameraView_ _ _ _-CSDN 3]: https://blog.csdn.net/EthanCo/article/details/134691849
相关 Android 相机库CameraView源码解析 (四) : 带滤镜预览 Android 相机库CameraView源码解析 (四) : 带滤镜预览 太过爱你忘了你带给我的痛/ 2024年04月25日 20:24/ 0 赞/ 81 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 我不是女神ヾ/ 2024年04月25日 20:24/ 0 赞/ 78 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG 解决相机库CameraView多滤镜拍照错乱的BUG 女爷i/ 2024年04月25日 20:24/ 0 赞/ 88 阅读
相关 为什么相机库CameraView预览和拍照的效果不一致 ? 从源码解析 : 为什么CameraView预览和拍照的效果会不一致呢 ? 我就是我/ 2024年04月25日 20:24/ 0 赞/ 86 阅读
相关 Android 相机库CameraView源码解析 (六) : 保存滤镜效果 Android 相机库CameraView源码解析 : 保存滤镜效果部分 约定不等于承诺〃/ 2024年04月25日 20:24/ 0 赞/ 91 阅读
相关 Android 相机库CameraView源码解析 (二) : 拍照 Android 相机库CameraView源码解析 : 拍照部分 ╰+哭是因爲堅強的太久メ/ 2024年04月25日 20:24/ 0 赞/ 77 阅读
相关 Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 Android 相机库CameraView源码解析 : 滤镜相关类说明 - 日理万妓/ 2024年04月25日 20:24/ 0 赞/ 85 阅读
相关 Android 相机库CameraView源码解析 (一) : 预览 Android 相机库CameraView源码解析 : 预览部分 青旅半醒/ 2024年04月25日 20:23/ 0 赞/ 73 阅读
相关 Android 相机库CameraView源码解析 (五) : 带滤镜拍照 Android 相机库CameraView源码解析 : 带滤镜拍照 我不是女神ヾ/ 2024年04月25日 20:23/ 0 赞/ 101 阅读
相关 直播预览层添加滤镜效果(CIFilter使用场景) 直播预览层添加滤镜效果 原理,在显示之前,提前对图片进行滤镜处理,把处理后的图片展示出来就好了. CIFiter(滤镜类):给图片添加特殊效果(模糊,高亮等等). ゝ一纸荒年。/ 2022年06月16日 13:37/ 0 赞/ 143 阅读
还没有评论,来说两句吧...