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

1. 前言

这段时间,在使用 natario1/CameraView 来实现带滤镜的预览拍照录像功能。

由于CameraView封装的比较到位,在项目前期,的确为我们节省了不少时间。

但随着项目持续深入,对于CameraView的使用进入深水区,逐渐出现满足不了我们需求的情况。
Github中的issues中,有些BUG作者一直没有修复。

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

以下源码解析基于CameraView 2.7.2

kotlin 复制代码
implementation("com.otaliastudios:cameraview:2.7.2")

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

2. 初始化CameraEngine

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

java 复制代码
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

java 复制代码
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

java 复制代码
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是我们这里的关键,下文会详细再讲

java 复制代码
final Renderer renderer = instantiateRenderer();
java 复制代码
protected Renderer instantiateRenderer() {
    return new Renderer();
}

4.3 将GlCameraPreview和Renderer建立关联

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

java 复制代码
glView.setEGLContextClientVersion(2);
glView.setRenderer(renderer);
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

5. Renderer类

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

java 复制代码
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渲染。

java 复制代码
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(),从而设置滤镜的尺寸。

java 复制代码
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

java 复制代码
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 都会在后面绘制的时候用到。

java 复制代码
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 等参数进行裁剪和旋转的操作

java 复制代码
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上,从而达到了预览的效果。

java 复制代码
 mOutputTextureDrawer.draw(mInputSurfaceTexture.getTimestamp() / 1000L);
5.3.3 分发回调

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

java 复制代码
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博客

相关推荐
NotesChapter41 分钟前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快2 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl2 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江2 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-3 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记
佛系小嘟嘟3 小时前
Android Studio不显示需要的tag日志解决办法《All logs entries are hidden by the filter》
android·ide·android studio
mariokkm3 小时前
Django一分钟:django中收集关联对象关联数据的方法
android·django·sqlite
长亭外的少年4 小时前
如何查看 Android 项目的依赖结构树
android
深海呐5 小时前
Android 从本地选择视频,用APP播放或进行其他处理
android·音视频·从本地选择视频,用app播放·从本地选择视频,并拿到信息·跳转到本地视频列表
深海呐5 小时前
Android Google登录接入
android·google登录接入·android 谷歌登录接入·google登录·android google