这段时间,在使用 natario1/CameraView 来实现带滤镜的预览
、拍照
、录像
功能。
由于CameraView
封装的比较到位,在项目前期,的确为我们节省了不少时间。
但随着项目持续深入,对于CameraView
的使用进入深水区,逐渐出现满足不了我们需求的情况。
Github
中的issues
中,有些BUG
作者一直没有修复。
那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。
而这篇文章是其中关于CameraView
怎么进行预览的源码解析。
以下源码解析基于CameraView 2.7.2
implementation("com.otaliastudios:cameraview:2.7.2")
为了在博客上更好的展示,本文贴出的代码进行了部分精简
在CameraView
构造方法中,会调用doInstantiateEngine
,用来初始化CameraEngine
。
CameraEngine
是一个抽象类,根据我们的配置分别返回Camera1Engine
和Camera2Engine
。
可以看到,这里mExperimental
成立并且engine
等于CAMERA2
的情况下,会返回Camera2Engine
。
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);
}
}
所以如果我们要使用Camera2 API
,需要配置上app:cameraEngine="camera2"
和app:cameraExperimental="true"
<com.otaliastudios.cameraview.CameraView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cameraEngine="camera2"
app:cameraExperimental="true" />
在CameraView
调用View
生命周期中的onAttachedToWindow
的时候,调用了doInstantiatePreview()
方法,初始化预览相关代码
void doInstantiatePreview() {
mCameraPreview = instantiatePreview(mPreview, getContext(), this);
mCameraEngine.setPreview(mCameraPreview);
if (mPendingFilter != null) {
setFilter(mPendingFilter);
mPendingFilter = null;
}
}
mCameraPreview
是CameraPreview
抽象类,根据xml
中不同的app:cameraPreview
配置会创建不同的CameraPreview
surface
: 创建SurfaceCameraPreview
texture
: 创建TextureCameraPreview
glSurface
: 创建GlCameraPreview
这里以surface
为例,实际创建的CameraPreview
是SurfaceCameraPreview
,其职责就是在初始化方法的时候,通过LayoutInflater.from
创建SurfaceView
,并封装了SurfaceHolder.Callback
回调。
public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHolder> {
//...省略了部分代码...
@Override
protected SurfaceView onCreateView(@NonNull Context context, @NonNull ViewGroup parent) {
View root = LayoutInflater.from(context).inflate(R.layout.cameraview_surface_view, parent, false);
parent.addView(root, 0);
SurfaceView surfaceView = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = surfaceView.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
//...
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (!mDispatched) {
dispatchOnSurfaceAvailable(width, height);
mDispatched = true;
} else {
dispatchOnSurfaceSizeChanged(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback: surfaceDestroyed");
dispatchOnSurfaceDestroyed();
mDispatched = false;
}
});
mRootView = root;
return surfaceView;
}
}
接着,调用mCameraEngine.setPreview(mCameraPreview);
,mCameraEngine
内部设置了SurfaceCallback
回调,SurfaceCallback
回调有onSurfaceAvailable
、onSurfaceChanged
、onSurfaceDestroyed
三个方法。
public final void setPreview(@NonNull CameraPreview cameraPreview) {
if (mPreview != null) mPreview.setSurfaceCallback(null);
mPreview = cameraPreview;
mPreview.setSurfaceCallback(this);
}
在CameraBaseEngine
的onSurfaceAvailable
回调中,调用了startBind()->onStartBind()
和startPreview()->onStartPreview()
@Override
public final void onSurfaceAvailable() {
startBind();
startPreview();
}
onStartBind
和onStartPreview
是抽象方法,Camera1Engine
和Camera2Engine
分别实现了CameraBaseEngine
,分别用来实现Camera1
和Camera2
。
@NonNull
@EngineThread
protected abstract Task<Void> onStartBind();
@NonNull
@EngineThread
protected abstract Task<Void> onStartPreview();
这里以Camera2
为例,对于Camera2
不了解的同学,可以先看我的另一篇博客 : 十分钟实现 Android Camera2 相机预览 。
接下来的部分,就是Camera2 API
绑定Surface
和预览的具体实现了。
分别估算出预览和拍照的图像尺寸,为后续预览和拍照做准备
//估算出拍照时图像的尺寸
mCaptureSize = computeCaptureSize();
//估算出预览时的图像尺寸
mPreviewStreamSize = computePreviewStreamSize();
接着调用setFixedSize
,给SurfaceView
设置刚才估算出来的预览的尺寸 ;
然后调用outputSurfaces.add
,将CameraView
的SurfaceHolder
添加到outputSurfaces
列表中。
List<Surface> outputSurfaces = new ArrayList<>();
final Class outputClass = mPreview.getOutputClass();
final Object output = mPreview.getOutput();
if (outputClass == SurfaceHolder.class) {
Tasks.await(Tasks.call(new Callable<Void>() {
@Override
public Void call() {
//必须在UI线程调用
((SurfaceHolder) output).setFixedSize(
mPreviewStreamSize.getWidth(),
mPreviewStreamSize.getHeight());
return null;
}
}));
mPreviewStreamSurface = ((SurfaceHolder) output).getSurface();
} else if (outputClass == SurfaceTexture.class) {
//...省略了关于SurfaceTexture的实现...
} else {
throw new RuntimeException("Unknown CameraPreview output class.");
}
outputSurfaces.add(mPreviewStreamSurface);
如果是Video
模式,那么会初始化Full2VideoRecorder
,这个专门用来在Camera2
中录制视频的类。
接着在outputSurfaces
列表中添加Full2VideoRecorder
单独创建的Surface
。( 实质就是从mMediaRecorder.getSurface()
中获取的Surface
)
if (getMode() == Mode.VIDEO) {
if (mFullVideoPendingStub != null) {
Full2VideoRecorder recorder = new Full2VideoRecorder(this, mCameraId);
try {
outputSurfaces.add(recorder.createInputSurface(mFullVideoPendingStub));
} catch (Full2VideoRecorder.PrepareException e) {
throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT);
}
mVideoRecorder = recorder;
}
}
如果是Picture
模式,则会先判断设置的图像格式,如果不是JPEG
或DNG
,则抛出异常,说明不支持该格式。
接着会根据mCaptureSize
拍照尺寸和图片格式,创建mPictureReader
,这个类是Camera2
中拍照要用到的类,用来获取相机捕获的图像数据。
接着会将mPictureReader
中的Surface
也添加到outputSurfaces
列表中。
if (getMode() == Mode.PICTURE) {
int format;
switch (mPictureFormat) {
case JPEG: format = ImageFormat.JPEG; break;
case DNG: format = ImageFormat.RAW_SENSOR; break;
default: throw new IllegalArgumentException("Unknown format:" + mPictureFormat);
}
mPictureReader = ImageReader.newInstance(
mCaptureSize.getWidth(),
mCaptureSize.getHeight(),
format, 2);
outputSurfaces.add(mPictureReader.getSurface());
}
创建一个帧处理的ImageReader
,名字叫做mFrameProcessingReader
。
并将其添加到outputSurfaces
列表中。
if (hasFrameProcessors()) {
mFrameProcessingSize = computeFrameProcessingSize();
/**
* 很难把原因写出来,但是在Camera2中,我们需要的帧数比图像数少1。
* 如果我们让所有图像都成为帧的一部分,从而让所有图像在任何给定时刻被处理器使用,Camera2输出就会中断。
* 事实上,如果没有可用的图像,传感器会阻塞,直到它找到一个图像,这是一个大问题,因为处理器时间成为预览的瓶颈。
* 这是ImageReader / sensor实现中的一个设计缺陷,因为如果没有可用的图像,它们应该简单地将写入的帧放置到surface上。
* 由于这不是事情的工作方式,我们确保在这里始终有一个图像可用。
*/
mFrameProcessingReader = ImageReader.newInstance(
mFrameProcessingSize.getWidth(),
mFrameProcessingSize.getHeight(),
mFrameProcessingFormat,
getFrameProcessingPoolSize() + 1);
mFrameProcessingReader.setOnImageAvailableListener(this,
null);
mFrameProcessingSurface = mFrameProcessingReader.getSurface();
outputSurfaces.add(mFrameProcessingSurface);
} else {
mFrameProcessingReader = null;
mFrameProcessingSize = null;
mFrameProcessingSurface = null;
}
这里特别需要注意的是这个ImageReader
调用了setOnImageAvailableListener
,当有图像数据时,就会回调onImageAvailable
方法。
这里会从reader.acquireLatestImage()
中获取到android.media.Image
对象,然后将其组装成com.otaliastudios.cameraview.frame.Frame
对象,接着调用getCallback().dispatchFrame(frame);
来进行回调。
@EngineThread
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (getState() == CameraState.PREVIEW && !isChangingState()) {
Frame frame = getFrameManager().getFrame(image, System.currentTimeMillis());
if (frame != null) {
getCallback().dispatchFrame(frame);
}
} else {
image.close();
}
}
这个getCallback()
是在哪里设置的呢 ? CameraView
中有addFrameProcessor
方法,专门用来设置这个回调。
public void addFrameProcessor(@Nullable FrameProcessor processor) {
if (processor != null) {
mFrameProcessors.add(processor);
if (mFrameProcessors.size() == 1) {
mCameraEngine.setHasFrameProcessors(true);
}
}
}
所以我们想要取到预览时候的实时帧数据,就在自己Activity
的代码中,添加这个回调就行。
cameraView.addFrameProcessor {
//预览每一帧的回调
val image = it.getData<Image>()
Log.i(TAG, "image width:${image.width} height:${image.height}")
image.close()
}
这里就是根据outputSurfaces
列表,创建对应的android.hardware.camera2.CameraCaptureSession
,从而实现关联对应功能的Surface
。
mCamera.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mSession = session;
task.trySetResult(null);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
//...省略了配置失败的代码...
}
}, null);
首先调用回调方法onCameraPreviewStreamSizeChanged()
,内部会去调用下requestLayout()
,从而触发onMeasure
来重新测量CameraView
尺寸。
getCallback().onCameraPreviewStreamSizeChanged();
CameraPreview
这个previewSizeForView
就是在onStartBind()
中估算出来的预览大小,并且会对传感器的方向做翻转操作。并将其设置到mPreview
中,CameraPreview
是一个抽象类,具体实现类有SurfaceCameraPreview
、TextureCameraPreview
、GlCameraPreview
,这里以SurfaceCameraPreview
为例。
Size previewSizeForView = getPreviewStreamSize(Reference.VIEW);
//设置预览尺寸大小
mPreview.setStreamSize(previewSizeForView.getWidth(), previewSizeForView.getHeight());
//给mPreview设置绘制的方向
mPreview.setDrawRotation(getAngles().offset(Reference.BASE, Reference.VIEW, Axis.ABSOLUTE));
if (hasFrameProcessors()) {
//如果有FrameProcessors,那么初始化FrameManager
getFrameManager().setUp(mFrameProcessingFormat, mFrameProcessingSize, getAngles());
}
public final Size getPreviewStreamSize(@NonNull Reference reference) {
Size size = mPreviewStreamSize;
if (size == null) return null;
return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size;
}
setRepeatingRequest
接着会调用这两句
addRepeatingRequestBuilderSurfaces();
applyRepeatingRequestBuilder(false, CameraException.REASON_FAILED_TO_START_PREVIEW);
addRepeatingRequestBuilderSurfaces
会对mRepeatingRequestBuilder
做一些配置,将预览的Surface
添加到mRepeatingRequestBuilder
中,mRepeatingRequestBuilder
是android.hardware.camera2.CaptureRequest.Builder
类,是接下来调用Camera2
中调用setRepeatingRequest
必备的一个参数。
private void addRepeatingRequestBuilderSurfaces(@NonNull Surface... extraSurfaces) {
mRepeatingRequestBuilder.addTarget(mPreviewStreamSurface);
if (mFrameProcessingSurface != null) {
mRepeatingRequestBuilder.addTarget(mFrameProcessingSurface);
}
for (Surface extraSurface : extraSurfaces) {
if (extraSurface == null) {
throw new IllegalArgumentException("Should not add a null surface.");
}
mRepeatingRequestBuilder.addTarget(extraSurface);
}
}
然后调用applyRepeatingRequestBuilder
,在内部会调用setRepeatingRequest
,因为mRepeatingRequestBuilder
中添加了预览的Surface
,所以调用后将不断地实时发送视频流给预览的Surface
,从而实现了预览的效果。
@EngineThread
private void applyRepeatingRequestBuilder(boolean checkStarted, int errorReason) {
if ((getState() == CameraState.PREVIEW && !isChangingState()) || !checkStarted) {
//这将不断地实时发送视频流,直到会话断开或调用session.stoprepeat()
mSession.setRepeatingRequest(mRepeatingRequestBuilder.build(),
mRepeatingRequestCallback, null);
}
}
到这里我们就对于CameraView
的预览流程有了大致的了解了,内部就是调用了Camera2
的API
,关联SurfaceView
进行预览。
mCameraPreview
,具体实现类是SurfaceCameraPreview
,内部封装了SurfaceView
SurfaceCallback
接口用来回调onSurfaceAvailable()
、onSurfaceChanged()
、onSurfaceDestroyed
方法Camera2Engine.setPreview(CameraPreview)
,就是Camera2Engine
实现了CameraPreview
的SurfaceCallback
回调onSurfaceAvailable()
方法里
CaemraView
中的SurfaceHolder
添加到outputSurfaces
列表video
模式,初始化视频录制Surface
并添加到outputSurfaces
列表picture
模式,初始化拍照Surface
并添加到outputSurfaces
列表Surface
,并添加到outputSurfaces
列表outputSurfaces
列表创建CameraCaptureSession
: CameraDevice.createCaptureSession()
CameraCaptureSession.setRepeatingRequest()
开始不断地传递视频流给预览的Surface
,从而完成预览功能Android 相机库CameraView源码解析 (一) : 预览-CSDN博客
Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客