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
在GlCameraPreview
的onCreateView()
方法中,初始化了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
,将GlCameraPreview
和Renderer
建立了关联
java
glView.setEGLContextClientVersion(2);
glView.setRenderer(renderer);
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
5. Renderer类
Renderer
类继承自GLSurfaceView.Renderer
,有3
个实现方法onSurfaceCreated
、onSurfaceChanged
、onDrawFrame
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
赋值给GlTextureDrawer
,GlTextureDrawer
是负责绘制的类。
接着,由于我们使用的是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
回调,从而来现实拍照和录像的功能。
SnapshotGlPictureRecorder
中take()
的时候会添加该回调 : 是用来拍照的。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()
中,会将宽高赋值给mOutputSurfaceWidth
和mOutputSurfaceHeight
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
进行裁剪缩放的计算,这里的mCropping
、mCropScaleX
、mCropScaleY
都会在后面绘制的时候用到。
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
矩阵,然后根据之前计算出来的mCropping
、mCropScaleX
、mCropScaleY
等参数进行裁剪和旋转的操作
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.onRendererFrame
,RendererFrameCallback
我们刚才已经说过了,带滤镜拍照、录像都实现了这个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博客