Android Camera2学习笔记

直接说Camera2,参看文章(本文章是下面文章的学习笔记,建议直接阅读下面文章)

https://www.jianshu.com/p/9a2e66916fcb 概览

https://www.jianshu.com/p/df3c8683bb90 开关

https://www.jianshu.com/p/067889611ae7 预览

https://www.jianshu.com/p/2ae0a737c686 拍照

概览

1. 拍照的流程

如下图,Camera2的API模型被设计成一个Pipeline(管道),顺序的处理每一帧的请求并返回请求结果给客户端。

2. Capture

Capture不仅仅是拍照而已,事实上,Camera2中所有的操作都被抽象成Capture(捕获),如对焦等。

Capture又可以细分为单次模式、多次模式和重复模式

  • 单次模式(One-shot):指的是只执行一次的Capture操作,例如设置闪光灯模式、对焦模式和拍一张照片等,多次一次性模式的Capture会进入队列按顺序执行
  • 多次模式(Burst):指的是连续多次执行指定的Capture操作,注意,多次Capture执行期间不允许插入其他任何的Capture操作,例如连续拍摄100张照片
  • 重复模式(Repeating):指的是不断重复执行指定的Capture操作,当有其他模式的Capture提交时会暂停该模式,转而执行其他模式的Capture,当其他模式的Capture执行完毕后会恢复执行。 如预览等

3. CameraManager

CameraManager功能不多,主要用来查询相机信息和执行打开相机的操作

4. CameraCharacteristics

CameraCharacteristics中携带了大量的相机信息,如相机朝向,判断闪光灯是否可用,AE模式等

5. CameraDevice

表示当前连接的摄像头,通过CameraDevice,可以有下面操作

  • 创建CameraCaptureSession
  • 创建CaptureRequest
  • 关闭相机操作(打开是通过CameraManager)
  • 监听设备状态

6. Surface

Surface是一块用于填充图像的内存空间,例如你可以使用SurfaceView的Surface接收每一帧预览数据用于显示预览画面,也可以使用ImageReader和Surface接收JPEG或YUV数据,每一个Surface都可以有自己的尺寸和数据格式,你可以从CameraCharacteristics获取某一个数据格式支持的尺寸列表

7. CameraCaptureSession

顾名思义,CameraCaptureSession就是与摄像头建立的一次“会话”,后面的CaptureRequest都会提交该回话

8. CameraRequest

一次相机操作请求,如拍照,预览等

开关相机

1. 相机配置

AndroidManfiest中添加如下代码

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

    <uses-feature android:name="android.hardware.camera" />

动态申请相机权限(太简单了,略)

2. 获取摄像头信息

    private val mCameraManager by lazy {
        mContext.getSystemService(CameraManager::class.java) as CameraManager
    }

    /**
     * 初始化得到CameraId
     */
    fun initCameraId() {
        if (mBackCameraId.isNotEmpty() || mFortCameraId.isNotEmpty()) return
        // mCameraManager就是CameraManager实例
        for (cameraId in mCameraManager.cameraIdList) {
            val cameraCharacter = mCameraManager.getCameraCharacteristics(cameraId)
            val facing = cameraCharacter.get(CameraCharacteristics.LENS_FACING)
            //相机朝向, 我们这里很简单, 就分别获取第一个摄像头
            if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
                if (mFortCameraId.isEmpty()) mFortCameraId = cameraId
            } else if (facing == CameraCharacteristics.LENS_FACING_BACK) {
                if (mBackCameraId.isEmpty()) {
                    mBackCameraId = cameraId
                }
            }
        }
    }

2. 打开相机

代码如下

    fun openCamera(surface: Surface) {
        if (isOpenCamera()) return
        initCameraId()
        //检查权限
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            return
        }
        //打开相机
        mCameraManager.openCamera(mBackCameraId, mCameraStateCallback, mBackGroundHandler)
        mPreviewSurface = surface
    }


    private val mCameraStateCallback: CameraDevice.StateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            logcat("camera onOpen")
            // 获取到CameraDevice实例,后面还需要这个实例进行后续操作
            mCameraDevice = camera
            openPreviewSession()
        }

        override fun onDisconnected(camera: CameraDevice) {
            logcat("camera on disconnect")
            camera.close()
            mCameraDevice = null
        }

        override fun onClosed(camera: CameraDevice) {
            logcat("camera onClosed")
            mCameraDevice = null
        }

        override fun onError(camera: CameraDevice, error: Int) {
            logcat("camera onError")
            camera.close()
            mCameraDevice = null
        }
    }

    private val mBackGroundHandler by lazy {
        Handler(mBackGroundHandlerThread.looper)
    }

    private val mBackGroundHandlerThread by lazy {
        val thread = HandlerThread("back ground")
        thread.start()
        thread
    }

3. 关闭相机

    fun closeCamera() {
        mCameraDevice?.close()
        mCameraDevice = null
    }

预览

1. 获取Surface

我们使用SurfaceTexture来显示预览画面

        <TextureView
            android:id="@+id/camera_preview"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

我们设置SurfaceTextureListener,成功之后创建Surface

        binding.cameraPreview.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
                if (surface == null) return
                //获取预览尺寸, 这里对主流程来说不重要, 这里根据心情来写
                val previewSize = MyCameraManager.instance.getPreviewSize(binding.cameraPreview.height, binding.cameraPreview.width) ?: return
                surface.setDefaultBufferSize(previewSize.width, previewSize.height)
                val lp = binding.cameraPreview.layoutParams
                lp.width = previewSize.height
                lp.height = previewSize.width
                // 根据预览尺寸来设置预览控件大小
                binding.cameraPreview.layoutParams = lp
                //获取预览的Surface
                mPreviewSurface = Surface(surface)
                if (!MyCameraManager.instance.isOpenCamera() && mIsResumed) {
                    //打开相机
                    MyCameraManager.instance.openCamera(mPreviewSurface!!)
                }
            }

            override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
                logcat("onSurfaceTextureSizeChanged")
            }

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
                logcat("onSurfaceTextureDestroyed")
                return true
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
//                logcat("onSurfaceTextureUpdated")
            }
        }

2. 预览

在上面我们已经展示了打开相机的操作,打开相机之后调用openPreviewSession来创建一个会话。具体实现代码如下

    private fun openPreviewSession() {
        val surface = mPreviewSurface ?: return
        val device = mCameraDevice ?: return
        val characteristics = mCameraManager.getCameraCharacteristics(mBackCameraId)
        //得到预览的尺寸, 这个对本教程不重要, 这里根据自己的心情来
        val imageSize = getOptimalSize(characteristics, ImageReader::class.java, maxWidth = 1920, maxHeight = 1080)
        if (imageSize != null) {
            logcat("ImageSize:${imageSize.width} ${imageSize.height}")
            val jpegReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 2)
            jpegReader.setOnImageAvailableListener(OnJpegImageAvailableListener(), mBackGroundHandler)
            mJpegSurface = jpegReader.surface
            //这里添加两个Surface, 一个是预览的Surface, 另外一个是拍照的Surface
            device.createCaptureSession(listOf(surface, mJpegSurface), mSessionStateCallback, mBackGroundHandler)
        }
    }

    private var mSessionStateCallback: CameraCaptureSession.StateCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            logcat("session onConfigured")
            val camera = mCameraDevice ?: return
            val surface = mPreviewSurface ?: return
            mSession = session

            //当Session创建成功之后, 发出一个request
            val requestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            //这里就是我们预览的Surface
            requestBuilder.addTarget(surface)
            val request = requestBuilder.build()
            // 发起一个Repeating Request
            session.setRepeatingRequest(request, object : CameraCaptureSession.CaptureCallback() {
                override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
                }

                override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult) {
                }

                override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
                }
            }, mBackGroundHandler)
        }
    }

如上,我们就可以将预览的图像显示出来了

拍照

注: 拍照这个部分可能会有兼容性问题,下面代码小米手机拍照异常,华为ok。应该还是有一些细节没有处理好。

创建用于获取拍照图像的Surface

    private fun openPreviewSession() {
        val surface = mPreviewSurface ?: return
        val device = mCameraDevice ?: return
        val characteristics = mCameraManager.getCameraCharacteristics(mBackCameraId)
        //得到预览的尺寸, 这个对本教程不重要, 这里根据自己的心情来
        val imageSize = getOptimalSize(characteristics, ImageReader::class.java, maxWidth = 1920, maxHeight = 1080)
        if (imageSize != null) {
            logcat("ImageSize:${imageSize.width} ${imageSize.height}")
            val jpegReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 2)
            jpegReader.setOnImageAvailableListener(OnJpegImageAvailableListener(), mBackGroundHandler)
            mJpegSurface = jpegReader.surface
            //这里添加两个Surface, 一个是预览的Surface, 另外一个是拍照的Surface
            device.createCaptureSession(listOf(surface, mJpegSurface), mSessionStateCallback, mBackGroundHandler)
        }
    }

这里需要设置ImageAvailableListener,用于保存图像

    private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {
        private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.CHINA)
        private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera"

        override fun onImageAvailable(reader: ImageReader?) {
            logcat("onImageAvailable:$reader")
            if (reader == null) return
            val image = reader.acquireNextImage()
            val captureResult = captureResults.take()
            if (image != null && captureResult != null) {
                logcat("image != null ")
                val jpegByteBuffer = image.planes[0].buffer
                val jpegByteArray = ByteArray(jpegByteBuffer.remaining())
                jpegByteBuffer.get(jpegByteArray)
                val width = image.width
                val height = image.height
                runIO {
                    val date = System.currentTimeMillis()
                    val title = "IMG_${dateFormat.format(date)}"
                    val displayName = "$title.jpeg"
                    val path = "$cameraDir/$displayName"
                    val orientation = captureResult[CaptureResult.JPEG_ORIENTATION]
                    val location = captureResult[CaptureResult.JPEG_GPS_LOCATION]
                    val longitude = location?.longitude ?: 0.0
                    val latition = location?.latitude ?: 0.0
                    logcat("write to local:$path")
                    File(path).writeBytes(jpegByteArray)

                    val values = ContentValues()
                    values.put(MediaStore.Images.ImageColumns.TITLE, title)
                    values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName)
                    values.put(MediaStore.Images.ImageColumns.DATA, path)
                    values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date)
                    values.put(MediaStore.Images.ImageColumns.WIDTH, width)
                    values.put(MediaStore.Images.ImageColumns.HEIGHT, height)
                    values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation)
                    values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude)
                    values.put(MediaStore.Images.ImageColumns.LATITUDE, latition)

                    mContext.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
                }
            }
        }
    }

创建Request,发起拍照请求

    fun takePic() {
        val builder = mCameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) ?: return
        builder.addTarget(mPreviewSurface!!)
        builder.addTarget(mJpegSurface!!)
        mSession?.capture(builder.build(), CaptureImageStateCallback(), mBackGroundHandler)
    }

获取CaptureRequest

    private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
            logcat("onCaptureCompleted")
            super.onCaptureCompleted(session, request, result)
            captureResults.put(result)
        }
    }

这样就可以了。

猜你喜欢

转载自blog.csdn.net/weixin_43662090/article/details/113851758