前言
在開發Android應用的時候,如果需要調用攝像頭獲取拍攝的照片,除了通過Intent調用系統現有相機應用拍攝照片之外,還可以通過直接調用Camera硬件去去獲取攝像頭拍攝的照片。本篇博客將講解如何在Android應用中通過Camera拍攝照片,這個對開發相機類應用尤為重要,同樣最后也將以一個簡單的Demo演示。
本篇博客的主要內容:
Camera是Android攝像頭硬件的相機類,位於硬件包"android.hardware.Camera"下。它主要用於攝像頭捕獲圖片、啟動/停止預覽圖片、拍照、獲取視頻幀等,它是設備本地的服務,負責管理設備上的攝像頭硬件。
Camera既然用於管理設備上的攝像頭硬件,那么它也為開發人員提供了相應的方法,並且這些方法大部分都是native的,用C++在底層實現,下面簡單介紹一下Camera的一些方法:
- static Camera open():打開Camera,返回一個Camera實例。
- static Camera open(int cameraId):根據cameraId打開一個Camera,返回一個Camera實例。
- final void release():釋放掉Camera的資源。
- static int getNumberOfCameras():獲取當前設備支持的Camera硬件個數。
- Camera.Parameters getParameters():獲取Camera的各項參數設置類。
- void setParameters(Camera.Parameters params):通過params把Camera的各項參數寫入到Camera中。
- final void setDisplayOrientation(int degrees):攝像預覽的旋轉度。
- final void setPreviewDisplay(SurfaceHolder holder):設置Camera預覽的SurfaceHolder。
- final void starPreview():開始Camera的預覽。
- final void stopPreview():停止Camera的預覽
- final void autoFocus(Camera.AutoFocusCallback cb):自動對焦。
- final takePicture(Camera.ShutterCallback shutter,Camera.PictureCallback raw,Camera.PictureCallback jpeg):拍照。
- final void lock():鎖定Camera硬件,使其他應用無法訪問。
- final void unlock():解鎖Camera硬件,使其他應用可以訪問。
上面已經介紹了Camera的常用方法,下面根據這些方法詳細講解Android下使用Camera開發拍照應用最基本的過程:
- 使用open()方法獲取一個Camera對象,鑒於Android設備可能配置了多個攝像頭,open()方法可以通過攝像頭Id開啟指定的攝像頭。
- 為Camera對象設置預覽類,它是一個SurfaceHolder對象,通過setPreviewDisplay(SurfaceHolder)方法設置。
- 調用startPreview()方法開始Camera對象的預覽。
- 調用takePicture()方法進行拍照,其中可以通過Camera.PictureCallback()回調獲得拍攝的Image數據。
- 當拍攝完成后,需要調用stopPreview()方法停止預覽,並使用release()釋放Camera占用的資源。
以上介紹的步驟都是最基本的過程,是必不可少的。Camera沒有提供公開的構造函數,只能通過open()方法獲取,並且必須設置一個預覽類SurfaceHolder,如果不設置的話,將無法使用Camera。在使用完成Camera之后,必須使用release()釋放Camera資源。
因為Android的開源,所以其支持的設備多而雜,在使用Camera的時候,最好驗證一下當前運行的設備上是否配備了攝像頭硬件。這里需要用到一個PackageManager,它用於檢測應用安裝設備的各種信息,可以通過Context.getPackageManager()方法獲取。可以通過PackageManager.hasSystemFeature(String name)方法檢測設備是否支持攝像頭操作,它傳遞的是一個String類型的參數,被PackageManager定義為了常量,返回一個Boolean數據,驗證是否檢測通過,對於Camera而言,可以檢測如下信息:
- FEATURE_CAMERA:設備是否有攝像頭。
- FEATURE_CAMERA_ANY:設備至少有一個攝像頭。
- FEATURE_CAMERA_AUTOFOCUS:設備支持的攝像頭是否支持自動對焦
- FEATURE_CAMERA_FLASH:設備是否配備閃光燈。
- FEATURE_CAMERA_FRONT:設備是否有一個前置攝像頭。
一般而言,檢測FEATURE_CAMERA即可:
1 /** 檢測設備是否存在Camera硬件 */ 2 private boolean checkCameraHardware(Context context) { 3 if (context.getPackageManager().hasSystemFeature( 4 PackageManager.FEATURE_CAMERA)) { 5 // 存在 6 return true; 7 } else { 8 // 不存在 9 return false; 10 } 11 }
Camera的預覽,需要放到一個SurfaceView中,出於安全的考慮,在設計Android的時候就規定,如果需要調用Camera,必須為其制定顯示在屏幕上的SurfaceView預覽,否則將無法使用Camera。關於SurfaceView和SurfaceHolder的內容,在之前的博客中已經詳細講解,這里不再贅述,不清楚的朋友可以看看之前的博客:Android--SurfaceView。
下面我們直接使用一個類去繼承SurfaceView當做Camera的預覽類:
1 package cn.bgxt.camerapicturedemo; 2 3 import java.io.IOException; 4 import android.content.Context; 5 import android.hardware.Camera; 6 import android.util.Log; 7 import android.view.SurfaceHolder; 8 import android.view.SurfaceView; 9 10 /** 11 * 定義一個預覽類 12 */ 13 public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { 14 private static final String TAG = "main"; 15 private SurfaceHolder mHolder; 16 private Camera mCamera; 17 18 public CameraPreview(Context context, Camera camera) { 19 super(context); 20 mCamera = camera; 21 22 // 通過SurfaceView獲得SurfaceHolder 23 mHolder = getHolder(); 24 // 為SurfaceHolder指定回調 25 mHolder.addCallback(this); 26 // 設置Surface不維護自己的緩沖區,而是等待屏幕的渲染引擎將內容推送到界面 在Android3.0之后棄用 27 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 28 } 29 30 public void surfaceCreated(SurfaceHolder holder) { 31 // 當Surface被創建之后,開始Camera的預覽 32 try { 33 mCamera.setPreviewDisplay(holder); 34 mCamera.startPreview(); 35 } catch (IOException e) { 36 Log.d(TAG, "預覽失敗"); 37 } 38 } 39 40 public void surfaceDestroyed(SurfaceHolder holder) { 41 42 } 43 44 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 45 // Surface發生改變的時候將被調用,第一次顯示到界面的時候也會被調用 46 if (mHolder.getSurface() == null){ 47 // 如果Surface為空,不繼續操作 48 return; 49 } 50 51 // 停止Camera的預覽 52 try { 53 mCamera.stopPreview(); 54 } catch (Exception e){ 55 Log.d(TAG, "當Surface改變后,停止預覽出錯"); 56 } 57 58 // 在預覽前可以指定Camera的各項參數 59 60 // 重新開始預覽 61 try { 62 mCamera.setPreviewDisplay(mHolder); 63 mCamera.startPreview(); 64 65 } catch (Exception e){ 66 Log.d(TAG, "預覽Camera出錯"); 67 } 68 } 69 }
當指定了Camera的預覽類,並開始預覽之后,就可以通過takePicture()方法進行拍照了,下面是它的完整簽名:
public final void taskPicture(Camera.ShuffterCallback shutter,Camera.PictureCallback raw,Camera.PictureCallback postview,Camera.PictureCallback jpeg)
它將以異步的方式從Camera中獲取圖像,具有多個回調類作為參數,並且都可以為null,下面分別介紹這些參數的意義:
- shutter:在按下快門的時候回調,這里可以播放一段聲音。
- raw:從Camera獲取到未經處理的圖像。
- postview:從Camera獲取一個快速預覽的圖片,不是所有設備都支持。
- jpeg:從Camera獲取到一個經過壓縮的jpeg圖片。
雖然raw、postview、jpeg都是Camera.PictureCallback回調,但是一般我們只需要獲取jpeg,其他傳null即可,Camera.PictureCallback里需要實現一個方法onPictureTaken(byte[] data,Camera camera),data及為圖像數據。值得注意的是,一般taskPicture()方法拍照完成之后,SurfaceView都會停留在拍照的瞬間,需要重新調用startPreview()才會繼續預覽。
如果直接使用taskPicture()進行拍照的話,Camera是不會進行自動對焦的,這里需要使用Camera.autoFocus()方法進行對焦,它傳遞一個Camera.AutoFocusCallback參數,用於自動對焦完成后回調,一般會在它對焦完成在進行taskPicture()拍照。
上面已經介紹了使用Camera的基本步驟以及涉及的內容,這里通過一個簡單的Demo演示一下,使用上面附上代碼的預覽類進行Camera預覽。代碼中注釋比較完整,這里不再贅述。
布局代碼:activity_main.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:orientation="horizontal" 4 android:layout_width="fill_parent" 5 android:layout_height="fill_parent" 6 > 7 <FrameLayout 8 android:id="@+id/camera_preview" 9 android:layout_width="fill_parent" 10 android:layout_height="fill_parent" 11 android:layout_weight="1" 12 /> 13 14 <Button 15 android:id="@+id/button_capture" 16 android:text="拍照" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:layout_gravity="center" 20 /> 21 </LinearLayout>
實現代碼:MainActivity.java
1 package cn.bgxt.camerapicturedemo; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 6 import android.os.Bundle; 7 import android.util.Log; 8 import android.view.View; 9 import android.widget.Button; 10 import android.widget.FrameLayout; 11 import android.app.Activity; 12 import android.content.Context; 13 import android.content.pm.PackageManager; 14 import android.hardware.Camera; 15 import android.hardware.Camera.AutoFocusCallback; 16 import android.hardware.Camera.PictureCallback; 17 18 public class MainActivity extends Activity { 19 protected static final String TAG = "main"; 20 private Camera mCamera; 21 private CameraPreview mPreview; 22 23 @Override 24 protected void onCreate(Bundle savedInstanceState) { 25 super.onCreate(savedInstanceState); 26 setContentView(R.layout.activity_main); 27 28 mCamera = getCameraInstance(); 29 30 // 創建預覽類,並與Camera關聯,最后添加到界面布局中 31 mPreview = new CameraPreview(this, mCamera); 32 FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); 33 preview.addView(mPreview); 34 35 36 Button captureButton = (Button) findViewById(R.id.button_capture); 37 captureButton.setOnClickListener(new View.OnClickListener() { 38 @Override 39 public void onClick(View v) { 40 // 在捕獲圖片前進行自動對焦 41 mCamera.autoFocus(new AutoFocusCallback() { 42 43 @Override 44 public void onAutoFocus(boolean success, Camera camera) { 45 // 從Camera捕獲圖片 46 mCamera.takePicture(null, null, mPicture); 47 } 48 }); 49 } 50 }); 51 } 52 53 /** 檢測設備是否存在Camera硬件 */ 54 private boolean checkCameraHardware(Context context) { 55 if (context.getPackageManager().hasSystemFeature( 56 PackageManager.FEATURE_CAMERA)) { 57 // 存在 58 return true; 59 } else { 60 // 不存在 61 return false; 62 } 63 } 64 65 /** 打開一個Camera */ 66 public static Camera getCameraInstance() { 67 Camera c = null; 68 try { 69 c = Camera.open(); 70 } catch (Exception e) { 71 Log.d(TAG, "打開Camera失敗失敗"); 72 } 73 return c; 74 } 75 76 private PictureCallback mPicture = new PictureCallback() { 77 78 @Override 79 public void onPictureTaken(byte[] data, Camera camera) { 80 // 獲取Jpeg圖片,並保存在sd卡上 81 File pictureFile = new File("/sdcard/" + System.currentTimeMillis() 82 + ".jpg"); 83 try { 84 FileOutputStream fos = new FileOutputStream(pictureFile); 85 fos.write(data); 86 fos.close(); 87 } catch (Exception e) { 88 Log.d(TAG, "保存圖片失敗"); 89 } 90 } 91 }; 92 93 @Override 94 protected void onDestroy() { 95 // 回收Camera資源 96 if(mCamera!=null){ 97 mCamera.stopPreview(); 98 mCamera.release(); 99 mCamera=null; 100 } 101 super.onDestroy(); 102 } 103 104 }
效果展示:


上面介紹Camera拍照的時候介紹了兩個回調,Camera還提供了一些其他的回調的事件監聽方法,這里簡單介紹幾個常用的:
- final void setErrorCallback(Camera.ErrorCallback cb):Camera發送錯誤的時候回調,可以在其中進行錯誤的后續處理。
- final void setPreviedCallback(Camera.PreviewCallback cb):Camera預覽界面發生變化的時候回調,可以在其中獲取到Camera捕獲到的幀圖像。
Camera作為一個攝像頭硬件的調用類,還為我們提供了詳細的設置攝像頭各項參數的方法,比如閃光燈的模式、自動對焦的模式等。步驟是使用Camera.getParameters()方法獲取到Camera.Parameters對象,在其中設置好攝像頭的各項參數后,再通過Camera.setParameters()方法寫入到Camera對象中即可。這個設置必須在使用startPreview()之前完成。關於Camera參數的設置,比較細致,不在本篇博客的內容之中,以后有機會再詳細介紹。

