在本課程中,我們將討論如何使用架構 API 來直接控制相機硬體。
注意:本頁面所述是指已淘汰的 Camera 類別。建議使用 CameraX,或者,在特定情況使用 Camera2。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。
比起從現有的相機應用程式要求圖片或影片,直接控制裝置相機的程式碼要多上許多。不過,如果您想要建構專用的相機應用程式,或是完整整合至應用程式的 UI,本課程介紹可以如何做到。
請參閱下列相關資源:
開啟相機物件
如要直接控制相機,首先要取得 Camera
物件的執行個體。如同 Android 本身相機應用程式的做法,存取相機的建議作法是,在透過 onCreate()
啟動的另一個單獨的執行緒中開啟 Camera
。這是一個不錯的做法,因為可能需要一些時間才能完成,並且可能會使 UI 執行緒停滯。在更基本的實作中,開啟相機可轉由 onResume()
方法進行,可達到重複使用程式碼的效果,並簡化控制流程。
呼叫 Camera.open()
時,如果其他應用程式正在使用相機,系統會擲回例外狀況,因此會將該物件納入 try
區塊。
private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() mCamera = Camera.open(id) true } catch (e: Exception) { Log.e(getString(R.string.app_name), "failed to open Camera") e.printStackTrace() false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) mCamera?.also { camera -> camera.release() mCamera = null } }
private boolean safeCameraOpen(int id) { boolean qOpened = false; try { releaseCameraAndPreview(); camera = Camera.open(id); qOpened = (camera != null); } catch (Exception e) { Log.e(getString(R.string.app_name), "failed to open Camera"); e.printStackTrace(); } return qOpened; } private void releaseCameraAndPreview() { preview.setCamera(null); if (camera != null) { camera.release(); camera = null; } }
自 API 級別 9 開始,相機架構可支援多部相機。如果使用舊版 API,並且在沒有引數的情況下呼叫 open()
,則會先取用背面相機。
建立相機預覽
拍照時,使用者通常需要看到拍攝主體的預覽畫面,才會按下快門。方法是使用 SurfaceView
繪製相機感應器擷取的預覽畫面。
預覽類別
如要開始顯示預覽畫面,必須具備預覽類別。預覽畫面需要實作 android.view.SurfaceHolder.Callback
介面,用於將圖片資料從相機硬體傳送至應用程式。
class Preview( context: Context, val surfaceView: SurfaceView = SurfaceView(context) ) : ViewGroup(context), SurfaceHolder.Callback { var mHolder: SurfaceHolder = surfaceView.holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } ... }
class Preview extends ViewGroup implements SurfaceHolder.Callback { SurfaceView surfaceView; SurfaceHolder holder; Preview(Context context) { super(context); surfaceView = new SurfaceView(context); addView(surfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. holder = surfaceView.getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } ... }
預覽類別必須傳遞至 Camera
物件,才能開始進行即時圖片預覽,方式如下一節所示。
設定並開始預覽
必須以特定順序建立相機執行個體及其相關預覽,並優先顯示相機物件。下列程式碼片段封裝了相機的初始化程序,如此一來,每當使用者變更相機時,setCamera()
方法都會呼叫 Camera.startPreview()
。另外也必須在預覽類別 surfaceChanged()
回呼方法中重新啟動預覽。
fun setCamera(camera: Camera?) { if (mCamera == camera) { return } stopPreviewAndFreeCamera() mCamera = camera mCamera?.apply { mSupportedPreviewSizes = parameters.supportedPreviewSizes requestLayout() try { setPreviewDisplay(holder) } catch (e: IOException) { e.printStackTrace() } // Important: Call startPreview() to start updating the preview // surface. Preview must be started before you can take a picture. startPreview() } }
public void setCamera(Camera camera) { if (mCamera == camera) { return; } stopPreviewAndFreeCamera(); mCamera = camera; if (mCamera != null) { List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes(); supportedPreviewSizes = localSizes; requestLayout(); try { mCamera.setPreviewDisplay(holder); } 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(); } }
修改相機設定
相機設定會改變相機拍照的方式,包括對焦等級到曝光補償。這個範例只會變更預覽大小;詳情請參閱相機應用程式的原始碼。
override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { mCamera?.apply { // Now that the size is known, set up the camera parameters and begin // the preview. parameters?.also { params -> params.setPreviewSize(previewSize.width, previewSize.height) requestLayout() parameters = params } // Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. startPreview() } }
@Override 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(previewSize.width, previewSize.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(); }
設定預覽方向
大多數相機應用程式會將螢幕鎖定為橫向模式,因為這是相機感應器的自然方向。這項設定不會導致無法拍攝肖像模式相片,因為裝置的方向會記錄在 EXIF 標頭中。setCameraDisplayOrientation()
方法可讓您變更預覽的顯示方式,而不會影響圖片的記錄方式。不過,在 API 級別 14 之前的 Android 中,必須停止預覽,才能變更方向並重新啟動。
拍攝相片
預覽開始後,請使用 Camera.takePicture()
方法拍照。您可以建立 Camera.PictureCallback
和 Camera.ShutterCallback
物件,並將其傳遞至 Camera.takePicture()
。
如要持續擷取圖片,可以建立實作 onPreviewFrame()
的 Camera.PreviewCallback
。若要其中的內容,可以只擷取選取的預覽畫面,或設定延遲動作來呼叫 takePicture()
。
重新開始預覽
使用者拍照後,必須重新啟動預覽,才能拍攝其他相片。此範例是透過按住快門按鈕來重新啟動。
fun onClick(v: View) { previewState = if (previewState == K_STATE_FROZEN) { camera?.startPreview() K_STATE_PREVIEW } else { camera?.takePicture(null, rawCallback, null) K_STATE_BUSY } shutterBtnConfig() }
@Override public void onClick(View v) { switch(previewState) { case K_STATE_FROZEN: camera.startPreview(); previewState = K_STATE_PREVIEW; break; default: camera.takePicture( null, rawCallback, null); previewState = K_STATE_BUSY; } // switch shutterBtnConfig(); }
停止預覽並釋出相機
應用程式完成相機拍攝後,可開始進行清除。請特別注意,必須釋出 Camera
物件,否則可能會造成其他應用程式停止運作,包括應用程式的新執行個體。
何時應停止預覽並釋出相機?如此類方法中的 Preview
類別所示,刪除預覽介面時,即應停止預覽並釋出相機。
override fun surfaceDestroyed(holder: SurfaceHolder) { // Surface will be destroyed when we return, so stop the preview. // Call stopPreview() to stop updating the preview surface. mCamera?.stopPreview() } /** * When this function returns, mCamera will be null. */ private fun stopPreviewAndFreeCamera() { mCamera?.apply { // Call stopPreview() to stop updating the preview surface. 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()). release() mCamera = null } }
@Override 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; } }
如本課程之前所述,這個程序也是 setCamera()
方法的一部分,因此初始化相機一律從停止預覽開始。