音视频:使用 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:
- 初始化相机
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
。
- 调用
openCamera()
。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
Session
创建完成后,在onConfigured
回调中,发送请求。override fun onConfigured(session: CameraCaptureSession) { Log.d(TAG, "onConfigured") session.setRepeatingRequest( mPreviewRequestBuilder.build(), object : CameraCaptureSession.CaptureCallback() {}, mBackgroundHandler
- 最后,由于我们使用了
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()