Camera2學習總結(jié)

一瞎饲、簡述

1伯诬、camera2的發(fā)展是由于手機廠商在添加過多的攝像頭,而google的camera1只是針對單攝像頭的所以對于多攝像頭的處理不是很友好织盼。
2杨何、由于手機主板需要占據(jù)手機大部分的空間,而手機的攝像頭也是長方形的悔政,所以手機廠商的后置攝像頭的放置一般都是逆時針旋轉(zhuǎn)90度的晚吞。

二、學習camera2需要了解哪些api的使用谋国?

CameraManager//獲取相機的一些參數(shù)信息和打開相機的管理類
CameraCaptureSession//用來向相機設(shè)備發(fā)送獲取圖像的請求
ImageReader//獲取屏幕渲染數(shù)據(jù) 可以搭配MediaProjectionManager錄屏一起使用

三槽地、學習camera2遇到的一些問題

1:用戶在使用手機在拍照的時候不會是完全豎直使用的,那在用戶點擊拍照的時候怎么處理手機旋轉(zhuǎn)帶來的問題?
2:前置攝像頭就像是鏡子捌蚊,所以拍照出來的數(shù)據(jù)是鏡像的我們需要怎么處理集畅?
3:用戶切換前后攝像頭代碼應該怎么做?
? ?? 帶著以上3個問題開始代碼之旅

四缅糟、問題處理
  1. 手機旋轉(zhuǎn)我們可以做監(jiān)聽處理挺智,系統(tǒng)提供了OrientationEventListener
val mOrEventListener: OrientationEventListener =
            object : OrientationEventListener(context) {
                override fun onOrientationChanged(orientation: Int) {
                    if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
                        phoneDegree = 0;
                    } else if ((orientation > 45) && (orientation <= 135)) {
                        phoneDegree = 90;
                    } else if ((orientation > 135) && (orientation <= 225)) {
                        phoneDegree = 180;
                    } else if ((orientation > 225) && (orientation <= 315)) {
                        phoneDegree = 270;
                    }
                }
            }
mOrEventListener.enable()

我們將獲取的旋轉(zhuǎn)角度保存為全局變量,此時只是獲取了旋轉(zhuǎn)角度窗宦,還有攝像頭對應得角度我們還需要再處理赦颇,上代碼

val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)

mCameraId是對應得前置還是后置的攝像頭,mOrientation 就是相機需要的旋轉(zhuǎn)角度

  • 對應得前置攝像頭的處理方案
(mOrientation-phoneDegree + 360)% 360
  • 對應得后置攝像頭的處理方案
(mOrientation+phoneDegree )% 360

至此第一個問題可以解決了!8昂媒怯!

  1. 鏡像處理應該大家都做過很簡單的一個沿著x翻轉(zhuǎn)就可以了,這里我們用的是Matrix
 matrix.postScale(-1F, 1F);//利用matrix 對矩陣進行轉(zhuǎn)換髓窜,x軸鏡像

至此第二個問題可以解決了I劝!寄纵!

  1. 第三個不是技術(shù)問題鳖敷,是攝像頭在使用過程中不支持動態(tài)更改配置,如果需要更改的話必須重新初始化所有配置操作程拭。所以封裝好代碼按步驟初始化吧定踱。這類需要提的幾點就是,對于camera如果已經(jīng)打開過了我們需要release
 captureSession!!.close()
 cameraDevice!!.close()

對于ImageReader類我們已經(jīng)創(chuàng)建過的需要close

imageReader!!.close()

至此以上遇到的一些問題都解決了2负N荻帧!沒有可以難倒我們的了山宾。接下來就是看看代碼如何處理吧
(代碼主要是kotlin寫的,初學者鳍徽,希望寫的不對的大家可以指教W拭獭!=准馈1炼拧)
下面貼一下完整的代碼。

class Camera2Manager(var context: Context, var captureTexture: SurfaceTexture) {
    private val tag = Camera2Manager::class.java.simpleName
    private var handlerThread = HandlerThread("Camera2Manager")
    private var handler: Handler? = null
    private var mCameraId = "0"http://初始為后置攝像頭
    private var cameraManager: CameraManager? = null//獲取相機的一些參數(shù)信息和打開相機的管理類
    private var captureSession: CameraCaptureSession? = null//用來向相機設(shè)備發(fā)送獲取圖像的請求
    private var imageReader: ImageReader? = null//獲取屏幕渲染數(shù)據(jù) 可以搭配MediaProjectionManager錄屏一起使用
    private var previewWidth = 0//圖片預覽寬
    private var previewHeight = 0//圖片預覽高
    private var cameraDevice: CameraDevice? = null
    private var captureCallBack: CaptureCallBack? = null
    private var isFrontCamera: Boolean = false//前置攝像頭的判斷
    private var phoneDegree = 0;//手機的旋轉(zhuǎn)角度
    init {
        handlerThread.start()//啟動工作線程
        handler=Handler(handlerThread.looper)
        cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        mCameraId = getCamera()[0]
        initDefaultPreviewSize()
        startOrientationListener()
    }
    /**
     * 主要是監(jiān)聽手機旋轉(zhuǎn)角度來保證所拍攝的照片始終是正向的
     */
    private fun startOrientationListener() {
        val mOrEventListener: OrientationEventListener =
            object : OrientationEventListener(context) {
                override fun onOrientationChanged(orientation: Int) {
                    if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
                        phoneDegree = 0;
                    } else if ((orientation > 45) && (orientation <= 135)) {
                        phoneDegree = 90;
                    } else if ((orientation > 135) && (orientation <= 225)) {
                        phoneDegree = 180;
                    } else if ((orientation > 225) && (orientation <= 315)) {
                        phoneDegree = 270;
                    }
                }
            }
        mOrEventListener.enable()
    }

    /**
     * 獲取CameraId 一臺設(shè)備會有多個攝像頭
     */
    private fun getCamera(): Array<out String> {
        return cameraManager!!.cameraIdList
    }

    /**
     * 獲取當前camera的支持預覽大小 可以供用戶選擇分辨率不過一般給與最大分辨率拍攝
     */
    private fun getPreviewSize(): Array<out Size>? {
        var characteristics = cameraManager?.getCameraCharacteristics(mCameraId)
        val configs = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
        return configs!!.getOutputSizes(SurfaceTexture::class.java)
    }

    /**
     * 根據(jù)cameraId打開指定的攝像頭
     */
    fun openCamera() {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        createImageReader()
        cameraManager?.openCamera(mCameraId, cameraStateCallback, handler)
    }

    /**
     *  打開Camera的狀態(tài)回調(diào)
     */
    private var cameraStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(device: CameraDevice) {
            cameraDevice = device
            createCaptureSession()
        }
        override fun onDisconnected(device: CameraDevice) {
        }
        override fun onError(device: CameraDevice, p1: Int) {
        }
    }

    /**
     * 創(chuàng)建CameraCaptureSession的狀態(tài)回調(diào)
     */
    private var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            captureSession = session
            var captureSessionRequest =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureSessionRequest.addTarget(Surface(captureTexture))
            // 設(shè)置自動對焦模式
            captureSessionRequest?.set(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 設(shè)置自動曝光模式
            captureSessionRequest?.set(CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            var request = captureSessionRequest.build()
            captureSession!!.setRepeatingRequest(request, null, handler)
        }
        override fun onConfigureFailed(session: CameraCaptureSession) {
        }
    }
    /**
     * ImageReader獲取到數(shù)據(jù)時的回調(diào)
     */
    private var imageAvailableListener = ImageReader.OnImageAvailableListener {
        var image = imageReader!!.acquireLatestImage()//點擊拍照的時候會獲取當前的一幀數(shù)據(jù)
        var buffer = image.planes[0].buffer
        var length = buffer.remaining()
        var bytes = ByteArray(length)
        buffer.get(bytes)
        image.close()
        try {
            var bmp = BitmapFactory.decodeByteArray(bytes, 0, length, null)
            var matrix = Matrix()

            val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
            var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
            if (isFrontCamera) {
                matrix.postRotate((mOrientation-phoneDegree + 360)% 360 * 1.0F)
                matrix.postScale(-1F, 1F);//利用matrix 對矩陣進行轉(zhuǎn)換濒募,x軸鏡像
            } else {
                matrix.postRotate((mOrientation+phoneDegree )% 360 * 1.0F)
            }
            var bitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, false)
            if (captureCallBack != null) {
                captureCallBack!!.onSucceed(bitmap)
            }
        } catch (e: Exception) {
            if (captureCallBack != null) {
                captureCallBack!!.onFailed(Throwable(e.localizedMessage))
            }
        }

    }

    /**
     * 更改預覽鞭盟,本質(zhì)上是重新創(chuàng)建CameraCaptureSession和ImageReader
     */
    fun changePreviewSize(width: Int, height: Int) {
        previewWidth = width
        previewHeight = height
        if (cameraDevice != null) {
            createImageReader()
            createCaptureSession()
        }
    }

    /**
     * 創(chuàng)建CameraCaptureSession
     */
    private fun createCaptureSession() {
        captureTexture!!.setDefaultBufferSize(previewWidth, previewHeight);//設(shè)置SurfaceTexture緩沖區(qū)大小
        if (captureSession != null) {
            captureSession!!.close()
            captureSession = null
        }
        cameraDevice!!.createCaptureSession(
            listOf(
                Surface(captureTexture),
                (imageReader!!.surface)
            ), captureSessionCallback, handler
        )
    }

    /**
     * 創(chuàng)建ImageReader
     */
    private fun createImageReader() {
        if (imageReader != null) {
            imageReader!!.close()
            imageReader = null
        }
        imageReader = ImageReader.newInstance(previewWidth, previewHeight, ImageFormat.JPEG, 2)
        imageReader!!.setOnImageAvailableListener(imageAvailableListener, handler)
    }

    /**
     * 重新打開一個相機 打開之前要先做一下清理
     * 每次切換攝像頭都需要重新創(chuàng)建
     */
    fun openCamera(cameraId: String) {
        if (cameraDevice != null) {
            releaseCamera()
        }

        //可以根據(jù)下面的代碼判斷是否是前置攝像頭或者后置
//        val characteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
//        if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)//前置
//        if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK)//后置
        mCameraId = cameraId
        isFrontCamera = when (cameraId) {
            "0" -> false
            else -> true
        }
        initDefaultPreviewSize()
        openCamera()
    }

    /**
     * 獲取默認的預覽尺寸 取第一個尺寸的默認最大
     */
    private fun initDefaultPreviewSize() {
        var previewSize = getPreviewSize()
        for (i in 0 until previewSize?.size!!) {
            var width = previewSize[i].width
            var height = previewSize[i].height
            Log.e("我的預覽尺寸$i", "$width  ----  $height")
        }
        var size = previewSize!![0]
        previewWidth = size.width
        previewHeight = size.height
    }

    /**
     * 拍照
     */
    fun capturePic(captureCallBack: CaptureCallBack) {
        if (cameraDevice == null) {
            return
        }
        this.captureCallBack = captureCallBack
        try {
            var requestBuilder =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
            requestBuilder.addTarget(imageReader!!.surface)
            var request = requestBuilder.build()
            captureSession!!.capture(request, null, handler)
        } catch (e: Exception) {
            if (captureCallBack != null) {
                captureCallBack.onFailed(Throwable(e.localizedMessage))
            }
        }
    }
    /**
     * 釋放相機
     */
    fun releaseCamera() {
        captureSession!!.close()
        cameraDevice!!.close()
    }
    /**
     * 釋放handlerThread
     */
    fun destroy() {
        handlerThread.quit()
    }
}

至此暫時結(jié)束簡單的處理,接下來有時間會寫更詳細的處理瑰剃,還有camerax的使用之類的齿诉。
簡書不一定會常用,以后也會不定期更新一些關(guān)于音視頻方面的東西,希望跟志同道合的朋友一起學習進步粤剧。
下一篇會寫android端p2p的投屏軟件歇竟,喜歡的朋友可以關(guān)注下一起進步。
追加一些自動對焦的處理抵恋,引用下別人的文章Android Camera2之 手動點擊區(qū)域?qū)?/a>

  • 序言:七十年代末焕议,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子弧关,更是在濱河造成了極大的恐慌盅安,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡血巍,警方通過查閱死者的電腦和手機本鸣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吃挑,“玉大人,你說我怎么就攤上這事∽崖” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵猫胁,是天一觀的道長箱亿。 經(jīng)常有香客問我,道長弃秆,這世上最難降的妖魔是什么届惋? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮菠赚,結(jié)果婚禮上脑豹,老公的妹妹穿的比我還像新娘。我一直安慰自己衡查,他們只是感情好瘩欺,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拌牲,像睡著了一般俱饿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上塌忽,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天拍埠,我揣著相機與錄音,去河邊找鬼土居。 笑死枣购,一個胖子當著我的面吹牛嬉探,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坷虑,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼甲馋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迄损?” 一聲冷哼從身側(cè)響起定躏,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芹敌,沒想到半個月后痊远,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡氏捞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年碧聪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片液茎。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡逞姿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捆等,到底是詐尸還是另有隱情滞造,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布栋烤,位于F島的核電站谒养,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏明郭。R本人自食惡果不足惜买窟,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薯定。 院中可真熱鬧始绍,春花似錦、人聲如沸话侄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽满葛。三九已至,卻和暖如春罢屈,著一層夾襖步出監(jiān)牢的瞬間嘀韧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工缠捌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锄贷,地道東北人译蒂。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像谊却,于是被迫代替她去往敵國和親柔昼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容