Android 相机库CameraView源码解析 (一) : 预览 青旅半醒 2024-04-25 20:23 59阅读 0赞 ### 1. 前言 ### 这段时间,在使用 [natario1/CameraView][natario1_CameraView] 来实现带滤镜的`预览`、`拍照`、`录像`功能。 由于`CameraView`封装的比较到位,在项目前期,的确为我们节省了不少时间。 但随着项目持续深入,对于`CameraView`的使用进入深水区,逐渐出现满足不了我们需求的情况。 `Github`中的`issues`中,有些`BUG`作者一直没有修复。 那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。 而这篇文章是其中关于`CameraView`怎么进行预览的源码解析。 以下源码解析基于`CameraView 2.7.2` implementation("com.otaliastudios:cameraview:2.7.2") > 为了在博客上更好的展示,本文贴出的代码进行了部分精简 ![在这里插入图片描述][c92f5c122cb6428ba2fa01c2f4421049.png] ### 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: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" /> ### 3. 初始化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` : 创建`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; } } ### 4. 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); } ### 5. 回调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 Task<Void> onStartBind(); @NonNull @EngineThread protected abstract Task<Void> onStartPreview(); 这里以`Camera2`为例,对于`Camera2`不了解的同学,可以先看我的另一篇博客 : [十分钟实现 Android Camera2 相机预览][Android Camera2] 。 接下来的部分,就是`Camera2 API`绑定`Surface`和预览的具体实现了。 ### 6. onStartBind ### #### 6.1 估算图像尺寸 #### 分别估算出预览和拍照的图像尺寸,为后续预览和拍照做准备 //估算出拍照时图像的尺寸 mCaptureSize = computeCaptureSize(); //估算出预览时的图像尺寸 mPreviewStreamSize = computePreviewStreamSize(); #### 6.2 添加预览SurfaceHolder #### 接着调用`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); #### 6.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; } } #### 6.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()); } #### 6.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.getData<Image>() Log.i(TAG, "image width:${ image.width} height:${ image.height}") image.close() } #### 6.6 创建CameraCaptureSession #### 这里就是根据`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); ### 7. onStartPreview ### #### 7.1 重新测量CaemraView大小 #### 首先调用回调方法`onCameraPreviewStreamSizeChanged()`,内部会去调用下`requestLayout()`,从而触发`onMeasure`来重新测量`CameraView`尺寸。 getCallback().onCameraPreviewStreamSizeChanged(); #### 7.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,那么初始化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; } #### 7.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); } } ### 8. 小结 ### 到这里我们就对于`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`,从而完成预览功能 ### 9. 其他 ### #### 9.1 CameraView源码解析系列 #### [Android 相机库CameraView源码解析 (一) : 预览-CSDN博客][Android _CameraView_ _ _ _-CSDN] [Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN 1] [natario1_CameraView]: https://github.com/natario1/CameraView [c92f5c122cb6428ba2fa01c2f4421049.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/cb68520d6f6c45b58061af6246debf33.png [Android Camera2]: https://blog.csdn.net/EthanCo/article/details/131371887 [Android _CameraView_ _ _ _-CSDN]: https://blog.csdn.net/EthanCo/article/details/134511622 [Android _CameraView_ _ _ _-CSDN 1]: https://blog.csdn.net/EthanCo/article/details/134545086
相关 Android 相机库CameraView源码解析 (四) : 带滤镜预览 Android 相机库CameraView源码解析 (四) : 带滤镜预览 太过爱你忘了你带给我的痛/ 2024年04月25日 20:24/ 0 赞/ 62 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 我不是女神ヾ/ 2024年04月25日 20:24/ 0 赞/ 57 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG 解决相机库CameraView多滤镜拍照错乱的BUG 女爷i/ 2024年04月25日 20:24/ 0 赞/ 71 阅读
相关 为什么相机库CameraView预览和拍照的效果不一致 ? 从源码解析 : 为什么CameraView预览和拍照的效果会不一致呢 ? 我就是我/ 2024年04月25日 20:24/ 0 赞/ 67 阅读
相关 Android 相机库CameraView源码解析 (六) : 保存滤镜效果 Android 相机库CameraView源码解析 : 保存滤镜效果部分 约定不等于承诺〃/ 2024年04月25日 20:24/ 0 赞/ 71 阅读
相关 Android 相机库CameraView源码解析 (二) : 拍照 Android 相机库CameraView源码解析 : 拍照部分 ╰+哭是因爲堅強的太久メ/ 2024年04月25日 20:24/ 0 赞/ 57 阅读
相关 Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 Android 相机库CameraView源码解析 : 滤镜相关类说明 - 日理万妓/ 2024年04月25日 20:24/ 0 赞/ 64 阅读
相关 Android 相机库CameraView源码解析 (一) : 预览 Android 相机库CameraView源码解析 : 预览部分 青旅半醒/ 2024年04月25日 20:23/ 0 赞/ 60 阅读
相关 Android 相机库CameraView源码解析 (五) : 带滤镜拍照 Android 相机库CameraView源码解析 : 带滤镜拍照 我不是女神ヾ/ 2024年04月25日 20:23/ 0 赞/ 87 阅读
相关 Android 源码解析AsyncTask(一) Android开发中,总免不了开启子线程去实现一些耗时的操作,因为如果阻塞主线程的话,应用可能就无响应了。Android中的线程是Java中的线程,我们都知 深碍√TFBOYSˉ_/ 2022年07月15日 07:23/ 0 赞/ 217 阅读
还没有评论,来说两句吧...