使用 Camera API 获取 NV21 数据流

in Program with 0 comment
之前学习了图片和音频,这次我们尝试使用 Android Camera API 获取到视频数据。

简介

关于 Camera2 API

这次使用的 API 是 Camera2Camera2 是 Google 在 Android L 之后推出的全新的相机 API。Camera2 支持的功能要比 Camera 丰富很多,但是相应的,也增加了 API 的使用难度。

流程图

这是使用 Camera2 打开相机获取预览数据的流程图:

NV21 是什么?

NV21YUV420p 的一种存储模式。存储顺序是先存 Y,再存 U,然后再 VU 交替存储。

那么问题来了,YUV 是啥?

这里简要介绍下,后续可以专门一篇文章来介绍,当然你也可以在网上寻找其他资料来了解这个。

YUV 是啥

YUV 是一种颜色编码方法,主要应用于电视系统和模拟视频领域。其中 YUV 代表三个分量,Y 代表明亮度UV 表示的是色度

如何使用 Camera2?

这次我们关注的是获取视频数据,所以对于相机相关的一些东西不会涉及。

主要的类

这次我们要获取视频数据,还有一个类很重要:

使用流程

接下来用简洁的代码描述下如何使用 Camera2 API:

  1. 初始化相机

    private fun configCamera(cameraManager: CameraManager, cameraId: String): Boolean {
        val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
        val facing = cameraCharacteristics[LENS_FACING]
        if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
            // 不使用前置摄像头
            return false
        }
        val streamConfigurationMap =
            (cameraCharacteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
                ?: return false)
        mImageReader = ImageReader.newInstance(
            mSurfaceView.width,
            mSurfaceView.height,
            ImageFormat.YUV_420_888,
            2
        )
        mImageReader.setOnImageAvailableListener(ImageReaderAvailableListenerImp(), mBackgroundHandler)
        mCameraId = cameraId
        return true
    }

    这里是获取目标相机的 ID,还有初始化 ImageReader

  2. 调用 openCamera()
  3. openCamera 之后在 onOpened 回调中初始化 Session

    private fun createCameraPreviewSession(camera: CameraDevice) {
        mPreviewRequestBuilder = camera.createCaptureRequest(TEMPLATE_PREVIEW)
        mPreviewRequestBuilder.addTarget(mSurfaceView.holder.surface)
        mPreviewRequestBuilder.addTarget(mImageReader.surface)
        camera.createCaptureSession(
            listOf(
                mSurfaceView.holder.surface,
                mImageReader.surface
            ), mCameraSessionStateCallback, mBackgroundHandler
        )
    }
  4. Session 创建完成后,在 onConfigured 回调中,发送请求。

    override fun onConfigured(session: CameraCaptureSession) {
        Log.d(TAG, "onConfigured")
        session.setRepeatingRequest(
            mPreviewRequestBuilder.build(),
            object : CameraCaptureSession.CaptureCallback() {},
            mBackgroundHandler
        )
    }
  5. 最后,由于我们使用了 ImageReader,所以会在 onImageAvailable 回调中收到图像的回传。

    override fun onImageAvailable(reader: ImageReader) {
        val image = reader.acquireNextImage()
        if (image.format == ImageFormat.YUV_420_888) {
            val planes = image.planes
            lock.lock()
            if (!::y.isInitialized) {
                y = ByteArray(planes[0].buffer.limit() - planes[0].buffer.position())
                u = ByteArray(planes[1].buffer.limit() - planes[1].buffer.position())
                v = ByteArray(planes[2].buffer.limit() - planes[2].buffer.position())
    
            }
            if (planes[0].buffer.remaining() == y.size) {
                planes[0].buffer.get(y)
                planes[1].buffer.get(u)
                planes[2].buffer.get(v)
                // 接下来通过转换,可以转换为 Bitmap 进行展示
            }
            lock.unlock()
        }
        image.close()
    }

这部分的完整代码可以在仓库Camera2Helper 类中看到。

最后

代码还是要有的,可以在 GitHub 仓库中找到:https://github.com/T-Oner/MediaPractice

Responses