Android平臺Camera開發實踐指南

關於做者html

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。java

文章目錄android

  • 一 Camera實踐指南
    • 1.1 打開相機
    • 1.2 關閉相機
    • 1.3 開啓預覽
    • 1.4 關閉預覽
    • 1.5 拍照
    • 1.6 開始視頻錄製
    • 1.7 結束視頻錄製
  • 二 Camera2實踐指南
    • 2.1 打開相機
    • 2.2 關閉相機
    • 2.3 開啓預覽
    • 2.4 關閉預覽
    • 2.5 拍照
    • 2.6 開始視頻錄製
    • 2.7 結束視頻錄製

Android Camera 相關API也是Android生態碎片化最爲嚴重的一塊,首先Android自己就有兩套API,Android 5.0如下的Camera和Android 5.0以上的Camera2,並且 更爲嚴重的時,各家手機廠商都Camera2的支持程度也各不相同,這就致使咱們在相機開發中要花費很大精力來處理兼容性問題。git

相機開發的通常流程是什麼樣的?🤔程序員

  1. 檢測並訪問相機資源 檢查手機是否存在相機資源,若是存在則請求訪問相機資源。
  2. 建立預覽界面,建立繼承自SurfaceView並實現SurfaceHolder接口的拍攝預覽類。有了拍攝預覽類,便可建立一個佈局文件,將預覽畫面與設計好的用戶界面控件融合在一塊兒,實時顯示相機的預覽圖像。
  3. 設置拍照監聽器,給用戶界面控件綁定監聽器,使其能響應用戶操做, 開始拍照過程。
  4. 拍照並保存文件,將拍攝得到的圖像轉換成位圖文件,最終輸出保存成各類經常使用格式的圖片。
  5. 釋放相機資源,相機是一個共享資源,當相機使用完畢後,必須正確地將其釋放,以避免其它程序訪問使用時發生衝突。

相機開發通常須要注意哪些問題?🤔github

  1. 版本兼容性問題,Android 5.0如下的Camera和Android 5.0以上使用Camera2,Android 4.0如下的SurfaceView和Android 4.0以上的TextureView,Android 6.0以上要作相機等運行時權限兼容。
  2. 設備兼容性問題,Camera/Camera2裏的各類特性在有些手機廠商的設備實現方式和支持程度是不同的,這個須要作兼容性測試,一點點踩坑。
  3. 各類場景下的生命週期變化問題,最多見的是後臺場景和鎖屏場景,這兩種場景下的相機資源的申請與釋放,Surface的建立與銷燬會帶來一些問題,這個咱們 後面會仔細分析。

關於Camera/Camear2session

既然要解決這種兼容性問題,就要兩套並用,那是否是根據版原本選擇:Android 5.0 如下用Camera,Android 5.0以上用Camera2呢?🤔架構

事實上,這樣是不可取的。前面說過不一樣手機廠商對Camera2的支持程度各不相同,即使是Android 5.0 以上的手機,也存在對Camera2支持很是差的狀況,這個時候就要降級使用Camera,如何判斷對Camera的支持 程度咱們下面會說。框架

關於SurfaceView/TextureViewide

  • SurfaceView是一個有本身Surface的View。界面渲染能夠放在單獨線程而不是主線程中。它更像是一個Window,自身不能作變形和動畫。
  • TextureView一樣也有本身的Surface。可是它只能在擁有硬件加速層層的Window中繪製,它更像是一個普通View,能夠作變形和動畫。

更多關於SurfaceView與TextureView區別的內容能夠參考這篇文章Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView.

那麼如何針對版本進行方案的選擇呢?🤔

官方的開源庫cameraview給出了方案:

既然要兩套並用,就要定義統一的接口,針對不一樣場景提供不一樣的實現,使用的時候也是根據不一樣的場景來建立不一樣的實例。

咱們不難發現,這個接口通常須要定義如下功能:

  • 打開相機
  • 關閉相機
  • 開啓預覽
  • 關閉預覽
  • 拍照
  • 開始視頻錄製
  • 結束視頻錄製

定義好了接口,咱們就有了思路,針對相機的具體特性實現相應的方案,那麼另外一個問題就出來了,相機在平常開發中通常做爲一個SDK的形式存在供各個業務方調用,那麼如何設計 出一個功能與UI相分離,高度可定製的相機SDK呢?🤔

答案就是利用Fragment,將各類點擊事件(點擊拍照、點擊切換攝像頭、點擊切換閃光模式等)對應的功能封裝在Fragment裏,業務方在用的時候能夠在Fragment之上蒙一層 UI(固然咱們也須要提供默認的實現),這樣就可讓功能和UI相分離,集成起來也很是的簡便。

相機SDK框架圖以下所示:

  • CameraActivity:相機界面,主要用來實現UI的定製,實際功能(點擊事件)交由CameraFragment完成。
  • CameraFragment:向CameraActivity提供功能接口,完成CameraActivity裏的點擊事件,例如:拍照、錄像等。
  • CameraLifecycle:處理相機隨着Activity生命週期變化的狀況,內部持有CameraManager,處理相機初始化和釋放,預覽的建立與銷燬等問題。
  • CameraManager:相機的實際管理者,調用相機API來操做相機,進行拍照和錄像等操做。
  • Camera/Camera2:相機API。

phoenix項目已經實現了這套方案,效果圖以下所示:

理解了總體的架構,咱們接着就來分析針對這套架構,Camera/Camera2分別該如何實現。

一 Camera實踐指南

Camera API中主要涉及如下幾個關鍵類:

  • Camera:操做和管理相機資源,支持相機資源切換,設置預覽和拍攝尺寸,設置光圈、曝光等相關參數。
  • SurfaceView:用於繪製相機預覽圖像,提供實時預覽的圖像。
  • SurfaceHolder:用於控制Surface的一個抽象接口,它能夠控制Surface的尺寸、格式與像素等,並能夠監視Surface的變化。
  • SurfaceHolder.Callback:用於監聽Surface狀態變化的接口。

SurfaceView和普通的View相比有什麼區別呢?🤔

普通View都是共享一個Surface的,全部的繪製也都在UI線程中進行,由於UI線程還要處理其餘邏輯,所以對View的更新速度和繪製幀率沒法保證。這顯然不適合相機實時 預覽這種狀況,於是SurfaceView持有一個單獨的Surface,它負責管理這個Surface的格式、尺寸以及顯示位置,它的Surface繪製也在單獨的線程中進行,於是擁有更高 的繪製效率和幀率。

SurfaceHolder.Callback接口裏定義了三個函數:

  • surfaceCreated(SurfaceHolder holder); 當Surface第一次建立的時候調用,能夠在這個方法裏調用camera.open()、camera.setPreviewDisplay()來實現打開相機以及鏈接Camera與Surface 等操做。
  • surfaceChanged(SurfaceHolder holder, int format, int width, int height); 當Surface的size、format等發生變化的時候調用,能夠在這個方法裏調用camera.startPreview()開啓預覽。
  • surfaceDestroyed(SurfaceHolder holder); 當Surface被銷燬的時候調用,能夠在這個方法裏調用camera.stopPreview(),camera.release()等方法來實現結束預覽以及釋放

1.1 打開相機

打開相機以前咱們須要先獲取系統相機的相關信息。

//有多少個攝像頭
numberOfCameras = Camera.getNumberOfCameras();

for (int i = 0; i < numberOfCameras; ++i) {
    final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();

    Camera.getCameraInfo(i, cameraInfo);
    //後置攝像頭
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
        faceBackCameraId = i;
        faceBackCameraOrientation = cameraInfo.orientation;
    } 
    //前置攝像頭
    else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        faceFrontCameraId = i;
        faceFrontCameraOrientation = cameraInfo.orientation;
    }
}
複製代碼

知道了相機相關信息,就能夠經過相機ID打開相機了。

camera = Camera.open(cameraId);
複製代碼

另外,打開相機之後你會得到一個Camera對象,從這個對象裏能夠獲取和設置相機的各類參數信息。

//獲取相機參數
camera.getParameters();
//設置相機參數
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 夜間場景;

1.2 關閉相機

關閉相機很簡單,只須要把相機釋放掉就能夠了。

camera.release();
複製代碼

1.3 開啓預覽

Camera的預覽時經過SurfaceView的SurfaceHolder進行的,先經過,具體說來:

private void startPreview(SurfaceHolder surfaceHolder) {
    try {
        final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(currentCameraId, cameraInfo);
        int cameraRotationOffset = cameraInfo.orientation;

        //獲取相機參數
        final Camera.Parameters parameters = camera.getParameters();
        //設置對焦模式
        setAutoFocus(camera, parameters);
        //設置閃光模式
        setFlashMode(mCameraConfigProvider.getFlashMode());

        if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)
            turnPhotoCameraFeaturesOn(camera, parameters);
        else if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO)
            turnVideoCameraFeaturesOn(camera, parameters);

        final int rotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break; // Natural orientation
            case Surface.ROTATION_90:
                degrees = 90;
                break; // Landscape left
            case Surface.ROTATION_180:
                degrees = 180;
                break;// Upside down
            case Surface.ROTATION_270:
                degrees = 270;
                break;// Landscape right
        }

        //根據前置與後置攝像頭的不一樣,設置預覽方向,不然會發生預覽圖像倒過來的狀況。
        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            displayRotation = (cameraRotationOffset + degrees) % 360;
            displayRotation = (360 - displayRotation) % 360; // compensate
        } else {
            displayRotation = (cameraRotationOffset - degrees + 360) % 360;
        }
        this.camera.setDisplayOrientation(displayRotation);

        if (Build.VERSION.SDK_INT > 13
                && (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
// parameters.setRecordingHint(true);
        }

        if (Build.VERSION.SDK_INT > 14
                && parameters.isVideoStabilizationSupported()
                && (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
            parameters.setVideoStabilization(true);
        }

        //設置預覽大小
        parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
        parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight());

        //設置相機參數
        camera.setParameters(parameters);
        //設置surfaceHolder
        camera.setPreviewDisplay(surfaceHolder);
        //開啓預覽
        camera.startPreview();

    } catch (IOException error) {
        Log.d(TAG, "Error setting camera preview: " + error.getMessage());
    } catch (Exception ignore) {
        Log.d(TAG, "Error starting camera preview: " + ignore.getMessage());
    }
}
複製代碼

1.4 關閉預覽

關閉預覽很簡單,直接調用camera.stopPreview()便可。

camera.stopPreview();
複製代碼

1.5 拍照

拍照時經過調用Camera的takePicture()方法來完成的,

takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback postview, PictureCallback jpeg)
複製代碼

該方法有三個參數:

  • ShutterCallback shutter:在拍照的瞬間被回調,這裏一般能夠播放"咔嚓"這樣的拍照音效。
  • PictureCallback raw:返回未經壓縮的圖像數據。
  • PictureCallback postview:返回postview類型的圖像數據
  • PictureCallback jpeg:返回通過JPEG壓縮的圖像數據。

咱們通常用的就是最後一個,實現最後一個PictureCallback便可。

camera.takePicture(null, null, new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            //存儲返回的圖像數據
            final File pictureFile = outputPath;
            if (pictureFile == null) {
                Log.d(TAG, "Error creating media file, check storage permissions.");
                return;
            }
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(pictureFile);
                fileOutputStream.write(bytes);
                fileOutputStream.close();
            } catch (FileNotFoundException error) {
                Log.e(TAG, "File not found: " + error.getMessage());
            } catch (IOException error) {
                Log.e(TAG, "Error accessing file: " + error.getMessage());
            } catch (Throwable error) {
                Log.e(TAG, "Error saving file: " + error.getMessage());
            }
        }
 });
複製代碼

拍照完成後若是還要繼續拍照則調用camera.startPreview()繼續開啓預覽,不然關閉預覽,釋放相機資源。

1.6 開始視頻錄製

視頻的錄製時經過MediaRecorder來完成的。

if (prepareVideoRecorder()) {
            mediaRecorder.start();
            isVideoRecording = true;
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    videoListener.onVideoRecordStarted(videoSize);
                }
            });
}
複製代碼

MediaRecorder主要用來錄製音頻和視頻,在使用以前要進行初始化和相關參數的設置,以下所示:

protected boolean preparemediaRecorder() {
    mediaRecorder = new MediaRecorder();
    try {
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        
        //輸出格式
        mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
        //視頻幀率
        mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
        //視頻大小
        mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
        //視頻比特率
        mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
        //視頻編碼器
        mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
        
        //音頻編碼率
        mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
        //音頻聲道
        mediaRecorder.setAudioChannels(camcorderProfile.audioChannels);
        //音頻採樣率
        mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
        //音頻編碼器
        mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
        
        File outputFile = outputPath;
        String outputFilePath = outputFile.toString();
        //輸出路徑
        mediaRecorder.setOutputFile(outputFilePath);
        
        //設置視頻輸出的最大尺寸
        if (mCameraConfigProvider.getVideoFileSize() > 0) {
            mediaRecorder.setMaxFileSize(mCameraConfigProvider.getVideoFileSize());
            mediaRecorder.setOnInfoListener(this);
        }
        
        //設置視頻輸出的最大時長
        if (mCameraConfigProvider.getVideoDuration() > 0) {
            mediaRecorder.setMaxDuration(mCameraConfigProvider.getVideoDuration());
            mediaRecorder.setOnInfoListener(this);
        }
        mediaRecorder.setOrientationHint(getVideoOrientation(mCameraConfigProvider.getSensorPosition()));
        
        //準備
        mediaRecorder.prepare();

        return true;
    } catch (IllegalStateException error) {
        Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage());
    } catch (IOException error) {
        Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage());
    } catch (Throwable error) {
        Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage());
    }
    releasemediaRecorder();
    return false;
}
複製代碼

值得一提的是,平常的業務中常常對拍攝視頻的時長或者大小有要求,這個能夠經過mediaRecorder.setOnInfoListener()來處理,OnInfoListener會監聽正在錄製的視頻,而後咱們 能夠在它的回調方法裏處理。

@Override
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
    if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED == what) {
        //到達最大時長
    } else if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED == what) {
        //到達最大尺寸
    }
}
複製代碼

更多關於MediaRecorder的介紹能夠參考MediaRecorder官方文檔

1.7 結束視頻錄製

結束視頻錄製也很簡單,只須要調用mediaRecorder.stop()方法便可。

mediaRecorder.stop();
複製代碼

此外,若是再也不使用相機,也要注意釋放相機資源。

以上即是Camera的所有內容,仍是比較簡單的,下面咱們接着來說Camera2的相關內容,注意體會二者的區別。

二 Camera2實踐指南

Camera2 API中主要涉及如下幾個關鍵類:

  • CameraManager:攝像頭管理器,用於打開和關閉系統攝像頭
  • CameraCharacteristics:描述攝像頭的各類特性,咱們能夠經過CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法來獲取。
  • CameraDevice:描述系統攝像頭,相似於早期的Camera。
  • CameraCaptureSession:Session類,當須要拍照、預覽等功能時,須要先建立該類的實例,而後經過該實例裏的方法進行控制(例如:拍照 capture())。
  • CaptureRequest:描述了一次操做請求,拍照、預覽等操做都須要先傳入CaptureRequest參數,具體的參數控制也是經過CameraRequest的成員變量來設置。
  • CaptureResult:描述拍照完成後的結果。

Camera2拍照流程以下所示:

開發者經過建立CaptureRequest向攝像頭髮起Capture請求,這些請求會排成一個隊列供攝像頭處理,攝像頭將結果包裝在CaptureMetadata中返回給開發者。整個流程創建在一個CameraCaptureSession的會話中。

2.1 打開相機

打開相機以前,咱們首先要獲取CameraManager,而後獲取相機列表,進而獲取各個攝像頭(主要是前置攝像頭和後置攝像頭)的參數。

mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
    final String[] ids = mCameraManager.getCameraIdList();
    numberOfCameras = ids.length;
    for (String id : ids) {
        final CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);

        final int orientation = characteristics.get(CameraCharacteristics.LENS_FACING);
        if (orientation == CameraCharacteristics.LENS_FACING_FRONT) {
            faceFrontCameraId = id;
            faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            frontCameraCharacteristics = characteristics;
        } else {
            faceBackCameraId = id;
            faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            backCameraCharacteristics = characteristics;
        }
    }
} catch (Exception e) {
    Log.e(TAG, "Error during camera initialize");
}
複製代碼

Camera2與Camera同樣也有cameraId的概念,咱們經過mCameraManager.getCameraIdList()來獲取cameraId列表,而後經過mCameraManager.getCameraCharacteristics(id) 獲取每一個id對應攝像頭的參數。

關於CameraCharacteristics裏面的參數,主要用到的有如下幾個:

  • LENS_FACING:前置攝像頭(LENS_FACING_FRONT)仍是後置攝像頭(LENS_FACING_BACK)。
  • SENSOR_ORIENTATION:攝像頭拍照方向。
  • FLASH_INFO_AVAILABLE:是否支持閃光燈。
  • CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:獲取當前設備支持的相機特性。

注:事實上,在各個廠商的的Android設備上,Camera2的各類特性並不都是可用的,須要經過characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 來根據返回值來獲取支持的級別,具體說來:

  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,容許手動控制全高清的攝像、支持連拍模式以及其餘新特性。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,這個須要單獨查詢。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:全部設備都會支持,也就是和過期的Camera API支持的特性是一致的。

利用這個INFO_SUPPORTED_HARDWARE_LEVEL參數,咱們能夠來判斷是使用Camera仍是使用Camera2,具體方法以下:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean hasCamera2(Context mContext) {
    if (mContext == null) return false;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
    try {
        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        String[] idList = manager.getCameraIdList();
        boolean notFull = true;
        if (idList.length == 0) {
            notFull = false;
        } else {
            for (final String str : idList) {
                if (str == null || str.trim().isEmpty()) {
                    notFull = false;
                    break;
                }
                final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str);

                final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    notFull = false;
                    break;
                }
            }
        }
        return notFull;
    } catch (Throwable ignore) {
        return false;
    }
}
複製代碼

更多ameraCharacteristics參數,能夠參見CameraCharacteristics官方文檔

打開相機主要調用的是mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler)方法,如你所見,它有三個參數:

  • String cameraId:攝像頭的惟一ID。
  • CameraDevice.StateCallback callback:攝像頭打開的相關回調。
  • Handler handler:StateCallback須要調用的Handler,咱們通常能夠用當前線程的Handler。
mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler);
複製代碼

上面咱們提到了CameraDevice.StateCallback,它是攝像頭打開的一個回調,定義了打開,關閉以及出錯等各類回調方法,咱們能夠在 這些回調方法裏作對應的操做。

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        //獲取CameraDevice
        mcameraDevice = cameraDevice;
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        //關閉CameraDevice
        cameraDevice.close();

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int error) {
        //關閉CameraDevice
        cameraDevice.close();
    }
};
複製代碼

2.2 關閉相機

經過上面的描述,關閉就很簡單了。

//關閉CameraDevice
cameraDevice.close();
複製代碼

2.3 開啓預覽

Camera2都是經過建立請求會話的方式進行調用的,具體說來:

  1. 調用mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法建立CaptureRequest,調用
  2. mCameraDevice.createCaptureSession()方法建立CaptureSession。
CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) 複製代碼

createCaptureRequest()方法裏參數templateType表明了請求類型,請求類型一共分爲六種,分別爲:

  • TEMPLATE_PREVIEW:建立預覽的請求
  • TEMPLATE_STILL_CAPTURE:建立一個適合於靜態圖像捕獲的請求,圖像質量優先於幀速率。
  • TEMPLATE_RECORD:建立視頻錄製的請求
  • TEMPLATE_VIDEO_SNAPSHOT:建立視視頻錄製時截屏的請求
  • TEMPLATE_ZERO_SHUTTER_LAG:建立一個適用於零快門延遲的請求。在不影響預覽幀率的狀況下最大化圖像質量。
  • TEMPLATE_MANUAL:建立一個基本捕獲請求,這種請求中全部的自動控制都是禁用的(自動曝光,自動白平衡、自動焦點)。
createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
複製代碼

createCaptureSession()方法一共包含三個參數:

  • List outputs:咱們須要輸出到的Surface列表。
  • CameraCaptureSession.StateCallback callback:會話狀態相關回調。
  • Handler handler:callback能夠有多個(來自不一樣線程),這個handler用來區別那個callback應該被回調,通常寫當前線程的Handler便可。

關於CameraCaptureSession.StateCallback裏的回調方法:

  • onConfigured(@NonNull CameraCaptureSession session); 攝像頭完成配置,能夠處理Capture請求了。
  • onConfigureFailed(@NonNull CameraCaptureSession session); 攝像頭配置失敗
  • onReady(@NonNull CameraCaptureSession session); 攝像頭處於就緒狀態,當前沒有請求須要處理。
  • onActive(@NonNull CameraCaptureSession session); 攝像頭正在處理請求。
  • onClosed(@NonNull CameraCaptureSession session); 會話被關閉
  • onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface準備就緒

理解了這些東西,建立預覽請求就十分簡單了。

previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(workingSurface);

//注意這裏除了預覽的Surface,咱們還添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據的
mCameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()),
        new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "Fail while starting preview: ");
            }
        }, null);
複製代碼

能夠發現,在onConfigured()裏調用了cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler),這樣咱們就能夠 持續的進行預覽了。

注:上面咱們說了添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據,具體操做就是爲ImageReader設置一個OnImageAvailableListener,而後在它的onImageAvailable() 方法裏獲取。

mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            //當圖片可獲得的時候獲取圖片並保存
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

 };
複製代碼

2.4 關閉預覽

關閉預覽就是關閉當前預覽的會話,結合上面開啓預覽的內容,具體實現以下:

if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}
複製代碼

2.5 拍照

拍照具體來講分爲三步:

  1. 對焦
try {
    //相機對焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
    //修改狀態
    previewState = STATE_WAITING_LOCK;
    //發送對焦請求
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
} catch (Exception ignore) {
}
複製代碼

咱們定義了一個CameraCaptureSession.CaptureCallback來處理對焦請求返回的結果。

private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

    @Override
    public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            //等待對焦
            final Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
            if (afState == null) {
                //對焦失敗,直接拍照
                captureStillPicture();
            } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState
                    || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState
                    || CaptureResult.CONTROL_AF_STATE_INACTIVE == afState
                    || CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) {
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                    previewState = STATE_PICTURE_TAKEN;
                    //對焦完成,進行拍照
                    captureStillPicture();
                } else {
                    runPreCaptureSequence();
                }
            }
    }
};
複製代碼
  1. 拍照

咱們定義了一個captureStillPicture()來進行拍照。

private void captureStillPicture() {
    try {
        if (null == mCameraDevice) {
            return;
        }
        
        //構建用來拍照的CaptureRequest
        final CaptureRequest.Builder captureBuilder =
                mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(imageReader.getSurface());

        //使用相同的AR和AF模式做爲預覽
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        //設置方向
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(mCameraConfigProvider.getSensorPosition()));

        //建立會話
        CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                Log.d(TAG, "onCaptureCompleted: ");
            }
        };
        //中止連續取景
        captureSession.stopRepeating();
        //捕獲照片
        captureSession.capture(captureBuilder.build(), CaptureCallback, null);

    } catch (CameraAccessException e) {
        Log.e(TAG, "Error during capturing picture");
    }
}
複製代碼
  1. 取消對焦

拍完照片後,咱們還要解鎖相機焦點,讓相機恢復到預覽狀態。

try {
    //重置自動對焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
    //相機恢復正常的預覽狀態
    previewState = STATE_PREVIEW;
    //打開連續取景模式
    captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
} catch (Exception e) {
    Log.e(TAG, "Error during focus unlocking");
}
複製代碼

2.6 開始視頻錄製

//先關閉預覽,由於須要添加一個預覽輸出的Surface,也就是mediaRecorder.getSurface()
closePreviewSession();

//初始化MediaRecorder,設置相關參數
if (preparemediaRecorder()) {

    final SurfaceTexture texture = Camera2Manager.this.texture;
    texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());

    try {
        //構建視頻錄製aptureRequest
        previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        final List<Surface> surfaces = new ArrayList<>();

        //設置預覽Surface
        final Surface previewSurface = workingSurface;
        surfaces.add(previewSurface);
        previewRequestBuilder.addTarget(previewSurface);

        //設置預覽輸出Surface
        workingSurface = mediaRecorder.getSurface();
        surfaces.add(workingSurface);
        previewRequestBuilder.addTarget(workingSurface);

        mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                captureSession = cameraCaptureSession;

                previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                try {
                    //持續發送Capture請求,實現實時預覽。
                    captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
                } catch (Exception e) {
                }

                try {
                    //開始錄像
                    mediaRecorder.start();
                } catch (Exception ignore) {
                    Log.e(TAG, "mediaRecorder.start(): ", ignore);
                }

                isVideoRecording = true;

                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        cameraVideoListener.onVideoRecordStarted(videoSize);
                    }
                });
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "onConfigureFailed");
            }
        }, backgroundHandler);
    } catch (Exception e) {
        Log.e(TAG, "startVideoRecord: ", e);
    }
}
複製代碼

關於MediaRecorder上面講Camera的時候咱們就已經說過,這裏再也不贅述。

以上即是視頻錄製的所有內容,就是簡單的API使用,仍是比較簡單的。

2.7 結束視頻錄製

結束視頻錄製主要也是關閉會話以及釋放一些資源,具體說來:

  1. 關閉預覽會話
  2. 中止mediaRecorder
  3. 釋放mediaRecorder
//關閉預覽會話
if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}

//中止mediaRecorder
if (mediaRecorder != null) {
    try {
        mediaRecorder.stop();
    } catch (Exception ignore) {
    }
}

//釋放mediaRecorder
try {
    if (mediaRecorder != null) {
        mediaRecorder.reset();
        mediaRecorder.release();
    }
} catch (Exception ignore) {

} finally {
    mediaRecorder = null;
}
複製代碼

以上即是Camera/Camera2實踐的相關內容,更多關於圖像、視頻處理的內容能夠參見phoenix項目。

相關文章
相關標籤/搜索