ZSL的概念
ZSL (zero shutter lag) 中文名稱爲零延時拍照,是爲了減少拍照延時,讓拍照&回顯瞬間完成的一種技術。
Single Shot
當開始預覽後,sensor 和 VFE 會產生 preview 和 snapshot幀, 而最新的snapshot 幀數據會被存儲在buffer 中。當拍照被觸發,系統計算實際的拍照時間,找出在buffer中的相應幀,然後返回幀到用戶,這就是所謂的“ZERO”。
系統計算出shutter lag的時間,然後把某個幀認作是拍照實時的那幀數據。
ZSL的實現機制
因爲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拍照功能。
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;
}
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的處理。