ZSL的概念

ZSL (zero shutter lag) 中文名称为零延时拍照,是为了减少拍照延时,让拍照&回显瞬间完成的一种技术。

Single Shot

当开始预览后,sensor 和 VFE 会产生 preview 和 snapshot帧, 而最新的snapshot 帧数据会被存储在buffer 中。当拍照被触发,系统计算实际的拍照时间,找出在buffer中的相应帧,然后返回帧到用户,这就是所谓的“ZERO”。

 

 

系统计算出shutter lag的时间,然后把某个帧认作是拍照实时的那帧数据。

ZSL的实现机制

因为ZSL实现需要实现一下几点:

1. 一个surfaceView用于预览

2. 一个队列缓存snapshot的数据

3. 拍照动作获取队列某桢数据作为拍照数据输出

4. 输出的照片需要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拍照功能。

Camera API2.0之ZSL预览

在文件OneCameraZslImpl.java文件中可以找到启动startpreview的代码,代码如下:

@Override
public void startPreview(Surface previewSurface, CaptureReadyCallback listener) {
mPreviewSurface
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);
/用于预览的surface
用于拍照的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.
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;
   }

 

Camera API2.0之ZSL拍照

根据前面的说明执行拍照命令其实去获取缓存队列中的数据,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之ZSL的问题点

不可否认Camera API2.0 给了应用更大的操作空间,对于以后的实时渲染有更多的操作性。但是会存在如下问题:

1.占用应用层太多的内存,ZSL需要在应用层存储10个buffer保存图片,势必容易造成资源问题,所以google在代码中强制追加了用于ZSL的图片size不同大于1080P.

YUV倒JPEG的转码对CPU计算能力要求,如果CPU计算能力不强,会导致慢的问题。Google在这里追加了缓存数据就是JPEG的处理。