ZSL (zero shutter lag) 中文名称为零延时拍照,是为了减少拍照延时,让拍照&回显瞬间完成的一种技术。
Single Shot
当开始预览后,sensor 和 VFE 会产生 preview 和 snapshot帧, 而最新的snapshot 帧数据会被存储在buffer 中。当拍照被触发,系统计算实际的拍照时间,找出在buffer中的相应帧,然后返回帧到用户,这就是所谓的“ZERO”。
系统计算出shutter lag的时间,然后把某个帧认作是拍照实时的那帧数据。
因为ZSL实现需要实现一下几点:
一个surfaceView用于预览
一个队列缓存snapshot的数据
拍照动作获取队列某桢数据作为拍照数据输出
输出的照片需要YUV->JPEG数据的转码
首先说一下ZSL功能在android4.4和android5.0上实现的区别。
Android4.4的实现对于2)步和3)步都是在HAL层实现,HAL层在维护缓存队列,当接收倒take_picture 命令时直接取得某桢缓存数据,进行转码,然后以正常拍照的流程利用@link android.hardware.Camera.PictureCallback通知应用层拍照的数据。
Android5.0的实现对于2)步和3)步都是在应用层实现,应用层在启动预览时给HAL层传递2个surface给HAL层,HAL层利用其中一个surface用于预览数据填充,一个surface用于填充snapshot的数据填充。应用层不断读取surface中snapshot的数据去维护一个缓存队列,当用户执行take_picture,读取缓存队列的数据作为拍照数据。
Android5.0中的应用层已经有实现ZSL类:
src/com/android/camera/one/v2/OneCameraZslImpl.java
默认该方法是没有被调用,因为HAL层默认是不支持,因为HAL层是没有实现代码的,需要各大不同厂商去实现实现后设置不同的才支持。
暂时不去考虑应用层如何去调用OneCameraZslImpl.java,直接带大家了解OneCameraZslImpl如何利用Camera API2.0实现ZSL拍照功能。
在文件OneCameraZslImpl.java文件中可以找到启动startpreview的代码,代码如下:
@Override
public void startPreview(Surface previewSurface, CaptureReadyCallback listener) {
mPreviewSurface = previewSurface;
setupAsync(mPreviewSurface, listener);
}
在SetupAsync传递两个参数,第一个参数mPreviewSurface为预览的surface,第二个为一个回调,从名称可以看出是一种为拍照准备完毕的回调。
*在android5.0的camera应用、camera framework四处有这种类似实现机制,似乎就是故意让人看不懂代码的。
在SetupAsync方法中会异步调用setup,启动预览:
private void setupAsync(final Surface previewSurface,
final CaptureReadyCallback listener) {
mCameraHandler.post(new Runnable() {
@Override
public void run() {
setup(previewSurface, listener);
}
});
}
现在可以查看setup方法,这个才是和HAL层交互的关键,也是应用层开始缓存拍照队列数据的关键。
private void setup(Surface previewSurface, final CaptureReadyCallback listener) {
.......
List<Surface> outputSurfaces = new ArrayList<Surface>(2);
outputSurfaces.add(previewSurface);//用于预览的surface
outputSurfaces.add(mCaptureImageReader.getSurface()); //用于拍照的surface
//mDevice为Framework的CameraDeviceImpl.java对象,
//也是app层和HAL层交互的对象
mDevice.createCaptureSession(outputSurfaces,
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigureFailed(CameraCaptureSession session) {
......
}
@Override
public void onConfigured(CameraCaptureSession session) {
.......//成功开始的操作
}
直接到framework查看createCaptureSession方法,在该方法中会创建新的CapureSession,创建成功以后会回调lisnter的onConfigured方法, 这样应用也可以获得新Sesssion,下面是createCaptureSession创建CapureSession的方法:
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@Override
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
.........
//创建新的Session
CameraCaptureSessionImpl newSession =
new CameraCaptureSessionImpl(mNextSessionId++,
outputs, callback, handler, this, mDeviceHandler,
configureSuccess);
......
}
}
查看应用的onConfigured方法,该方法中调用sendRepeatingCaptureRequest:
mCaptureSession = session;
.......
//执行缓存队列的处理
boolean success = sendRepeatingCaptureRequest();
if (success) {
mReadyStateManager.setInput(ReadyStateRequirement
.CAPTURE_NOT_IN_PROGRESS,
true);
mReadyStateManager.notifyListeners();
listener.onReadyForCapture();
} else {
listener.onSetupFailed();
}
sendRepeatingCaptureRequest方法中利用CameraDeviceImpl创建拍照请求并设置重复的拍照命令,保证开始更新缓存。
private boolean sendRepeatingCaptureRequest() {
builder = mDevice.
createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
//TEMPLATE_ZERO_SHUTTER_LAG 该参数很重要HAL层依据该参数
//确认是否需要启动ZSL数据格式
builder.addTarget(mPreviewSurface);
builder.addTarget(mCaptureImageReader.getSurface());
// 通知HAL执行重复的请求
mCaptureSession.setRepeatingRequest(builder.build(), mCaptureManager,
mCameraHandler);
return true;
}
根据前面的说明执行拍照命令其实去获取缓存队列中的数据,ZSL的缓存数据是利用framework提供的工具类ImageReader。
查看ImageReader的实例化:
mCaptureImageReader = ImageReader.newInstance(pictureSize.getWidth(),
pictureSize.getHeight(),
sCaptureImageFormat, MAX_CAPTURE_IMAGES);
其中sCaptureImageFormat的定义如下:
/**
* Set to ImageFormat.JPEG to use the hardware encoder, or
* ImageFormat.YUV_420_888 to use the software encoder. No other image
* formats are supported.
*/
private static final int sCaptureImageFormat = ImageFormat.YUV_420_888;
其中sCaptureImageFormat可以定义为JPEG,也可以定义为YUV_420_888,其中JEPG需要HAL转码,转码涉及到效率问题,设置为YUV_420_888则需要应用层转码,如果应用分配资源小,也可能直接导致应用over。
MAX_CAPTURE_IMAGES为定义的缓存数量。
/**
* The maximum number of images to store in the full-size ZSL ring buffer.
* <br>
* TODO: Determine this number dynamically based on available memory and the
* size of frames.
*/
private static final int MAX_CAPTURE_IMAGES = 10;
文件OneCameraZslImpl.java文件有takePicture方法,方法介绍如下:
@Override
public void takePicture(final PhotoCaptureParameters params,
final CaptureSession session) {
// 停止读取缓存
mReadyStateManager.setInput(
ReadyStateRequirement.CAPTURE_NOT_IN_PROGRESS, false);
//直接读取缓存图片
boolean capturedPreviousFrame = mCaptureManager.tryCaptureExistingImage(
new ImageCaptureTask(params, session), zslConstraints);
}
ImageCaptureTask实现了ImageCaptureListener 接口,实现了onImageCaptured方法:
@Override
public void onImageCaptured(Image image, TotalCaptureResult
captureResult) {
......
mReadyStateManager.setInput(
ReadyStateRequirement.CAPTURE_NOT_IN_PROGRESS, true);
mSession.startEmpty();
savePicture(image, mParams, mSession);
......
mParams.callback.onPictureTaken(mSession);
}
现在去查看如何实现拍照的在ImageCaptureManager类中方法如下:
public boolean tryCaptureExistingImage(final ImageCaptureListener onImageCaptured,
final List<CapturedImageConstraint> constraints) {
......
//创建选择的image对象
selector = new Selector<ImageCaptureManager.CapturedImage>() {
@Override
public boolean select(CapturedImage e) {
......
};
//这就是取得拍照数据的地方
final Pair<Long, CapturedImage> toCapture =
mCapturedImageBuffer.tryPinGreatestSelected(selector);
return tryExecuteCaptureOrRelease(toCapture, onImageCaptured);
}
在tryExecuteCaptureOrRelease方法中回调ImageCaptureTask的onImageCaptured方法,然后在onImageCaptured方法中调用savePicture方法保存数据,查看savePicture方法:
private void savePicture(Image image, final PhotoCaptureParameters captureParams,
CaptureSession session) {
// TODO: Add more exif tags here.
//
session.saveAndFinish(acquireJpegBytes(image), width, height, rotation, exif,
new OnMediaSavedListener() {
@Override
public void onMediaSaved(Uri uri) {
captureParams.callback.onPictureSaved(uri);
}
});
}
因为图片是由应用生成,应用应该负责文件Header和tag信息, 在该savePicture方法中填充图片的GSP、角度、高度和宽度信息。
在保存数据时需要把YUV转为jpeg,google 为了该问题专门做了一个so库该代码就在Camera的jni目录下,在Camera的Android.mk 文件中
LOCAL_JNI_SHARED_LIBRARIES := libjni_tinyplanet libjni_jpegutil
然后查看jni下的Android.mk 文件
# JpegUtil
include $(CLEAR_VARS)
LOCAL_CFLAGS := -std=c++11
LOCAL_NDK_STL_VARIANT := c++_static
LOCAL_LDFLAGS := -llog -ldl
LOCAL_SDK_VERSION := 9
LOCAL_MODULE := libjni_jpegutil
LOCAL_SRC_FILES := jpegutil.cpp jpegutilnative.cpp
不可否认Camera API2.0 给了应用更大的操作空间,对于以后的实时渲染有更多的操作性。但是会存在如下问题:
1.占用应用层太多的内存,ZSL需要在应用层存储10个buffer保存图片,势必容易造成资源问题,所以google在代码中强制追加了用于ZSL的图片size不同大于1080P.
YUV倒JPEG的转码对CPU计算能力要求,如果CPU计算能力不强,会导致慢的问题。Google在这里追加了缓存数据就是JPEG的处理。