AndroidR上展锐平台实现长按拍照键连拍的功能

2024-05-24 02:08

本文主要是介绍AndroidR上展锐平台实现长按拍照键连拍的功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

某客户定制需求:长按拍照键实现连拍功能。
我们先来跟进下单拍的流程,连拍与单拍流程重叠度很高,只是连拍在单拍的照片callback回来之后,在立即下发下一次的拍照请求。
View的click流程就不说了,点击拍照键后,点击事件会回调到PhotoModule的onShutterButtonClick方法中。
在这个方法中会做拍照前的逻辑判断,比如当前对否设置了拍照倒计时,如果有的话,就转到倒计时的逻辑中,延时拍照。我们这里不考虑这些设置,后面就是调用FocusOverlayManager的 focusAndCapture()。这个方法我们应该挺熟悉的,我们之前有篇文章专门介绍预览聚焦的流程,这里在拍照之前也去确认了当前是否已经是聚焦OK的状态,否则我们会拍出模糊的照片。

FocusOverlayManager下的focusAndCapture方法

public void focusAndCapture(CameraCapabilities.FocusMode currentFocusMode) {if (!mInitialized) {return;}if(isAFLock && mState != STATE_FOCUSING){capture();return;}/*** SPRD:fix bug 473602 add for half-press @{if (!needAutoFocusCall(currentFocusMode)) {*/if (!needAutoFocusCall(currentFocusMode) && mState != STATE_FOCUSING) {/*** @}*/// Focus is not needed.Log.i(TAG, "Focus is not needed.");capture();} else if (mState == STATE_SUCCESS || mState == STATE_FAIL) {// Focus is done already.Log.i(TAG, "Focus is done already.");capture();} else if (mState == STATE_FOCUSING) {// Still focusing and will not trigger snap upon finish.Log.i(TAG, "till focusing and will not trigger snap upon finish.");mState = STATE_FOCUSING_SNAP_ON_FINISH;} else if (mState == STATE_IDLE) {autoFocusAndCapture();}}

我们假定当前是已经在预览时聚焦OK的状态,一般情况下在预览阶段就已经聚焦好了,那么会在调用capture方法。

private void capture() {if (mListener.capture()) {if (!mFocusRing.isAEAFDraging()) {mState = STATE_IDLE;}mHandler.removeMessages(RESET_TOUCH_FOCUS);}
}

capture方法啥也没干,又把流程转到mListener(PhotoModule)中去了。

PhotoModule的capture方法前半部分做一些mCameraSettings的更新,最后就下发takePicture的流程

@Override
public boolean capture() {Log.i(TAG, "capture");mBurstCaptureCountOnCanceled = -1;//SPRD:fix bug1085123if(!mBurstWorking && mIsContinousCaptureFinish){Log.i(TAG,"capture cancel");mAppController.getCameraAppUI().setBottomPanelLeftRightClickable(true);mAppController.getCameraAppUI().setSwipeEnabled(true);mBurstNotShowShutterButton = false;return false;}// If we are already in the middle of taking a snapshot or the image// save request is full then ignore.if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS|| mCameraState == SWITCHING_CAMERA) {return false;}setCameraState(SNAPSHOT_IN_PROGRESS);mCaptureStartTime = System.currentTimeMillis();mPostViewPictureCallbackTime = 0;mJpegImageData = null;/* SPRD: add hide shutter animation after capture when HDR is onfinal boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);if (animateBefore) {animateAfterShutter();}*/updateFilterType();/* SPRD: fix bug672841 add for cancel burst when focusing state, burst can not stop*/if (!mIsImageCaptureIntent) {updateParametersBurstCount();mContinuousCaptureCount = getContinuousCount();}/* @} */Location loc = null;if (mIsImageCaptureIntent && !mActivity.getThirdLocationPermission()) {Log.i(TAG,"third app call camera, has not location premission");CameraUtil.setGpsParameters(mCameraSettings, null);} else {loc = mActivity.getLocationManager().getCurrentLocation();Log.i(TAG,"location info:"+loc);CameraUtil.setGpsParameters(mCameraSettings, loc);}boolean isHasEnteredBeauty = mDataModule.getBoolean(Keys.KEY_CAMERA_BEAUTY_ENTERED);/* SPRD:fix bug 823475 clear ae corrdinate before capture when after taf @ */if (mFocusManager != null) {mFocusManager.updateFocusState();setMeteringAreasIfSupported();}/* @} */if(isAutoHdr() && isHdrScene){Log.e(TAG,"auto hdr detect scene is hdr so change the scene to hdr");mCameraSettings.setSceneMode(CameraCapabilities.SceneMode.HDR);}if (mSmileCapture) {mCameraSettings.setSmileCapture(1);mSmileCapture = false;}mCameraDevice.applySettings(mCameraSettings);// Set JPEG orientation. Even if screen UI is locked in portrait, camera// orientation should// still match device orientation (e.g., users should always get// landscape photos while// capturing by putting device in landscape.)Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId);int sensorOrientation = info.getSensorOrientation();int deviceOrientation = mAppController.getOrientationManager().getDeviceOrientation().getDegrees();boolean isFrontCamera = info.isFacingFront();mJpegRotation = CameraUtil.getImageRotation(sensorOrientation,deviceOrientation, isFrontCamera);Log.i(TAG, " sensorOrientation = " + sensorOrientation+ " ,deviceOrientation = " + deviceOrientation+ " isFrontCamera = " + isFrontCamera);mCameraDevice.setJpegOrientation(mJpegRotation);Log.i(TAG, "takePicture start!");isHdrPicture = isAutoHdr() && isHdrScene;if (mReceiver != null) {//mActivity.unregisterReceiver(mReceiver);mActivity.unRegisterMediaBroadcastReceiver();mReceiver = null;}mIsHdrPicture = true;mIsFirstCallback = true;mFirstHasStartCapture = false;mIsNormalHdrDone = false;//SPRD:fix bug784774if (inBurstMode()){doCaptureSpecialContinue();} else {doCaptureSpecial();}//开始拍照啦mCameraDevice.takePicture(mHandler,/*** SPRD: fix bug462021 remove capture animation* * @{ new ShutterCallback(!animateBefore),*/new ShutterCallback(CameraUtil.isCaptureAnimatationEnable() && !isBurstCapture() && !isAudioCapture() &&!isCameraFrontFacing()),//SPRD:fix bug1154938/1137366/1162992/1201491/*** @}*/mRawPictureCallback, mPostViewPictureCallback, new JpegPictureCallback(loc));/*** SPRD: fix bug 473462 add for burst capture* mNamedImages.nameNewImage(mCaptureStartTime);*/mFaceDetectionStarted = false;return true;
}

我们关注最后的 mCameraDevice.takePicture(…),重点来了,该方法是在AndroidCamera2AgentImpl中的 AndroidCamera2ProxyImpl 实现的。
这个方法比较长,分为两个部分,
1, 构建一个 CaptureAvailableListener 类型的 picListener 对象,作为照片数据的回调对象

// TODO: We never call raw or postview
final CaptureAvailableListener picListener =new CaptureAvailableListener() {
@Override
public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,long timestamp, long frameNumber) {// SPRDLog.i(TAG,"AppFw takePicture onCaptureStarted");if (shutter != null) {handler.post(new Runnable() {@Overridepublic void run() {/** SPRD @{* Original Code*if (mShutterSoundEnabled) {mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);}*/shutter.onShutter(AndroidCamera2ProxyImpl.this);}});}
}@Override
public void onImageAvailable(ImageReader reader) {// 第一部分,处理Burst相关的if (mPicListener != null && !mBurstCaptureCanceled) {if (mBurstHasCaptureCount < mBurstMaxCaptureCount) {Log.i(TAG, "AppFw takePicture onImageAvailable mBurstHasCaptureCount = "+ mBurstHasCaptureCount);try {mDispatchThread.runJob(new Runnable() {@Overridepublic void run() {if (mBurstCaptureCanceled) {Log.i(TAG,"burst capture canceled");return;}mCameraState.waitForStates(~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));mBurstHasCaptureCount++;//SPRD:fix bug756490mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener).sendToTarget();}});} catch (RuntimeException ex) {mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);}}}Log.i(TAG,"AppFw takePicture onImageAvailable");//第二部分,处理上报的图片数据try (Image image = reader.acquireNextImage()) {if (!mNeedThumb && jpeg != null) {ByteBuffer buffer = image.getPlanes()[0].getBuffer();final byte[] pixels = new byte[buffer.remaining()];buffer.get(pixels);handler.post(new Runnable() {@Overridepublic void run() {jpeg.onPictureTaken(pixels, AndroidCamera2ProxyImpl.this);}});} else {onImageAvailableWithThumb(image, handler, jpeg);}}
}};

在该回调对象的 onImageAvailable 方法中,就是处理图片数据。onImageAvailable也分为两个部分。第一部分的if里面,我们看到Burst相关内容,没错,这就是处理连拍相关的逻辑的,我们先暂时略过,稍后会再回来讲这部分。
在第二部分中,我们看到一个很熟悉的方法onPictureTaken,这就是我们在PhotoModule中经常看到的图片数据的回调方法,在onPictureTaken中处理图片命名、保存到本地等逻辑。

2, CaptureAvailableListener 的回调对象我们看完了,在来看 takePicture 的第二部分。

try {mPicListener = picListener;mBurstMaxCaptureCount = mLastSettings.mBurstNumber;Log.i(TAG, "AppFw takePicture mBurstMaxCaptureCount now is " + mBurstMaxCaptureCount);if(takePictureRunnable == null) {Log.i(TAG, "AppFw takePicture new runJob");takePictureRunnable = new Runnable() {@Overridepublic void run() {Log.i(TAG, "AppFw takePicture runJob");if (mBurstMaxCaptureCount > 1) {mBurstHasCaptureCount = 0;// reset capture count, in case of didn't call// cancelBurstCapturemBurstHasCaptureCount++;mBurstCaptureCanceled = false;}// Wait until PREVIEW_ACTIVE or bettermCameraState.waitForStates(~(com.android.ex.camera2.portability.AndroidCamera2AgentImpl.AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));// SPRD : Add for bug 657472 Save normal hdr pictureif(mLastSettings.getNormalHdrModeEnable() == 1){mCameraHandler.obtainMessage(CameraActions.CAPTURE_HDR_PHOTO, mPicListener).sendToTarget();} else if (mNeedThumb) {mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO_WITH_THUMB, mPicListener).sendToTarget();} else if (mIsVideMode) {mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO_WITH_SNAP, mPicListener).sendToTarget();}else{mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener).sendToTarget();}/* @} */}};}mDispatchThread.runJob(takePictureRunnable);
} catch (RuntimeException ex) {takePictureRunnable = null;mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);
}

又看到熟悉的Hander发送消息,这次发送的是 CAPTURE_PHOTO ,看名称就知道 拍照 的消息。并且注意到我们前面的 CaptureAvailableListener picListener 作为handler消息的obj了。

接下来我们就看下,CAPTURE_PHOTO 消息的处理。

case CameraActions.CAPTURE_PHOTO: {final CaptureAvailableListener listener =(CaptureAvailableListener) msg.obj;if (mLegacyDevice || mIsYuvSensor || mCameraProxy.mLastSettings.mBurstNumber == 30 ||//SPRD: fix bug962989
mCameraProxy.mLastSettings.mBurstNumber == 20 ||(mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)) ||((mCurrentAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || mCurrentAeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&//SPRD:fix bug1193947!mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) &&!mPersistentSettings.matches(CaptureRequest.FLASH_MODE,CaptureRequest.FLASH_MODE_SINGLE))){// SPRDLog.i(TAG,"AppFw:   CameraActions.CAPTURE_PHOTO, mLegacyDevice = " + mLegacyDevice + " mIsYuvSensor = " + mIsYuvSensor);mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this);try {mSession.capture(mPersistentSettings.createRequest(mCamera,CameraDevice.TEMPLATE_STILL_CAPTURE,mCaptureReader.getSurface()),listener, /*handler*/this);} catch (CameraAccessException ex) {Log.e(TAG, "Unable to initiate immediate capture", ex);}} 

我们看到 if 的判断很复杂,但是我们最终还是进入到这个if条件里面了,并且我们前面讲的 CaptureAvailableListener listener 也从消息里面取出来了 (CaptureAvailableListener) msg.obj;

然后就是通过在创建会话时得到的mSession对象,去调用CameraCaptureSession的capture方法,并将CaptureAvailableListener 作为回调对象传下去。

到这里,我们的拍照流程算是讲完了。我们将上拍照流程总结为一个时序图
在这里插入图片描述

好了,拍照流程讲完了,下面我们来说下连拍的逻辑。

我们前面说过 CaptureAvailableListener picListener 这个回调,是用来接收拍照数据的,其中的onImageAvailable方法中有关于Burst的处理。我们再贴下代码

if (mPicListener != null && !mBurstCaptureCanceled) {if (mBurstHasCaptureCount < mBurstMaxCaptureCount) {Log.i(TAG, "AppFw takePicture onImageAvailable mBurstHasCaptureCount = "+ mBurstHasCaptureCount);try {mDispatchThread.runJob(new Runnable() {@Overridepublic void run() {if (mBurstCaptureCanceled) {Log.i(TAG,"burst capture canceled");return;}mCameraState.waitForStates(~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));mBurstHasCaptureCount++;//SPRD:fix bug756490mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener).sendToTarget();}});} catch (RuntimeException ex) {mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);}}
}

我们看到,如果这个if条件能满足,就会在发送一个 CAPTURE_PHOTO 消息去拍照。这其实就是连拍的逻辑,在前一张图片的数据callbakc回来之后,再去下发一个新的 CAPTURE_PHOTO 拍照请求,从而实现连拍。

那么,如何才能满足这个if条件呢?

我们看到有 mBurstCaptureCanceled 和 mBurstMaxCaptureCount 变量。如果满足连拍条件,mBurstCaptureCanceled = false;
在该类中搜索, mBurstCaptureCanceled 默认为true,只有如下位置会置为false。

 mBurstMaxCaptureCount = mLastSettings.mBurstNumber;if (mBurstMaxCaptureCount > 1) {mBurstHasCaptureCount = 0;// reset capture count, in case of didn't call// cancelBurstCapturemBurstHasCaptureCount++;mBurstCaptureCanceled = false;}

看来关键是 mLastSettings.mBurstNumber;的值了,在SprdCameraSettings中有唯一方法设置mBurstNumber的值

public void setBurstPicNum(int count) {mBurstNumber = count;
}

在全局搜一把 setBurstPicNum 方法的调用。
果然又来到了PhotoModule中

private void updateParametersBurstCount() {mCameraSettings.setBurstPicNum(!mBurstWorking ? 1 : MAX_BURST_COUNT);
}

如果 mBurstWorking = true,那么 setBurstPicNum传入的就是MAX_BURST_COUNT值了,而 mBurstWorking = true的条件之一便是当前是 inBurstMode()返回true。

public boolean inBurstMode() {return getModuleTpye() == DreamModule.AUTO_PHOTO_MODULE && mLongPressed; }

mLongPressed,长按拍照键条件。
所以只有长按拍照键,才会设置 mLastSettings.mBurstNumber 为 MAX_BURST_COUNT,在AndroidCamera2AgentImpl中才会设置 mBurstCaptureCanceled = false,才会在前一张图片数据callback之后立即下发下一次的 CAPTURE_PHOTO 拍照请求。

拍照和连拍的逻辑大部分都是一样的,只是连拍会设置SprdCameraSettings的mBurstNumber 值,在AndroidCamera2AgentImpl中会自动连续下发拍照请求。

这篇关于AndroidR上展锐平台实现长按拍照键连拍的功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/997066

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短