音视频:使用 Camera API 获取 NV21 数据流

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

关于 Camera2 API

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

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

NV21 是什么?

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

那么问题来了, YUV 是啥?

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

YUV 是啥

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

如何使用 Camera2?

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

  • CameraManager :摄像头的管理类。
  • CameraCharacteristics :用于描述特定摄像头所支持的特性。
  • CameraDevice :代表摄像头。
  • CameraCaptureSession :相机实际的控制端,我们需要在相机上做什么操作,都是由这个类发出相应的指令。
  • CameraRequest :每次发起捕获请求的时候都需要传递这个对象,这个类代表了一次捕获请求,用于描述捕获的各种参数。
  • 这次我们要获取视频数据,还有一个类很重要:

    • ImageReader :可以从 Surface 直接接收渲染的数据。需要注意的是,它并不是专为 Camera 设计的。
    • 接下来用简洁的代码描述下如何使用 Camera2 API:

      1. 初始化相机
      2.    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,
               mImageReader.setOnImageAvailableListener(ImageReaderAvailableListenerImp(), mBackgroundHandler)
               mCameraId = cameraId
               return true
         

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

        1. 调用 openCamera()
        2. openCamera 之后在 onOpened 回调中初始化 Session
        3.    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
           

          1. Session 创建完成后,在 onConfigured 回调中,发送请求。
          2.    override fun onConfigured(session: CameraCaptureSession) {
                   Log.d(TAG, "onConfigured")
                   session.setRepeatingRequest(
                       mPreviewRequestBuilder.build(),
                       object : CameraCaptureSession.CaptureCallback() {},
                       mBackgroundHandler
             

            1. 最后,由于我们使用了 ImageReader,所以会在 onImageAvailable 回调中收到图像的回传。
            2.    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()