編寫:kesenhoo - :http://developer.android.com/training/camera/cameradirect.html
在這一節(jié)課,我們會討論如何通過使用Android框架所提供的API來直接控制相機(jī)硬件。
直接控制相機(jī),比起向已有的相機(jī)應(yīng)用請求圖片或視頻,要復(fù)雜一些。這節(jié)課將會講解如何創(chuàng)建一個特殊的相機(jī)應(yīng)用或?qū)⑾鄼C(jī)整合在我們的應(yīng)用當(dāng)中。
獲取一個 Camera 對象是直接控制相機(jī)的第一步。正如Android自帶的相機(jī)程序一樣,比較好的訪問相機(jī)的方式是在onCreate()方法里面另起一個線程來打開相機(jī)。這種辦法可以避免因為啟動時間較長導(dǎo)致UI線程被阻塞。另外還有一種更好的方法:可以把打開相機(jī)的操作延遲到onResume()方法里面去執(zhí)行,這樣可以使得代碼更容易重用,還能保持控制流程更為簡單。
如果我們在執(zhí)行Camera.open()方法的時候相機(jī)正在被另外一個應(yīng)用使用,那么函數(shù)會拋出一個Exception,我們可以利用try
語句塊進(jìn)行捕獲:
private boolean safeCameraOpen(int id) {
boolean qOpened = false;
try {
releaseCameraAndPreview();
mCamera = Camera.open(id);
qOpened = (mCamera != null);
} catch (Exception e) {
Log.e(getString(R.string.app_name), "failed to open Camera");
e.printStackTrace();
}
return qOpened;
}
private void releaseCameraAndPreview() {
mPreview.setCamera(null);
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
自從API Level 9開始,相機(jī)框架可以支持多個相機(jī)。如果使用舊的API,在調(diào)用open()時不傳入?yún)?shù) ,那么我們會獲取后置攝像頭。
拍照通常需要向用戶提供一個預(yù)覽界面來顯示待拍攝的事物。我們可以使用SurfaceView來展現(xiàn)照相機(jī)采集的圖像。
我們需要使用Preview類來顯示預(yù)覽界面。這個類需要實現(xiàn)android.view.SurfaceHolder.Callback
接口,用這個接口把相機(jī)硬件獲取的數(shù)據(jù)傳遞給應(yīng)用。
class Preview extends ViewGroup implements SurfaceHolder.Callback {
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Preview(Context context) {
super(context);
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
...
}
Preview類必須在實時圖像預(yù)覽開始之前傳遞給Camera對象。
一個Camera實例與它相關(guān)的Preview必須以特定的順序來創(chuàng)建,其中Camera對象首先被創(chuàng)建。在下面的示例中,初始化Camera的動作被封裝了起來,這樣,無論用戶想對Camera做什么樣的改變,Camera.startPreview()都會被setCamera()
調(diào)用。另外,Preview對象必須在surfaceChanged()
這一回調(diào)方法里面重新啟用(restart)。
public void setCamera(Camera camera) {
if (mCamera == camera) { return; }
stopPreviewAndFreeCamera();
mCamera = camera;
if (mCamera != null) {
List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedPreviewSizes = localSizes;
requestLayout();
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
e.printStackTrace();
}
// Important: Call startPreview() to start updating the preview
// surface. Preview must be started before you can take a picture.
mCamera.startPreview();
}
}
相機(jī)設(shè)置可以改變拍照的方式,從縮放級別到曝光補(bǔ)償?shù)取O旅娴睦觾H僅演示了如何改變預(yù)覽大小,更多設(shè)置請參考相機(jī)應(yīng)用的源代碼。
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
// Important: Call startPreview() to start updating the preview surface.
// Preview must be started before you can take a picture.
mCamera.startPreview();
}
大多數(shù)相機(jī)程序會鎖定預(yù)覽為橫屏狀態(tài),因為該方向是相機(jī)傳感器的自然方向。當(dāng)然這一設(shè)定并不會阻止我們?nèi)ヅ呢Q屏的照片,因為設(shè)備的方向信息會被記錄在EXIF頭中。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下,改變預(yù)覽的方向。然而,對于Android API Level 14及以下版本的系統(tǒng),在改變方向之前,我們必須先停止預(yù)覽,然后再去重啟它。
只要預(yù)覽開始之后,可以使用Camera.takePicture()方法拍攝照片。我們可以創(chuàng)建Camera.PictureCallback與Camera.ShutterCallback對象并將他們傳遞到Camera.takePicture()中。
如果我們想要進(jìn)行連拍,可以創(chuàng)建一個Camera.PreviewCallback并實現(xiàn)onPreviewFrame()方法。我們可以拍攝選中的預(yù)覽幀,或是為調(diào)用takePicture()建立一個延遲。
在拍攝好圖片后,我們必須在用戶拍下一張圖片之前重啟預(yù)覽。下面的示例使用快門按鈕來實現(xiàn)重啟。
@Override
public void onClick(View v) {
switch(mPreviewState) {
case K_STATE_FROZEN:
mCamera.startPreview();
mPreviewState = K_STATE_PREVIEW;
break;
default:
mCamera.takePicture( null, rawCallback, null);
mPreviewState = K_STATE_BUSY;
} // switch
shutterBtnConfig();
}
當(dāng)應(yīng)用使用好相機(jī)后,我們有必要進(jìn)行清理操作。特別地,我們必須釋放Camera對象,不然的話可能會引起其他應(yīng)用崩潰,包括我們自己應(yīng)用的新實例。
那么何時應(yīng)該停止預(yù)覽并釋放相機(jī)呢?在預(yù)覽的Surface被摧毀之后,可以做停止預(yù)覽與釋放相機(jī)的操作。如下面Preview類中的方法所做的那樣:
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
// Call stopPreview() to stop updating the preview surface.
mCamera.stopPreview();
}
}
/**
* When this function returns, mCamera will be null.
*/
private void stopPreviewAndFreeCamera() {
if (mCamera != null) {
// Call stopPreview() to stop updating the preview surface.
mCamera.stopPreview();
// Important: Call release() to release the camera for use by other
// applications. Applications should release the camera immediately
// during onPause() and re-open() it during onResume()).
mCamera.release();
mCamera = null;
}
}
在這節(jié)課的前部分中,這一些系列的動作也是setCamera()
方法的一部分,因此初始化一個相機(jī)的動作,總是從停止預(yù)覽開始的。