Android 相机库CameraView源码解析 (二) : 拍照 ╰+哭是因爲堅強的太久メ 2024-04-25 20:24 61阅读 0赞 ### 1. 前言 ### 这段时间,在使用 [natario1/CameraView][natario1_CameraView] 来实现带滤镜的`预览`、`拍照`、`录像`功能。 由于`CameraView`封装的比较到位,在项目前期,的确为我们节省了不少时间。 但随着项目持续深入,对于`CameraView`的使用进入深水区,逐渐出现满足不了我们需求的情况。 `Github`中的`issues`中,有些`BUG`作者一直没有修复。 那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。 而这篇文章是其中关于`CameraView`怎么进行拍照的源码解析。 以下源码解析基于`CameraView 2.7.2` implementation("com.otaliastudios:cameraview:2.7.2") > 为了在博客上更好的展示,本文贴出的代码进行了部分精简 ![在这里插入图片描述][b56e198a1fec4d1894f881778ce7c7db.png] 拍照的入口是`cameraView.takePicture()`,我们从这个方法开始解析。 ### 2. CameraEngine.takePicture ### `cameraView.takePicture()`会调用到`mCameraEngine.takePicture()`, 这个`PictureResult.Stub`是一个参数封装类,这里重新创建了一个`PictureResult.Stub`并传入`takePicture()`方法中。 `mCameraEngine`是`CameraEngine`抽象类,实现类有`Camera1Engine`和`Camera2Engine`。 public void takePicture() { PictureResult.Stub stub = new PictureResult.Stub(); mCameraEngine.takePicture(stub); } 我们这里以`Camera2`为例,可以看到这里对`stub`参数封装类赋值了一些参数(`摄像头ID、图片格式等`),并调用了`onTakePicture` public void takePicture(final PictureResult.Stub stub) { final boolean metering = mPictureMetering; getOrchestrator().scheduleStateful("take picture", CameraState.BIND, new Runnable() { @Override public void run() { if (isTakingPicture()) return; if (mMode == Mode.VIDEO) { throw new IllegalStateException("Can't take hq pictures while in VIDEO mode"); } stub.isSnapshot = false; stub.location = mLocation; stub.facing = mFacing; stub.format = mPictureFormat; onTakePicture(stub, metering); } }); } ### 3. onTakePicture ### 接着来看`onTakePicture()` 设置`Rotation` stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); 设置设定好拍照图片尺寸 stub.size = getPictureSize(Reference.OUTPUT); 接着调用`mPictureRecorder.take()`,`mPictureRecorder`是`PictureRecorder`接口,具体实现是`Full2PictureRecorder`,专门用来调用`Camera2 API`捕获图片。 mPictureRecorder = new Full2PictureRecorder(stub, this, builder,mPictureReader); mPictureRecorder.take(); 来看一下完整的重点代码 @EngineThread @Override protected void onTakePicture(@NonNull final PictureResult.Stub stub, boolean doMetering) { //...省略不重要代码... //设置Rotation stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); //设置设定好拍照图片尺寸 stub.size = getPictureSize(Reference.OUTPUT); //...省略不重要代码... CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); applyAllParameters(builder, mRepeatingRequestBuilder); mPictureRecorder = new Full2PictureRecorder(stub, this, builder, mPictureReader); mPictureRecorder.take(); } ### 4. Full2PictureRecorder.take ### 再来看`Full2PictureRecorder.take()` @Override public void take() { mAction.start(mHolder); } 这里调用了`mAction.start(mHolder)`,来看一下`mAction`初始化 #### 4.1 初始化BaseAction #### mAction = new BaseAction() { @Override protected void onStart(ActionHolder holder) { //省略了代码,这里只看结构 } @Override public void onCaptureStarted(ActionHolder holder,CaptureRequest request) { //省略了代码,这里只看结构 } @Override public void onCaptureCompleted(ActionHolder holder,CaptureRequest request,TotalCaptureResult result) { //省略了代码,这里只看结构 } }; * `mAction`是`BaseAction`抽象类,有`onStart`、`onCaptureStarted`、`onCaptureProgressed`、`onCaptureCompleted`等方法。 * `mHolder`是构造方法传入过来的`Camera2Engine`,实现了`ActionHolder`接口。 #### 4.2 BaseAction.onStart #### 调用了`mAction.start(mHolder)`后,`mAction`和`mHolder`会建立关联,也就是`BaseAction`和`Camera2Engine`会建立关联,具体代码为`Camera2Engine.addAction(BaseAction)`将其添加到`Actions`列表中,并在合适的时机回调`BaseAction`的`onCaptureStarted`、`onCaptureProgressed`、`onCaptureCompleted`方法。 `mAction`和`mHolder`建立关联后,会调用`onStart`方法,这里是对`mPictureBuilder`这个建造者设置了一些值 @Override protected void onStart(@NonNull ActionHolder holder) { super.onStart(holder); //mPictureBuilder是一个建造者,这里给建造者设置一些值 mPictureBuilder.addTarget(mPictureReader.getSurface()); if (mResult.format == PictureFormat.JPEG) { mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation); } mPictureBuilder.setTag(CameraDevice.TEMPLATE_STILL_CAPTURE); //应用这个建造者 holder.applyBuilder(this, mPictureBuilder); } 再来看`onCaptureStarted`,调用了`dispatchOnShutter`来回调 @Override public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { super.onCaptureStarted(holder, request); if (request.getTag() == (Integer) CameraDevice.TEMPLATE_STILL_CAPTURE) { dispatchOnShutter(false); setState(STATE_COMPLETED); } } #### 4.3 BaseAction.onCaptureCompleted #### 再来看`onCaptureCompleted`,主要是在`DNG`格式的时候,做了一些特殊处理。 @Override public void onCaptureCompleted(ActionHolder holder, CaptureRequest request, TotalCaptureResult result) { if (mResult.format == PictureFormat.DNG) { mDngCreator = new DngCreator(holder.getCharacteristics(this), result); mDngCreator.setOrientation(ExifHelper.getExifOrientation(mResult.rotation)); if (mResult.location != null) { mDngCreator.setLocation(mResult.location); } } } 结果发现这里不是重点,那么重点在哪里呢 ? ### 5. 设置OnImageAvailableListener监听 ### 在`Full2PictureRecorder`初始化构造方法中,还有这么一句 mPictureReader.setOnImageAvailableListener(this, WorkerHandler.get().getHandler()); 在`Android`的`Camera2 API`中,`setOnImageAvailableListener`方法用于注册一个回调监听器,以在每次图像数据可用时接收通知。 #### 5.1 onImageAvailable回调 #### 来看`onImageAvailable`回调方法,这里会调用`android.media.ImageReader.acquireNextImage()`来获取图像数据。 然后如果是`JPEG`格式,则会调用`readJpegImage()`方法读取图像数据 最后都会调用`dispatchResult`来分发数据。 @Override public void onImageAvailable(ImageReader reader) { Image image = null; try { image = reader.acquireNextImage(); switch (mResult.format) { case JPEG: readJpegImage(image); break; case DNG: readRawImage(image); break; default: throw new IllegalStateException("Unknown format: " + mResult.format); } } catch (Exception e) { mResult = null; mError = e; dispatchResult(); return; } finally { if (image != null) { image.close(); } } dispatchResult(); } #### 5.2 读取JPEG数据 #### 我们先来看下`readJpegImage()`方法 private void readJpegImage(@NonNull Image image) { //从Iamge中读取数据 ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); mResult.data = bytes; //根据Exif设置rotation mResult.rotation = 0; ExifInterface exif = new ExifInterface(new ByteArrayInputStream(mResult.data)); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); mResult.rotation = ExifHelper.getOrientation(exifOrientation); } #### 5.3 分发回调 #### 再来看`dispatchResult`,最终会调用到`CameraView`中的`dispatchOnPictureTaken`,这个方法中会遍历`mListeners`回调列表,调用`onPictureTaken()` @Override public void dispatchOnPictureTaken(final PictureResult.Stub stub) { mUiHandler.post(new Runnable() { @Override public void run() { PictureResult result = new PictureResult(stub); for (CameraListener listener : mListeners) { listener.onPictureTaken(result); } } }); } 而`mListeners`什么时候被添加呢 ? `CameraView`中有一个`addCameraListener`方法,专门直接添加回调。 public void addCameraListener(CameraListener cameraListener) { mListeners.add(cameraListener); } #### 5.4 设置回调 #### 所以我们只要添加了这个回调,并实现`onPictureTaken`方法,就可以在`onPictureTaken()`中获取到拍照后的图像信息了。 binding.cameraView.addCameraListener(object : CameraListener() { override fun onPictureTaken(result: PictureResult) { super.onPictureTaken(result) //拍照回调 val bitmap = BitmapFactory.decodeByteArray(result.data, 0, result.data.size) bitmap?.also { Toast.makeText(this@Test2Activity, "拍照成功", Toast.LENGTH_SHORT).show() //将Bitmap设置到ImageView上 binding.img.setImageBitmap(it) val file = getNewImageFile() //保存图片到指定目录 ImageUtils.save(it, file, Bitmap.CompressFormat.JPEG) } } }) ### 6. 其他 ### #### 6.1 CameraView源码解析系列 #### [Android 相机库CameraView源码解析 (一) : 预览-CSDN博客][Android _CameraView_ _ _ _-CSDN] [Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN 1] [natario1_CameraView]: https://github.com/natario1/CameraView [b56e198a1fec4d1894f881778ce7c7db.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/68bff22e29f349fb8779232aebb150cf.png [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 赞/ 64 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 我不是女神ヾ/ 2024年04月25日 20:24/ 0 赞/ 63 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG 解决相机库CameraView多滤镜拍照错乱的BUG 女爷i/ 2024年04月25日 20:24/ 0 赞/ 73 阅读
相关 为什么相机库CameraView预览和拍照的效果不一致 ? 从源码解析 : 为什么CameraView预览和拍照的效果会不一致呢 ? 我就是我/ 2024年04月25日 20:24/ 0 赞/ 70 阅读
相关 Android 相机库CameraView源码解析 (六) : 保存滤镜效果 Android 相机库CameraView源码解析 : 保存滤镜效果部分 约定不等于承诺〃/ 2024年04月25日 20:24/ 0 赞/ 73 阅读
相关 Android 相机库CameraView源码解析 (二) : 拍照 Android 相机库CameraView源码解析 : 拍照部分 ╰+哭是因爲堅強的太久メ/ 2024年04月25日 20:24/ 0 赞/ 62 阅读
相关 Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 Android 相机库CameraView源码解析 : 滤镜相关类说明 - 日理万妓/ 2024年04月25日 20:24/ 0 赞/ 68 阅读
相关 Android 相机库CameraView源码解析 (一) : 预览 Android 相机库CameraView源码解析 : 预览部分 青旅半醒/ 2024年04月25日 20:23/ 0 赞/ 62 阅读
相关 Android 相机库CameraView源码解析 (五) : 带滤镜拍照 Android 相机库CameraView源码解析 : 带滤镜拍照 我不是女神ヾ/ 2024年04月25日 20:23/ 0 赞/ 90 阅读
相关 Android 源码解析AsyncTask(二) 上篇文章简单说明了AsyncTask的使用,如果你还未了解,请先看这篇文章,[Android 源码解析AsyncTask(一)][Android _AsyncTas 清疚/ 2022年07月15日 05:50/ 0 赞/ 233 阅读
还没有评论,来说两句吧...