计算机网站开发参考文献,wordpress插件更新保留修改,网络营销产品价格策略,深圳广告公司招聘1. 前言
这段时间#xff0c;在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位#xff0c;在项目前期#xff0c;的确为我们节省了不少时间。 但随着项目持续深入#xff0c;对于CameraView的使用进入深水区#xff0c;逐…1. 前言
这段时间在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位在项目前期的确为我们节省了不少时间。 但随着项目持续深入对于CameraView的使用进入深水区逐渐出现满足不了我们需求的情况。 Github中的issues中有些BUG作者一直没有修复。
那要怎么办呢 ? 项目迫切地需要实现相关功能只能自己硬着头皮去看它的源码去解决这些问题。 而这篇文章是其中关于CameraView怎么进行预览的源码解析。
以下源码解析基于CameraView 2.7.2
implementation(com.otaliastudios:cameraview:2.7.2)为了在博客上更好的展示本文贴出的代码进行了部分精简 2. 初始化CameraEngine
在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:cameraEnginecamera2和app:cameraExperimentaltrue
com.otaliastudios.cameraview.CameraViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentapp:cameraEnginecamera2app:cameraExperimentaltrue /2. 初始化CameraPreview
在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 : 创建SurfaceCameraPreviewtexture : 创建TextureCameraPreviewglSurface : 创建GlCameraPreview
这里以surface为例实际创建的CameraPreview是SurfaceCameraPreview其职责就是在初始化方法的时候通过LayoutInflater.from创建SurfaceView并封装了SurfaceHolder.Callback回调。
public class SurfaceCameraPreview extends CameraPreviewSurfaceView, SurfaceHolder {//...省略了部分代码...Overrideprotected 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() {//...Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {if (!mDispatched) {dispatchOnSurfaceAvailable(width, height);mDispatched true;} else {dispatchOnSurfaceSizeChanged(width, height);}}Overridepublic void surfaceDestroyed(SurfaceHolder holder) {LOG.i(callback: surfaceDestroyed);dispatchOnSurfaceDestroyed();mDispatched false;}});mRootView root;return surfaceView;}
}3. Camera2Engine和CameraView建立关联
接着调用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);
}4. 回调SurfaceCallBack.onSurfaceAvailable
在CameraBaseEngine的onSurfaceAvailable回调中调用了startBind()-onStartBind()和startPreview()-onStartPreview()
Override
public final void onSurfaceAvailable() {startBind();startPreview();
}onStartBind和onStartPreview是抽象方法Camera1Engine和Camera2Engine分别实现了CameraBaseEngine分别用来实现Camera1和Camera2。
NonNull
EngineThread
protected abstract TaskVoid onStartBind();NonNull
EngineThread
protected abstract TaskVoid onStartPreview();这里以Camera2为例对于Camera2不了解的同学可以先看我的另一篇博客 : 十分钟实现 Android Camera2 相机预览 。 接下来的部分就是Camera2 API绑定Surface和预览的具体实现了。
5. onStartBind
5.1 估算图像尺寸
分别估算出预览和拍照的图像尺寸为后续预览和拍照做准备
//估算出拍照时图像的尺寸
mCaptureSize computeCaptureSize();
//估算出预览时的图像尺寸
mPreviewStreamSize computePreviewStreamSize();5.2 添加预览SurfaceHolder
接着调用setFixedSize给SurfaceView设置刚才估算出来的预览的尺寸 ; 然后调用outputSurfaces.add将CameraView的SurfaceHolder添加到outputSurfaces列表中。
ListSurface outputSurfaces new ArrayList();final Class outputClass mPreview.getOutputClass();
final Object output mPreview.getOutput();
if (outputClass SurfaceHolder.class) {Tasks.await(Tasks.call(new CallableVoid() {Overridepublic 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);5.3 初始化视频录制相关类
如果是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;}
}5.4 初始化拍照相关类
如果是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());
}5.5 帧处理
创建一个帧处理的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.getDataImage()Log.i(TAG, image width:${image.width} height:${image.height})image.close()
}5.6 创建CameraCaptureSession
这里就是根据outputSurfaces列表创建对应的android.hardware.camera2.CameraCaptureSession从而实现关联对应功能的Surface。
mCamera.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {Overridepublic void onConfigured(NonNull CameraCaptureSession session) {mSession session;task.trySetResult(null);}Overridepublic void onConfigureFailed(NonNull CameraCaptureSession session) {//...省略了配置失败的代码...}
}, null);6. onStartPreview
6.1 重新测量CaemraView大小
首先调用回调方法onCameraPreviewStreamSizeChanged()内部会去调用下requestLayout()从而触发onMeasure来重新测量CameraView尺寸。 getCallback().onCameraPreviewStreamSizeChanged();6.2 设置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那么初始化FrameManagergetFrameManager().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;
}6.3 调用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);}
}7. 小结
到这里我们就对于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从而完成预览功能
8. 其他
8.1 CameraView源码解析系列
Android 相机库CameraView源码解析 (一) : 预览-CSDN博客 Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客