Android Camera 系列(二)控制Camera

yifanwu發表於2021-09-09

Camera系列文章首發於 ,歡迎關注。

概述

Camera 可能是接下來個人想深入學習的課題,準備新起一個系列,從個人的角度總結闡述自己對於 Android Camera 的研究過程,希望也能夠對其他想學習 Camera 的同學一些幫助。

本小節內容為 Android Camera 官方文件 的精要翻譯,原文請參考:

一、開啟相機物件

獲取Camera物件的例項是自定義相機的第一步。 作為Android的 自定義相機應用,更推薦是啟動一個單獨的執行緒並在onCreate()生命週期的回撥開啟Camera。 開啟攝像機的操作延遲到onResume()方法中處理是一個不錯的注意,因為Camera的初始化可能需要一段時間,而在UI執行緒這樣做則有可能讓應用變得卡頓。

如果攝像頭已被其他應用程式使用,則呼叫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() {
    mPreview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

二、建立相機預覽

預覽類

要開始顯示相機的預覽,您需要預覽類。 預覽需要實現android.view.SurfaceHolder.Callback介面,該介面用於將影像資料從相機硬體傳遞到應用程式。

class Preview(
        context: Context,
        val mSurfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = mSurfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

必須先將預覽類傳遞給Camera物件,然後才能啟動實時影像預覽,如下一節所示。

配置並開始預覽

Camera的配置和預覽等相關操作都需要嚴格地控制順序Camera物件首當其衝, 在下面的程式碼片段中,Camera的初始化過程被封裝,以便每當使用者做某事來更改Camera都會呼叫setCamera()方法,即呼叫Camera.startPreview()。 還必須在預覽類的surfaceChanged()回撥方法中重新啟動預覽。

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(mHolder)
        } 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()
    }
}

三、修改Camera的配置

相機設定會改變相機拍攝照片的方式,從縮放級別曝光補償。 下述程式碼示例僅展示瞭如何更改預覽大小:

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(mPreviewSize.width, mPreviewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}

四、設定預覽方向

大多數相機應用程式將顯示器鎖定為橫向模式,因為這是相機感測器的物理方向。 此設定不會阻止您拍攝縱向模式照片,因為裝置的方向記錄在EXIF Header中。 方法允許您更改預覽的顯示方式,而不會影響圖片的記錄方式。

// kotlin版本
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
fun setCameraDisplayOrientation(activity: Activity,
                                    cameraId: Int = 0,
                                    camera: Camera) {
       val info = Camera.CameraInfo()
       Camera.getCameraInfo(cameraId, info)
       val rotation = activity.windowManager.defaultDisplay.rotation
       var degrees = 0
       when (rotation) {
           Surface.ROTATION_0 -> degrees = 0
           Surface.ROTATION_90 -> degrees = 90
           Surface.ROTATION_180 -> degrees = 180
           Surface.ROTATION_270 -> degrees = 270
       }
       var result: Int
       if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
           result = (info.orientation + degrees) % 360
           result = (360 - result) % 360  // compensate the mirror
       } else {  // back-facing
           result = (info.orientation - degrees + 360) % 360
       }
       camera.setDisplayOrientation(result)
}

五、拍照

預覽開始展示後,可以使用Camera.takePicture()方法拍攝照片。 您可以建立Camera.PictureCallbackCamera.ShutterCallback物件,並將它們傳遞給Camera.takePicture()

如果要連續抓取影像,可以建立一個實現onPreviewFrame()Camera.PreviewCallback。 對於介於兩者之間的內容,您可以僅捕獲選定的預覽幀,或設定延遲操作以呼叫takePicture()

六、重新啟動預覽

拍攝照片後,必須重新開始預覽,然後使用者才能拍攝另一張照片。 下述程式碼展示瞭如何透過過載快門按鈕完成重啟:

fun onClick(v: View) {
    mPreviewState = if (mPreviewState == K_STATE_FROZEN) {
        mCamera?.startPreview()
        K_STATE_PREVIEW
    } else {
        mCamera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

七、停止預覽並釋放相機

Camera功能使用完畢後,釋放對應的資源保證相機能被其它應用所再次使用,至於釋放時機,在預覽View的surfaceDestroyed()處理是個不錯的選擇。

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
    }
}

筆者注:官方文件關於《控制Camera》小節的課程文件,歸納得實在是無力吐槽,關於Camera各項引數的配置方式請參考筆者接下來的文章,本小節的作用請將其視為Camera相關知識的系統化瞭解

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2157/viewspace-2815304/,如需轉載,請註明出處,否則將追究法律責任。

相關文章