【Android音视频学习之路(一)】如何在 Android 平台绘制一张图片
【Android音视频学习之路(二)】AudioRecord API详解及应用
【Android音视频学习之路(三)】AudioTrack 使用与详解
一、Camera 简介
这儿介绍的是Camera2,目录为android.hardware.Camera2,不再推荐使用camera因为从api21开始这个类已经被标记为过时,现在谷歌大大推荐使用android.hardware.Camera2,但是Camera2要从api21才支持。也会说明一下camera,毕竟用法简单
借助Camera可以利用设备的相机来预览画面,拍照和拍视频。要使用Camera需要在Manifest文件中添加Manifest.permission.CAMERA
权限同时如果要进行自动对焦,还需要特性声明。
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
//存储权限也需要
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
复制代码
注意:如果你只是想简单的实现拍照和拍照视频功能,可以利用Intent打开系统提供的功能。MediaStore.ACTION_IMAGE_CAPTURE
拍摄照片;MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频;
基本架构
在API架构方面, Camera2和之前的Camera有很大区别, APP和底层Camera之前可以想象成用管道方式连接, 如下图:
如上图所示, Camera APP 通过CameraCaptureSession发送CaptureRequest, CameraDevices收到请求后返回对应数据到对应的Surface,预览数据一般都是到TextureView, 拍照数据则在ImageReader中, 整体来说就是一个请求–响应过程, 请求完成后, 可以在回调中查询到相应的请求参数和CameraDevice当前状态, 总的来说, Camera2中预览/拍照/录像数据统一由Surface来接收, CaptureRequest代表请求控制的Camera参数, CameraMetadata(CaptureResult)则表示当前返回帧中Camera使用的参数以及当前状态.
使用流程
- 通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager.
- 调用CameraManager .open()方法在回调中得到CameraDevice.
- 通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession.
- 构建CaptureRequest, 有三种模式可选 预览/拍照/录像.
- 通过 CameraCaptureSession发送CaptureRequest, capture表示只发一次请求, setRepeatingRequest表示不断发送请求.
- 拍照数据可以在ImageReader.OnImageAvailableListener回调中获取, CaptureCallback中则可获取拍照实际的参数和Camera当前状态
查询Camera2功能支持情况
因为Camera2的功能比较多,所以不是所以手机都支持完整的Camera2功能,可能有些低端机功能就不完整,如某些中低端手机使用的HAL1, 使用HAL1就会导致Camera2一些高级功能都没法使用了, 下面讲一下如何查询设备对应Camera2的支持情况
INFO_SUPPORTED_HARDWARE_LEVEL
硬件层面支持的Camera2功能等级, 主要分为5个等级:
-
INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
向后兼容模式, 如果是此等级, 基本没有额外功能, HAL层大概率就是HAL1
-
INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
有最基本的功能, 还支持一些额外的高级功能, 这些高级功能是LEVEL_FULL的子集
-
INFO_SUPPORTED_HARDWARE_LEVEL_FULL
支持对每一帧数据进行控制,还支持高速率的图片拍摄
-
INFO_SUPPORTED_HARDWARE_LEVEL_3
支持YUV后处理和Raw格式图片拍摄, 还支持额外的输出流配置
-
INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL
API28中加入的, 应该是外接的摄像头, 功能和LIMITED类似
private fun getCameraHardwareSupport() {
// cameraCharacteristics: 相机设备的属性类,
// 通过 CameraManager 的 getCameraCharacteristics(String cameraId) 方法获取指定相机设备的 CameraCharacteristics 对象
val cameraManager = cameraManager ?: return
val cameraIdList = cameraManager.cameraIdList
// 这里直接拿第一个摄像头
val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraIdList[0])
val deviceLevel = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ?: return
when(deviceLevel) {
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY -> {}
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED -> {}
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL -> {}
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 -> {}
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL -> {}
}
}
复制代码
具体支持什么功能还可以通过REQUEST_AVAILABLE_CAPABILITIES来查询,详情见官方文档
二、预览 Camera 数据
众所周知有两种方法能够做到这一点:SurfaceView、TextureView。(但这2种本质上都拿到surface)
- camera的使用
private fun previewCamera() {
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
camera = Camera.open()
camera?.setDisplayOrientation(90);
binding.surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
camera?.setPreviewDisplay(surfaceHolder)
camera?.startPreview()
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
camera?.release()
}
})
}
复制代码
- camera2的使用
android 9 以上 推荐使用 SessionConfiguration 来创建 CameraCaptureSession
private fun previewCamera2(surface: Surface) {
val cameraIdList = cameraManager!!.cameraIdList
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
ALog.e("xiao", "无相机权限")
return
} else {
//handler: 用于指定调用该回调的线程。如果不指定则置null,此时系统会默认使用当前线程的looper。当需要将该回调放到后台线程处理时,可通过指定该handler的looper来设置:
cameraManager?.openCamera(cameraIdList[0], object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
//当camera成功打开之后会回调该方法。此时camera已经就绪,可以开始对相机进行一系列的操作,
//可通过调用CameraCaptureSession.createCaptureSession方法来设置第一个capture session
onOpenCamera(camera, surface)
}
override fun onDisconnected(camera: CameraDevice) {
//当camera不再可用,或调用CameraManager.openCamera()打开相机失败时都会
//调用此方法.此时任何尝试调用CameraDevice方法的操作都会失败并抛出一个
//CameraAccessException异常。
//安全策略或权限的改变、可移动相机设备的物理断开、或者当该camera需要更高优
//先级的camera API Client时都会导致该camera设备连接断开
}
override fun onError(camera: CameraDevice, p1: Int) {
//顾名思义。当camera出错时会回调该方法
//应在该方法调用CameraDevice.close()和做一些其他释放相机资源的操作,防止相机出错而导致一系列问
}
override fun onClosed(camera: CameraDevice) {
super.onClosed(camera)
//当调用CameraDevice.close()关闭相机设备后会回调此方法。camera为别关闭的相机设备。
//该方法被回调执行后,任何尝试调用camera相关的操作都会失败,并且抛出一个IllegalStateException异常
}
}, null)
}
}
private fun onOpenCamera(cameraDevice: CameraDevice, surface: Surface) {
val stateCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
// 将CaptureRequest的构建器与Surface对象绑定在一起
builder.addTarget(surface)
cameraCaptureSession.setRepeatingRequest(builder.build(), null, null)
}
override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
ALog.e("xiao", "onConfigureFailed")
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// android 9以上的相机打开方式
val sessionConfiguration = SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
listOf(OutputConfiguration(surface)),
mainExecutor,
stateCallback
)
cameraDevice.createCaptureSession(sessionConfiguration)
} else {
//创建CaptureSession会话。第一个参数 outputs 是一个 List 数组,相机会把捕捉到的图片数据传递给该参数中的 Surface 。第二个参数 StateCallback 是创建会话的状态回调。第三个参数描述了 StateCallback 被调用时所在的线程
cameraDevice.createCaptureSession(
listOf(surface),
stateCallback,
null
)
}
}
复制代码
三、取到 NV21 的数据回调
Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是
YV12。Android一般默认使用YCbCr_420_SP的格式(NV21)。
NV21 是什么?
NV21 是 YUV420p 的一种存储模式。存储顺序是先存 Y,再存 U,然后再 VU 交替存储。
YUV 是啥
YUV 是一种颜色编码方法,主要应用于电视系统和模拟视频领域。其中 YUV 代表三个分量,Y 代表明亮度,U 和 V 表示的是色度。
我们可以配置数据回调的格式:
- camera
val parameter = camera?.parameters
parameter?.previewFormat = ImageFormat.NV21
camera?.parameters = parameter
camera?.setPreviewCallback { bytes, camera ->
}
复制代码
- camera2
// 要获取每一帧的预览数据,我们需要ImageReader这个类的帮助
// maxImages 用户希望同时访问的最大映像数。这个值应该尽可能小,以限制内存的使用。一旦用户获得了maxImages Images,在通过AcquireLatestImage()或AcquireNextImage()访问新的Image之前,必须释放其中的一个Image。必须大于0。
val reader = ImageReader.newInstance(
binding.surfaceViewPreview.width,
binding.surfaceViewPreview.width,
ImageFormat.YUV_420_888, // camera2不支持NV21,官方推荐用YUV_420_888
2
)
reader.setOnImageAvailableListener({ imageReader ->
val image = imageReader.acquireNextImage()
if (image.format == ImageFormat.YUV_420_888) {
/**
* YUV_420_888是一种Y:U:V按4:1:1的比例进行采样的格式,也就是说其中每一个UV都被四个Y共享, 888表示每一个分量都是8bits
* NV21也是这种YUV_420的采样格式,只是其中U,V分量的排列不一样.
* Y,U,V的数据是分别存储在3个plane中的;
* plane#0为Y分量,plane#1为U分量,plane#2为V分量;
**/
ALog.e("xiao", "Y: ${image.planes[0].buffer}")
ALog.e("xiao", "U: ${image.planes[1].buffer}")
ALog.e("xiao", "V: ${image.planes[2].buffer}")
}
image.close()
}, null)
复制代码
builder.addTarget(reader.surface)
复制代码