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

之前学习了图片和音频,这次我们尝试使用 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?

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

主要的类

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

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

  • ImageReader:可以从 Surface 直接接收渲染的数据。需要注意的是,它并不是专为 Camera 设计的。

使用流程

接下来用简洁的代码描述下如何使用 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

  1. 调用 openCamera()
  2. 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
       )
   }

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

  1. 最后,由于我们使用了 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

最新更新请关注微信公众号

https://juejin.im/post/5e341cea6fb9a0301a06c761

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论