Android Camera2 開發實踐指南

咱們知道 Android 中相機開發是有兩套 API 可使用的,一個是 Camera,這個適用於 Android 5.0 如下,另一個是 Camera2,這個適用於 Android 5.0 以上。可是這僅僅是系統的建議,其實開發中因爲國內廠商對 Camera2 的支持程度各不相同,即使是 5.0 以上的手機,也可能對 Camera2 支持很是差的狀況,咱們可能還得降級使用 Camera 來開發。android

 

使用 Camera2 開發會涉及到一些系統方法的調用,咱們須要大概瞭解一下他們的做用。ios

1.相機的管理主要由如下兩個類提供:session

CameraManager:相機管理類,能夠獲取相機個數,以及打開或關閉相機等操做。ide

CameraCharacteristics:獲取相機的配置參數,好比獲取相機支持的拍攝分辨率大小、ISO範圍、曝光時間等,系統提供了大概78個配置選項。性能

2.相機的預覽和拍攝主要由下面的類管理:動畫

CameraDevice:這個至關因而打開相機後當前攝像頭的表示,相機開發後會傳入一個CameraDevice,咱們可使用此類來建立與相機的鏈接。ui

CameraCaputreSession:由CameraDevice配置好後產生的session,用於處理相機預覽或者是拍照等處理,就至關因而已經創建鏈接了,而後如今經過這個CameraCaptureSession處理與相機進行對話。this

CaptureRequest:控制本次獲取圖像的配置,好比配置圖片的ISO,對焦方式和曝光時間等。google

CaptureResult:描述拍照完成後的結果。.net

ImageReader:能夠用這個類來作簡單的捕獲圖像處理。

3.展現預覽圖像可使用 SurfaceView 或 TextureView:

SurfaceView:界面渲染能夠放在單獨線程,自身不能支持使用動畫。

TextureView:只能在擁有硬件加速層層的Window繪製,性能不如SurfaceView,而且可能丟幀,可是能夠作一些動畫效果。

二者詳細差異分析能夠參考:https://groups.google.com/a/chromium.org/forum/#!topic/graphics-dev/Z0yE-PWQXc4

 

Camera2 開發的整個流程就上面介紹的那樣:

先請求相機權限
使用 CameraManager找到你想要的攝像頭,前置或是後置。
經過 CameraCharacteristics 獲取相機的配置信息,方便以後調整相機的各類參數。
經過 CameraManager 打開相機,獲得當前的 CameraDevice。
經過 CameraDevice 建立本次會話,獲得本次會話的 CameraCaptureSession。
使用 CaptureRequest 設置獲取圖片的參數信息,設置到 CameraCaptureSession 中。
在 ImageReader 或 CaptureResult 處理獲得的圖片。
關閉相機(不關閉有可能會致使相機資源佔用,致使別的相機沒法正常打開)
 

開發以前先來了解一些相機的配置介紹:

AE:Automatic Exposure(自動曝光)。

AF:Auto Focus(自動對焦)。

ISO:International Orization for Standardization,這個是國際標準化組織,因爲照相機的感光度最終由這個組織發佈,因此稱爲感光度ISO值。

EV:Exposure value(曝光值)

F:F-number(光圈)

咱們經過本身設置這些參數能夠拍出很是有意思的照片,下面說一下 Android 爲咱們提供的與攝像頭交互的一些類,以及如何配置本身的相機參數來開發相機。

下面將使用 TextureView 結合 Camera2 API 製做一個簡單的相機預覽

1. 在TextureView可用狀態時打開相機。

    //監聽view的事件
    mTextureView.surfaceTextureListener = this
    
    override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
        //可用的時候會回調當前方法,width是此view的寬,height是高。
        openCamera(width,height)
    }
2.首先在 openCamera 中檢查是否有相機權限

    private fun openCamera(width : Int,height : Int) {
        //判斷是否有相機權限
        if (ContextCompat.checkSelfPermission(activity!!,         Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission()
            return
        }
        //設置要使用的攝像頭以及獲取攝像頭的配置
        setUpCameraOutputs(width, height)
        //打開攝像頭
        waitOpen()
    }
 
    //申請權限
    private fun requestCameraPermission() {
        requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
    }
3.配置相關屬性

    private fun setUpCameraOutputs(width: Int, height: Int) {
        val activity = act
        val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        manager.cameraIdList.forEach { cameraId ->
            val characteristics = manager.getCameraCharacteristics(cameraId)
 
            val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
            //這裏咱們不使用前置攝像頭
            if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                return@forEach
            }
            //獲得相機支持的流配置(包括支持的圖片分辨率等),不支持就返回
            val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                    ?: return@forEach
            //獲取支持的iso範圍
            val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE)
            //獲得最高和最低值
            if (isoRange != null) {
                val isoMin = isoRange.lower
                val isoMax = isoRange.upper
            }
            //獲取支持的圖像曝光時間範圍,單位納秒
            val timeRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE)
            //獲得最大最小值
            if (timeRange != null) {
                val timeMin = timeRange.lower
                val timeMax = timeRange.upper
            }
 
            //爲了方便,這裏咱們獲取支持攝像頭支持的最大尺寸來存儲
            //咱們直接使用了 JPEG 的圖片格式,android 支持的圖片格式能夠查看ImageFormat這個類
            val largest = Collections.max(
                    Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
                    CompareSizesByArea())
            //建立一個用於獲取攝像頭圖片的 ImageReader,最多接受 2 個
            mImageReader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, 2)
            //監聽接受到圖片的事件
            mImageReader?.setOnImageAvailableListener(mOnImageAvailableListener, mHandler)
            //檢查是否支持 flash
            mFlashSupported = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
            //獲得當前的攝像頭id
            mCameraId = cameraId
        }
    }
4.根據 id 打開相機

    private fun waitOpen() {
        val activity = act
        val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        if (mCameraId == null){
            //沒有找到符合的攝像頭
            return
        }
        manager.openCamera(mCameraId,mStateCallback,mHandler)
    }
 
    //在這裏獲得打開相機的各類回調
    private val mStateCallback : CameraDevice.StateCallback = object : CameraDevice.StateCallback(){
        override fun onOpened(camera: CameraDevice) {
            //獲得當前攝像頭
            mCameraDevice = camera
            //建立預覽請求
            createCameraPreviewSession()
        }
 
        override fun onDisconnected(camera: CameraDevice) {
        }
 
        override fun onError(camera: CameraDevice, error: Int) {
        }
    }
5.建立預覽請求

    private fun createCameraPreviewSession(){
        //獲取view的surface,這裏的寬高應該是獲取適合攝像頭當前的寬高,這裏爲了方便就直接使用屏幕寬高了
        val texture = mTextureView.surfaceTexture
        texture.setDefaultBufferSize(act.getWidth(),act.getHeight())
        val surface = Surface(texture)
        //構建預覽請求
        mPreviewRequestBuilder = mCameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
        mPreviewRequestBuilder?.addTarget(surface)
        //建立預覽會話
        mCameraDevice?.createCaptureSession(listOf(surface, mImageReader?.surface),mSessionCallBack,null)
    }
   //會在mSessionCallBack獲得咱們本次的session
    private val mSessionCallBack : CameraCaptureSession.StateCallback = object : CameraCaptureSession.StateCallback(){
        override fun onConfigureFailed(session: CameraCaptureSession) {}
 
        override fun onConfigured(session: CameraCaptureSession) {
            if (mCameraDevice == null){
                return
            }
            //獲得本次的會話類
            mCaptureSession= session
            //設置爲自動對焦的會話預覽
           mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
            //將imageReader添加進來便可在此類中獲得預覽的圖片
            mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
            mPreviewRequest = mPreviewRequestBuilder?.build()
            //設置爲連續請求
            mCaptureSession?.setRepeatingRequest(mPreviewRequest,mCaptureCallback,mHandler)
        }
    }
這裏還能夠設置別的模式,好比設置能夠本身調節ISO大小,值的大小能夠根據從相機配置讀取的參數範圍設置

    /**
     * 設置ios感光度以及曝光時間
     * ae 自動曝光 Automatic Exposure
     * af 自動對焦 Auto Focus
     * @param iso 靈敏度
     * @param exposure 曝光時間
     * @param frame 幀持續時間
     */
    private fun setIsoMode(iso: Int, exposure: Long, frame: Long) {
            //禁用全部自動設置
            //            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
            //只是禁用曝光,白平衡繼續開啓,本身設置iso等值,必須禁用曝光
            mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposure)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_SENSITIVITY, iso)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_FRAME_DURATION, frame)
            mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
            //須要預覽的話改爲這個而且添加到下面的createCaptureSession裏
            mPreviewRequest = mPreviewRequestBuilder?.build()
            mCaptureSession?.setRepeatingRequest(mPreviewRequest,
                    mCaptureCallback, mHandler)
    }
6.在 ImageReader 中處理圖片結果

    //這個是剛剛爲mImageReader添加的回調接口
    private val mOnImageAvailableListener = (ImageReader.OnImageAvailableListener { reader ->
        //獲取下一張圖片
        val image = reader.acquireNextImage()
        //這裏的planes大小和所選的圖片格式有關,好比YUV_444就有三個通道
        val buffer = image.planes[0].buffer
        val bytes = ByteArray(buffer.remaining())
        val bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size)
        //使用完記得回收
        image.close()
    })
7.操做完以後就記得釋放相機資源

    private fun closeCamera(){         if (mCaptureSession != null) {             mCaptureSession?.close()             mCaptureSession = null         }         if (mCameraDevice != null) {             mCameraDevice?.close()             mCameraDevice = null         }         if (mImageReader != null) {             mImageReader?.close()             mImageReader = null         }     }   -

相關文章
相關標籤/搜索