之前学习了图片和音频,这次我们尝试使用 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,
2
)
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()
}
这部分的完整代码可以在仓库的 Camera2Helper
类中看到。
最后
代码还是要有的,可以在 GitHub 仓库中找到:https://github.com/T-Oner/MediaPractice
最新更新请关注微信公众号