Android Camera 相機開發詳解

在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架構上作了巨大的變更, 可是基於衆所周知的緣由,咱們還必須基於 Android 4.+ 系統進行開發。本文介紹的是Camera接口開發及其使用方法,經過本文章,你將全面地學會Camera接口的開發流程。java

本圖文與GitHubPages原文均爲本人原創android

YOOJIA-CAMERAS

調用系統相機/其它App完成拍攝操做

若是你的App的需求只是調用攝像頭拍照並拿到照片,老司機的建議是別本身實現拍照模塊,這裏面坑多水深。你徹底可使用Intent來調用系統相機或第三方具有拍照功能的App來拍照並獲取返回照片數據。git

建立一個Intent,指定兩個拍攝類型之一:github

  • MediaStore.ACTION_IMAGE_CAPTURE 拍攝照片;
  • MediaStore.ACTION_VIDEO_CAPTURE 拍攝視頻;

Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);數組

通用流程startActivityForResult()onActivityResult()就不表述了。說說拍攝照片的Intent參數吧。架構

首先是設置拍攝後返回數據的地址:ide

intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);工具

MediaStore.EXTRA_OUTPUT 參數用於指定拍攝完成後的照片/視頻的儲存路徑。你可使用Android默認的儲存照片目錄來保存:ui

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)this

也能夠是其它任意你喜歡的儲存目錄。若是你使用了App內部目錄,某些臨時文件如拍攝並上傳的頭像文件,在處理完成後,要記得將它刪除。這樣作的好處是減小App佔用儲存空間,手機用戶特別喜歡對佔用大儲存空間的App下重手刪除和清理空間。若是你必須保存大致積的文件,可使用公共空間來儲存,把包袱丟出去,私有空間僅保存應用配置數據。

相機其它設置,如指定拍攝照片的尺寸大小,照片質量等,待之後文章更新吧。

// TODO 是程序界最大的謊話

使用Camera開發照相功能

使用Camera API來開發拍照模塊須要費一番大功夫。下面是介紹我在開發NextQRCode項目中使用Camera API的方法和流程。

1.在 Android Manifest.xml 中聲明相機權限

開發第一步是在 Android Manifest.xml 文件中聲明使用相機的權限:

<uses-permission android:name="android.permission.CAMERA" />

有些同窗在開發時忘了聲明權限,運行時應用可能會崩潰掉。另外也要增長如下兩個特性聲明:

<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>

required屬性是說明這個特性是否必須知足。比方說示例的設置就是要求必須擁有相機設備但能夠沒有自動對焦功能。

這兩個聲明是可選的,它們用於應用商店(Google Play)過濾不支持相機和不支持自動對焦的設備。

另外在保存照片時須要寫入儲存器的權限,也須要加上讀寫儲存器的權限聲明:

<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />

2. 打開相機設備

如今市面上銷售的手機/平板等消費產品基本標配兩個攝像頭。如華爲P9,更有前置雙攝像頭。講真,我很好奇開發雙攝像頭的App是怎樣的體驗。在打開相機設備前,先獲取當前設備有多少個相機設備。若是你的開發需求裏包含切換先後攝像頭功能,能夠獲取攝像頭數量來判斷是否存在後置攝像頭。

int cameras = Camera.getNumberOfCameras();

這個接口返回值爲攝像頭的數量:非負整數。對應地,攝像頭序號爲: cameras - 1。例如在擁有先後攝像頭的手機設備上,其返回結果是2,則第一個攝像頭的cameraId0,一般對應手機背後那個大攝像頭;第二個攝像頭的cameraId1,一般對應着手機的前置自拍攝像頭;

相機是一個硬件設備資源,在使用設備資源前須要將它打開,能夠經過接口Camera.open(cameraId)來打開。參考如下代碼:

public static Camera openCamera(int cameraId) {
    try{
        return Camera.open(cameraId);
    }catch(Exception e) {
        return null;
    }
}

注意

打開相機設備可能會失敗,你必定要檢查打開操做是否成功。打開失敗的可能緣由有兩種:一是安裝App的設備上根本沒有攝像頭,例如某些平板或特殊Android設備;二是cameraId對應的攝像頭正被使用,可能某個App正在後臺使用它錄製視頻。

3. 配置相機參數

在打開相機設備後,你將得到一個Camera對象,並獨佔相機設備資源。 經過Camera.getParameters()接口能夠獲取當前相機設備的默認配置參數。下面列舉一些我能理解的參數:

閃光燈配置參數,能夠經過Parameters.getFlashMode()接口獲取當前相機的閃光燈配置參數:

  • Camera.Parameters.FLASH_MODE_AUTO 自動模式,當光線較暗時自動打開閃光燈;
  • Camera.Parameters.FLASH_MODE_OFF 關閉閃光燈;
  • Camera.Parameters.FLASH_MODE_ON 拍照時閃光燈;
  • Camera.Parameters.FLASH_MODE_RED_EYE 閃光燈參數,防紅眼模式,科普一下:防紅眼

對焦模式配置參數,能夠經過Parameters.getFocusMode()接口獲取:

  • Camera.Parameters.FOCUS_MODE_AUTO 自動對焦模式,攝影小白專用模式;
  • Camera.Parameters.FOCUS_MODE_FIXED 固定焦距模式,拍攝老司機模式;
  • Camera.Parameters.FOCUS_MODE_EDOF 景深模式,文藝女青年最喜歡的模式;
  • Camera.Parameters.FOCUS_MODE_INFINITY 遠景模式,拍風景大場面的模式;
  • Camera.Parameters.FOCUS_MODE_MACRO 微焦模式,拍攝小花小草小螞蟻專用模式;

場景模式配置參數,能夠經過Parameters.getSceneMode()接口獲取:

  • Camera.Parameters.SCENE_MODE_BARCODE 掃描條碼場景,NextQRCode項目會判斷並設置爲這個場景;
  • Camera.Parameters.SCENE_MODE_ACTION 動做場景,就是抓拍跑得飛快的運動員、汽車等場景用的;
  • Camera.Parameters.SCENE_MODE_AUTO 自動選擇場景;
  • Camera.Parameters.SCENE_MODE_HDR 高動態對比度場景,一般用於拍攝晚霞等明暗分明的照片;
  • Camera.Parameters.SCENE_MODE_NIGHT 夜間場景;

Camera API提供了很是多的參數接口供開發者設置,有必要的話,能夠翻閱相關API文檔。

在NextQRCode項目中,須要使用到自動對焦的特性。在一些機型上多是沒有的自動對焦(雖然比較少見),須要對這種狀況進行處理。

4. 設置相機預覽方向

相機預覽圖須要設置正確的預覽方向才能正常地顯示預覽畫面,不然預覽畫面會被擠壓得很慘。 在一般狀況下,若是咱們須要知道設備的屏幕方向,能夠經過Resources.Configuration.orientation來獲取。Android屏幕方向有「豎屏」和「橫屏」兩種,對應的值分別是ORIENTATION_PORTRAITORIENTATION_LANDSCAPE。但相機設備的方向卻有些特別,設置預覽方向的接口Camera.setDisplayOrientaion(int)的參數是以角度爲單位的,並且只能是0,90,180,270其中之一,默認爲0,是指手機的左側爲攝像頭頂部畫面。記得只能是[0、90、180、270]其中之一,輸入其它角度數值會報錯。

若是你想讓相機跟隨設備的方向,預覽界面頂部一直保持正上方,如下代碼供參考:

public static void followScreenOrientation(Context context, Camera camera){
    final int orientation = context.getResources().getConfiguration().orientation;
    if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
        camera.setDisplayOrientation(180);
    }else if(orientation == Configuration.ORIENTATION_PORTRAIT) {
        camera.setDisplayOrientation(90);
    }
}

5. 預覽View與拍照

咱們通常使用SurfaceView做爲相機預覽View,你也可使用Texture。在SurfaceView中獲取得SurfaceHolder,並經過setPreviewDisplay()接口設置預覽。在設置預覽View後,必定要記得如下兩點:

  • 調用startPreview()方法啓動預覽,不然預覽View不會顯示任何內容;
  • 拍照操做須要在startPreview()方法執行以後調用;
  • 每次拍照後,預覽View會中止預覽。因此連續拍照,須要從新調用startPreview()來恢復預覽;

Camera接受一個SurfaceHolder接口,這個接口能夠經過SurfaceHolder.Callback得到。咱們能夠經過繼承SurfaceView來實現相機預覽效果。在NextQRCode項目中,實現了LiveCameraView類,它內部已實現了相機預覽所須要的處理過程,很簡潔的類,如下是它的所有源碼:

public class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback {
    private final static String TAG = LiveCameraView.class.getSimpleName();
    private Camera mCamera;
    private SurfaceHolder mSurfaceHolder;

    public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(TAG, "Start preview display[SURFACE-CREATED]");
        startPreviewDisplay(holder);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mSurfaceHolder.getSurface() == null){
            return;
        }
        Cameras.followScreenOrientation(getContext(), mCamera);
        Log.d(TAG, "Restart preview display[SURFACE-CHANGED]");
        stopPreviewDisplay();
        startPreviewDisplay(mSurfaceHolder);
    }

    public void setCamera(Camera camera) {
        mCamera = camera;
        final Camera.Parameters params = mCamera.getParameters();
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
    }

    private void startPreviewDisplay(SurfaceHolder holder){
        checkCamera();
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.e(TAG, "Error while START preview for camera", e);
        }
    }

    private void stopPreviewDisplay(){
        checkCamera();
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            Log.e(TAG, "Error while STOP preview for camera", e);
        }
    }

    private void checkCamera(){
        if(mCamera == null) {
            throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]");
        stopPreviewDisplay();
    }
}

從上面代碼能夠看出LiveCameraView的核心代碼是SurfaceHolder.Callback的回調:在建立/銷燬時啓動/中止預覽動做。在LiveCameraView類中,咱們利用了View的生命週期回調來實現自動管理預覽生命週期控制:

  • 當SurfaceView被建立後自動開啓預覽;
  • 當SurfaceView被銷燬時關閉預覽;
  • 當SurfaceView尺寸被改變時重置預覽;

預覽View須要注意預覽輸出畫面的尺寸。相機輸出畫面只支持部分尺寸。關於尺寸部分,後面再更新。

在啓用預覽View後,就能夠經過Camera.takePicture()方法拍攝一張照片,返回的照片數據經過Callback接口獲取。takePicture()接口能夠獲取三個類型的照片:

  • 第一個,ShutterCallback接口,在拍攝瞬間瞬間被回調,一般用於播放「咔嚓」這樣的音效;
  • 第二個,PictureCallback接口,返回未經壓縮的RAW類型照片;
  • 第三個,PictureCallback接口,返回通過壓縮的JPEG類型照片;

咱們使用第三個參數,JPEG類型的照片的圖片精度便可知足識別二維碼的需求。在NextQRCode項目中,ZXing識別二維碼的數據格式爲Bitmap,經過BitmapFactory能夠很方便方便地將byte數組轉換成Bitmap。

public abstract class BitmapCallback implements Camera.PictureCallback {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
    }
    public abstract void onPictureTaken(Bitmap bitmap);
}

詳細關於Android中Bitmap的說明,請參見文章Android: Bitmap與Drawable這件小事

若是你須要將照片保存爲文件,能夠參考這個類的實現:FilePhotoCallback.java

6. 釋放相機設備

在打開一個相機設備後,意味着你的App就獨佔了這個設備,其它App將沒法使用它。所以在你不須要相機設備時,記得調用release()方法釋放設備,再使用時能夠從新打開,這並不須要多大的成本。能夠選擇在stopPreview()後即釋放相機設備。

附加工具性代碼實現

1 - 判斷手機設備是否有相機設備

public static boolean hasCameraDevice(Context ctx) {
    return ctx.getPackageManager()
            .hasSystemFeature(PackageManager.FEATURE_CAMERA);
}

2 - 判斷是否支持自動對焦

public static boolean isAutoFocusSupported(Camera.Parameters params) {
   List<String> modes = params.getSupportedFocusModes();
   return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
}

如何正確地使用Camera來開發視頻拍攝功能

抱歉,這個我真沒研究過。

提供一個連接地址供你參考:Camera開發視頻拍攝

關於Camera2

後續再更新Camera2的開發教程

相關文章
相關標籤/搜索