经典好文推荐,通过阅读本文,您将收获以下知识点:
一、背景介绍
拍照的手机基本的功能,优化拍照性能,主要是优化点击拍照到生成照片的这一段时间,看看可以在什么地方减少耗时。下面将打开camera到拍照完成这段时间拆解一下。
这段过程主要分为:
Note:将预览流程与拍照流程合成一个大的流程,因为我们本文所说的优化重点就在这里。
二、核心思想
预览出帧是为了让用户感觉到此时camera正在运行,但是预览的帧数据是不能直接用作拍照的帧数据,为什么?因为预览的帧数据太小,拍照的帧数据很大,所以不能直接复用。那如果能直接复用呢?就是预览的帧数据可以直接被拍照来使用。
这也是我们本文讨论的重点,直接复用预览的帧数据。
直接复用预览的帧数据,那么首先需要保证的是 预览帧的大小必须和 实际拍照的帧大小是相同的,不然获取的预览帧数据也是没用的,没有意义。
预览的surface我们需要自定义,而且大小要和拍照的ImageReader的surface大小相同的。
2.1 定义Yuv Full ImageReader
private ImageReader mYuv1ImageReader;
初始化的时候需要创建这个 ImageReader的实例:
mYuv1ImageReader = ImageReader.newInstance(
mCameraInfoCache.getYuvStream1Size().getWidth(),
mCameraInfoCache.getYuvStream1Size().getHeight(),
ImageFormat.YUV_420_888,
YUV1_IMAGEREADER_SIZE);
mYuv1ImageReader.setOnImageAvailableListener(mYuv1ImageListener, mOpsHandler);
2.2 ImageReader的监听回调
ImageReader.OnImageAvailableListener mYuv1ImageListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image img = reader.acquireLatestImage();
if (img == null) {
Log.e(TAG, "Null image returned YUV1");
return;
if (mYuv1LastReceivedImage != null) {
mYuv1LastReceivedImage.close();
mYuv1LastReceivedImage = img;
if (++mYuv1ImageCounter % LOG_NTH_FRAME == 0) {
Log.v(TAG, "YUV1 buffer available, Frame #=" + mYuv1ImageCounter + " w=" + img.getWidth() + " h=" + img.getHeight() + " time=" + img.getTimestamp());
只要是处于预览状态,底层的sensor会一直出帧数据,这个onImageAvailable(ImageReader reader)会一直回调,发现我们在其中又定义了一个Image变量。
2.3 定义实时的Image返回值
// Handle to last received Image: allows ZSL to be implemented.
private Image mYuv1LastReceivedImage = null;
这个mYuv1LastReceivedImage从定义的变量名上就能看出来,是预览的最后一帧的数据,显然这个帧数据是完全的,和出图的大小完全一样的。
mYuv1LastReceivedImage保证本地总是存储预览的最后一帧数据。
2.4 创建captureSession
Camera打开的时候onOpened回调的时候,开始创建captureSession:
private CameraDevice.StateCallback mCameraStateCallback = new LoggingCallbacks.DeviceStateCallback() {
@Override
public void onOpened(CameraDevice camera) {
super.onOpened(camera);
startCaptureSession();
// Create CameraCaptureSession. Callback will start repeating request with current parameters.
private void startCaptureSession() {
Log.v(TAG, "Configuring session..");
List<Surface> outputSurfaces = new ArrayList<Surface>(4);
outputSurfaces.add(mPreviewSurface);
Log.v(TAG, " .. added SurfaceView " + mCameraInfoCache.getPreviewSize().getWidth() +
" x " + mCameraInfoCache.getPreviewSize().getHeight());
outputSurfaces.add(mYuv1ImageReader.getSurface());
Log.v(TAG, " .. added YUV ImageReader " + mCameraInfoCache.getYuvStream1Size().getWidth() +
" x " + mCameraInfoCache.getYuvStream1Size().getHeight());
if (mIsDepthCloudSupported) {
outputSurfaces.add(mDepthCloudImageReader.getSurface());
Log.v(TAG, " .. added Depth cloud ImageReader");
if (SECOND_YUV_IMAGEREADER_STREAM) {
outputSurfaces.add(mYuv2ImageReader.getSurface());
Log.v(TAG, " .. added YUV ImageReader " + mCameraInfoCache.getYuvStream2Size().getWidth() +
" x " + mCameraInfoCache.getYuvStream2Size().getHeight());
if (SECOND_SURFACE_TEXTURE_STREAM) {
outputSurfaces.add(mSurfaceTextureSurface);
Log.v(TAG, " .. added SurfaceTexture");
if (RAW_STREAM_ENABLE && mCameraInfoCache.rawAvailable()) {
outputSurfaces.add(mRawImageReader.getSurface());
Log.v(TAG, " .. added Raw ImageReader " + mCameraInfoCache.getRawStreamSize().getWidth() +
" x " + mCameraInfoCache.getRawStreamSize().getHeight());
if (USE_REPROCESSING_IF_AVAIL && mCameraInfoCache.isYuvReprocessingAvailable()) {
outputSurfaces.add(mJpegImageReader.getSurface());
Log.v(TAG, " .. added JPEG ImageReader " + mCameraInfoCache.getJpegStreamSize().getWidth() +
" x " + mCameraInfoCache.getJpegStreamSize().getHeight());
try {
if (USE_REPROCESSING_IF_AVAIL && mCameraInfoCache.isYuvReprocessingAvailable()) {
InputConfiguration inputConfig = new InputConfiguration(mCameraInfoCache.getYuvStream1Size().getWidth(),
mCameraInfoCache.getYuvStream1Size().getHeight(), ImageFormat.YUV_420_888);
mCameraDevice.createReprocessableCaptureSession(inputConfig, outputSurfaces,
mSessionStateCallback, null);
Log.v(TAG, " Call to createReprocessableCaptureSession complete.");
} else {
mCameraDevice.createCaptureSession(outputSurfaces, mSessionStateCallback, null);
Log.v(TAG, " Call to createCaptureSession complete.");
} catch (CameraAccessException e) {
Log.e(TAG, "Error configuring ISP.");
使用zsl的方式的话,就需要输入InputConfiguration配置数据,好让底层的camera hal复用这部分数据,我们也能真正达到zsl的目的。
InputConfiguration inputConfig = new InputConfiguration(mCameraInfoCache.getYuvStream1Size().getWidth(),
mCameraInfoCache.getYuvStream1Size().getHeight(), ImageFormat.YUV_420_888);
mCameraDevice.createReprocessableCaptureSession(inputConfig, outputSurfaces,
mSessionStateCallback, null);
mSessionStateCallback是当前captureSession所处状态的回调,我们会在captureSession的onReady回调函数中设置ImageWriter对象:
ImageWriter mImageWriter;
private CameraCaptureSession.StateCallback mSessionStateCallback = new LoggingCallbacks.SessionStateCallback() {
@Override
public void onReady(CameraCaptureSession session) {
Log.v(TAG, "capture session onReady(). HAL capture session took: (" + (SystemClock.elapsedRealtime() - CameraTimer.t_session_go) + " ms)");
mCurrentCaptureSession = session;
issuePreviewCaptureRequest(false);
if (session.isReprocessable()) {
mImageWriter = ImageWriter.newInstance(session.getInputSurface(), IMAGEWRITER_SIZE);
mImageWriter.setOnImageReleasedListener(
new ImageWriter.OnImageReleasedListener() {
@Override
public void onImageReleased(ImageWriter writer) {
Log.v(TAG, "ImageWriter.OnImageReleasedListener onImageReleased()");
}, null);
Log.v(TAG, "Created ImageWriter.");
super.onReady(session);
session.getInputSurface() 表示之前输入的inputConfiguration数据,这个数据暂时初始化放在ImageWriter中。后续每次得到的预览的最后一帧数据都会放在ImageWriter对象中,直接送入到底层。
2.5 设置预览
try {
CaptureRequest.Builder b1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
b1.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_USE_SCENE_MODE);
b1.set(CaptureRequest.CONTROL_SCENE_MODE, CameraMetadata.CONTROL_SCENE_MODE_FACE_PRIORITY);
if (AFtrigger) {
b1.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
} else {
b1.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
b1.set(CaptureRequest.NOISE_REDUCTION_MODE, mCaptureNoiseMode);
b1.set(CaptureRequest.EDGE_MODE, mCaptureEdgeMode);
b1.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, mCaptureFace ? mCameraInfoCache.bestFaceDetectionMode() : CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF);
Log.v(TAG, " .. NR=" + mCaptureNoiseMode + " Edge=" + mCaptureEdgeMode + " Face=" + mCaptureFace);
if (mCaptureYuv1) {
b1.addTarget(mYuv1ImageReader.getSurface());
Log.v(TAG, " .. YUV1 on");
if (mCaptureRaw) {
b1.addTarget(mRawImageReader.getSurface());
b1.addTarget(mPreviewSurface);
if (mIsDepthCloudSupported && !mCaptureYuv1 && !mCaptureYuv2 && !mCaptureRaw) {
b1.addTarget(mDepthCloudImageReader.getSurface());
if (mCaptureYuv2) {
if (SECOND_SURFACE_TEXTURE_STREAM) {
b1.addTarget(mSurfaceTextureSurface);
if (SECOND_YUV_IMAGEREADER_STREAM) {
b1.addTarget(mYuv2ImageReader.getSurface());
Log.v(TAG, " .. YUV2 on");
if (AFtrigger) {
b1.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
mCurrentCaptureSession.capture(b1.build(), mCaptureCallback, mOpsHandler);
b1.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
mCurrentCaptureSession.setRepeatingRequest(b1.build(), mCaptureCallback, mOpsHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "Could not access camera for issuePreviewCaptureRequest.");
这儿很多代码,核心的代码只有3行:
CaptureRequest.Builder b1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
b1.addTarget(mYuv1ImageReader.getSurface());
mCurrentCaptureSession.setRepeatingRequest(b1.build(), mCaptureCallback, mOpsHandler);
传入了初始定义的full yuv的ImageReader的surface结构,然后在CaptureCallback中需要获取captureResult,这个数据在拍照的时候还有用处。
2.6 CaptureCallback处理
private CameraCaptureSession.CaptureCallback mCaptureCallback = new LoggingCallbacks.SessionCaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
if (!mFirstFrameArrived) {
mFirstFrameArrived = true;
long now = SystemClock.elapsedRealtime();
long dt = now - CameraTimer.t0;
long camera_dt = now - CameraTimer.t_session_go + CameraTimer.t_open_end - CameraTimer.t_open_start;
long repeating_req_dt = now - CameraTimer.t_burst;
Log.v(TAG, "App control to first frame: (" + dt + " ms)");
Log.v(TAG, "HAL request to first frame: (" + repeating_req_dt + " ms) " + " Total HAL wait: (" + camera_dt + " ms)");
mMyCameraCallback.receivedFirstFrame();
mMyCameraCallback.performanceDataAvailable((int) dt, (int) camera_dt, null);
publishFrameData(result);
// Used for reprocessing.
mLastTotalCaptureResult = result;
super.onCaptureCompleted(session, request, result);
这个mLastTotalCaptureResult是预览capture的时候捕获的一个captureResult,后续处理的时候会用到
// Last total capture result
TotalCaptureResult mLastTotalCaptureResult;
2.7 拍照处理
终于来到了最核心的步骤,这儿的拍照处理,当然不会像之前那样直接调用CaptureSession的capture方法,因为执行capture方法,就必定要重新发送capture request,重新获取帧数据。
但是我们现在已经有了帧数据,就是之前保存的帧数据,这时候帧数据就起到了非常重要的作用。
void runReprocessing() {
if (mYuv1LastReceivedImage == null) {
Log.e(TAG, "No YUV Image available.");
return;
mImageWriter.queueInputImage(mYuv1LastReceivedImage);
Log.v(TAG, " Sent YUV1 image to ImageWriter.queueInputImage()");
try {
CaptureRequest.Builder b1 = mCameraDevice.createReprocessCaptureRequest(mLastTotalCaptureResult);
// Todo: Read current orientation instead of just assuming device is in native orientation
b1.set(CaptureRequest.JPEG_ORIENTATION, mCameraInfoCache.sensorOrientation());
b1.set(CaptureRequest.JPEG_QUALITY, (byte) 95);
b1.set(CaptureRequest.NOISE_REDUCTION_MODE, mReprocessingNoiseMode);
b1.set(CaptureRequest.EDGE_MODE, mReprocessingEdgeMode);
b1.addTarget(mJpegImageReader.getSurface());
mCurrentCaptureSession.capture(b1.build(), mReprocessingCaptureCallback, mOpsHandler);
mReprocessingRequestNanoTime = System.nanoTime();
} catch (CameraAccessException e) {
Log.e(TAG, "Could not access camera for issuePreviewCaptureRequest.");
mYuv1LastReceivedImage = null;
Log.v(TAG, " Reprocessing request submitted.");
mImageWriter.queueInputImage(mYuv1LastReceivedImage);将预览最后一帧数据放入ImageWriter的input 队列中。
// Reprocessing capture completed.
private CameraCaptureSession.CaptureCallback mReprocessingCaptureCallback = new LoggingCallbacks.SessionCaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
Log.v(TAG, "Reprocessing onCaptureCompleted()");
处理完成之后回调onCaptureCompleted(...)函数。
zsl方案有多快:原图拍照一张150ms,快得一笔
下面是截图样例:
优化之后的流程可以总结成如下:
作者:码上就说
链接:https://www.jianshu.com/p/3beb7403025f
友情推荐:
Android 开发干货集锦
至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!
点个在看,方便您使用时快速查找!
Zero Shutter Lag.
ubiFocus:
高通对照片后期的一种处理技术,可以利用多张照片来实现拍照不对焦,拍好之后随便选择对焦点的功能。ZSL出现背景:拍照的延时主要分两个方面:
1. 从按下拍照键到照片拍好放到内存里。
2. APK的后期处理。对于BSP这边,能优化的主要是第一条,因此ZSL的出现正
备注:博文仍然是分析Android5.1 API1代码的学习笔记。
这次笔记主要是来分析ZSL流程的。ZSL(zear shutter lag)即零延时,就是在拍照时不停预览就可以拍照.由于有较好的用户体验度,该feature是现在大部分手机都拥有的功能。
下面不再贴出大量代码来描述过程,直接上图。下图是画了2个小时整理出来的Android5.1 Zsl的基本流程,可以看到与ZSL密切
ZSL(zero shutter lag):google翻译为零快门滞后。也是就是我们经常讲的零延时拍照。
在日常生活中,相信大家拍照的时候有一种感觉就是,拍照会有延迟,这总感觉是很微妙的,为了解决这个问题,开发出了另一种拍照模式,零延时拍照,即拍即看。
一般情况下,拍照流程如下:
从图中我们可以看到,开始预览,出预览帧,这个时候已经正常预览了,这个时候按下shutter,开始拍照,开始...
所拍即所得,按下快门那一刻拍到的照片就是当时所看到的照片,它是相对于普通模式拍照来讲的。
普通模式拍照:
按下快门后需要进行一系列地处理和校正,如对焦、曝光、白平衡等动作,再进行编码。譬如在预览30fps的情况下,按下快门时在第1帧,而实际上拍得的照片是第8帧的图像。
ZSL模式拍照:
会缓存若干帧,在按下快门那一刻,直接提取缓存帧进行编码保存照片。譬如在预览30fps的情况下,按下快门时是在第1帧,则实际上拍得的照片是第1帧的图像。
// Run this program only in the Java mode inside the IDE,
// not on Processing.js (web mode)!!
import processing.video.*;
Capture cam;
void setup() {
size(600, 300);
cam = new Capture(this, 32...
2、使用Uri不返回数据
使用Bitmap有可能会导致图片过大,而不能返回实际大小的图片,我将采用大图Uri,小图Bitmap的数据存储方式。
我们将要使用到URI来保存拍照后的图片:
privatestaticfinalS...