Android在应用层提供丰富的音视频多媒体接口,本文主要介绍CameraX,背景是在Camera2基础上,提供Lifecycle生命周期管理,提供更加简洁易用接口,包括摄像头预览、拍照、录像、图片分析,另外支持扩展接口:自动切换弱光模式、焦外成像、HDR拍摄等。
第一篇文章探讨:MediaPlayer、MediaCodec、AudioTrack、MediaMuxer、MediaExtractor。第二篇文章探讨:MediaRecorder、AudioRecord、MediaMetadataRetriever、MediaProjection、AudioManager。
目录
一、权限与依赖
二、配置参数
1、旋转角度
2、矩形裁剪
3、选择摄像头
4、相机分辨率
5、?变焦与曝光
三、摄像头预览
1、使用PreviewView布局
2、请求CameraProvider
3、检查CameraProvider可用性
4、选择相机并绑定生命周期
5、监听相机状态
四、拍照
1、拍照接口
2、拍照模式
3、拍照实例并绑定生命周期
4、拍照保存
五、录像
1、配置录像参数
2、开始录像
六、图像分析
1、创建图像分析器
2、自定义分析器
七、扩展API
一、权限与依赖
使用摄像头需要camera权限和feature:
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
如果要录像录音,需要额外权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
?摄像头核心库与扩展库依赖:
// CameraX core library
def camerax_version = '1.1.0-beta02'
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:$camerax_version"
implementation "androidx.camera:camera-video:${camerax_version}"
// CameraX Extensions library
implementation "androidx.camera:camera-extensions:$camerax_version"
二、配置参数
1、旋转角度
相机的旋转角度会设置为与默认的显示屏旋转角度保持一致。也可以通过Preview、ImageCapture、ImageAnalysis设置旋转角度。以ImageCapture拍照为例,监听屏幕方向变化,自适应设置旋转角度:
val imageCapture = ImageCapture.Builder().build()
val orientationEventListener = object : OrientationEventListener(this as Context) {
override fun onOrientationChanged(orientation : Int) {
val rotation : Int = when (orientation) {
in 45..134 -> Surface.ROTATION_270
in 135..224 -> Surface.ROTATION_180
in 225..314 -> Surface.ROTATION_90
else -> Surface.ROTATION_0
}
imageCapture.targetRotation = rotation
}
}
orientationEventListener.enable()
2、矩形裁剪
默认情况下,显示完整预览画面,按照完整尺寸进行拍照。我们也可以通过ViewPort和UseCaseGroup自定义裁剪矩形区域。裁剪代码示例如下:
val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
.addUseCase(preview)
.addUseCase(imageAnalysis)
.addUseCase(imageCapture)
.setViewPort(viewPort)
.build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)
3、选择摄像头
CameraX会自动选择最佳摄像头设备。可以使用?CameraSelector.DEFAULT_FRONT_CAMERA?设置默认为前置摄像头,使用CameraSelector.DEFAULT_BACK_CAMERA?设置默认为后置摄像头。另外,可以通过CameraSelector来动态切换摄像头。
4、相机分辨率
CameraX会默认选择最佳分辨率,并且默认宽高比为4:3。也可以通过如下方法指定分辨率:
setTargetResolution(Size resolution);
5、?变焦与曝光
CameraControl提供变焦、对焦、手电筒、曝光补偿等控制。按照变焦比例进行变焦setZoomRatio(),按照变焦系数进行变焦setLinearZoom()。对焦的方法如下:
CameraControl.startFocusAndMetering();
?当应用需要对自动曝光 (AE) 输出结果以外的曝光值 (EV) 进行微调时,曝光补偿很有用。CameraX 将按以下方式组合曝光补偿值,以确定当前图像条件下所需的曝光:
Exposure = ExposureCompensationIndex * ExposureCompensationStep
设置曝光补偿索引值?的方法如下:
Camera.CameraControl.setExposureCompensationIndex();
完整的CameraX介绍与参数配置,请查看文档:CameraX官方文档?
三、摄像头预览
1、使用PreviewView布局
CameraX配合PreviewView控件进行预览,布局示例如下:
<FrameLayout
android:id="@+id/root">
<androidx.camera.view.PreviewView
android:id="@+id/preview_view" />
</FrameLayout>
2、请求CameraProvider
在onCreate生命周期时,进行请求:
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
3、检查CameraProvider可用性
得到CameraProvider后,检查其是否可用:
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))
4、选择相机并绑定生命周期
?以选择后置摄像头为例,示例代码如下:
fun bindPreview(cameraProvider : ProcessCameraProvider) {
// 1、创建Preview
var preview : Preview = Preview.Builder().build()
// 2、选择后置摄像头
var cameraSelector : CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
// 3、preview连接到previewView
preview.setSurfaceProvider(previewView.getSurfaceProvider())
// 4、绑定生命周期
var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)
}
5、监听相机状态
相机状态包括:准备打开、打开中、已打开、关闭中、已关闭。而错误状态包括:流配置出错、相机在使用中、已达到最大数量、相机关闭等等。示例代码如下:
private fun observeCameraState(cameraInfo: CameraInfo) {
cameraInfo.cameraState.observe(viewLifecycleOwner) { cameraState ->
run {
when (cameraState.type) {
CameraState.Type.PENDING_OPEN -> {
Toast.makeText(context, "CameraState: Pending Open", Toast.LENGTH_SHORT).show()
}
CameraState.Type.OPENING -> {
Toast.makeText(context, "CameraState: Opening", Toast.LENGTH_SHORT).show()
}
CameraState.Type.OPEN -> {
Toast.makeText(context, "CameraState: Open", Toast.LENGTH_SHORT).show()
}
CameraState.Type.CLOSING -> {
Toast.makeText(context, "CameraState: Closing", Toast.LENGTH_SHORT).show()
}
CameraState.Type.CLOSED -> {
Toast.makeText(context, "CameraState: Closed", Toast.LENGTH_SHORT).show()
}
}
}
cameraState.error?.let { error ->
when (error.code) {
CameraState.ERROR_STREAM_CONFIG -> {
Log.e(TAG, "Stream config error")
}
CameraState.ERROR_CAMERA_IN_USE -> {
Log.e(TAG, "Camera is in using")
}
CameraState.ERROR_MAX_CAMERAS_IN_USE -> {
Log.e(TAG, "Max cameras in use")
}
CameraState.ERROR_CAMERA_DISABLED -> {
Log.e(TAG, "Camera disabled")
}
}
}
}
}
四、拍照
1、拍照接口
CameraX的拍照提供自动白平衡、自动曝光和自动对焦 (3A) 功能。使用takePicture进行拍照:
2、拍照模式
拍照时使用闪光灯选项和连续自动对焦进行拍摄。如需缩短照片拍摄的延迟时间,我们可以把?ImageCapture.CaptureMode?设置为?CAPTURE_MODE_MINIMIZE_LATENCY。如需优化照片质量,则设置为?CAPTURE_MODE_MAXIMIZE_QUALITY。
3、拍照实例并绑定生命周期
创建ImageCapture对象实例,并且绑定到生命周期:
val imageCapture = ImageCapture.Builder()
.setTargetRotation(view.display.rotation)
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageCapture,
imageAnalysis, preview)
4、拍照保存
以拍照保存为例,相关代码如下:
fun doTakePicture(photoFile: File) {
imageCapture?.let { imageCapture ->
val metadata = Metadata().apply {
// 前置摄像头需要水平镜像
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}
// 指定保存图片路径,并设置metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build()
// 开始拍照,监听拍照结果回调
imageCapture.takePicture(
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture error: ${exc.message}")
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
// 发送广播通知更新相册
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
requireActivity().sendBroadcast(
Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)
)
}
// 扫描媒体库
val mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(savedUri.toFile().extension)
MediaScannerConnection.scanFile(context,
arrayOf(savedUri.toFile().absolutePath),
arrayOf(mimeType)
) { _, uri ->
Log.d(TAG, "Photo scanned into media store: $uri")
}
}
})
}
}
五、录像
1、配置录像参数
CameraX提供VideoCapture进行录像,配置步骤:选择摄像头、选择分辨率、连接PreviewView、创建videoCapture对象、绑定生命周期。示例代码如下:
private suspend fun bindCaptureUsecase() {
val cameraProvider = ProcessCameraProvider.getInstance(requireContext()).await()
// 选择前置/后置摄像头
val cameraSelector = getCameraSelector(cameraIndex)
// 选择分辨率
val quality = cameraCapabilities[cameraIndex].qualities[qualityIndex]
val qualitySelector = QualitySelector.from(quality)
// 连接preview到previewView
val preview = Preview.Builder()
.setTargetAspectRatio(quality.getAspectRatio(quality))
.build()
preview.setSurfaceProvider(captureViewBinding.previewView.surfaceProvider)
// 创建videoCapture对象
val recorder = Recorder.Builder()
.setQualitySelector(qualitySelector)
.build()
videoCapture = VideoCapture.withOutput(recorder)
try {
// 绑定生命周期
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, videoCapture, preview)
} catch (e: Exception) {
Log.e(TAG, "VideoCapture binding failed:$e")
}
}
2、开始录像
先调用VideoCapture的prepareRecording()准备就绪,然后调用start()开始。示例代码如下:
private fun startRecording() {
// 设置存储路径与名称
val name = "hello.mp4"
val contentValues = ContentValues().apply {
put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
requireActivity().contentResolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build()
// 准备与开始录像
currentRecording = videoCapture.output
.prepareRecording(requireActivity(), mediaStoreOutput)
.apply { if (audioEnabled) withAudioEnabled() }
.start(mainThreadExecutor, captureListener)
}
六、图像分析
1、创建图像分析器
使用ImageAnalysis创建图像分析器,设置图像宽高比、旋转角度,然后设置自定义分析器,最终绑定生命周期,示例代码如下:
// 构建图像分析器
imageAnalyzer = ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// 设置亮度分析器
imageAnalyzer?.setAnalyzer(cameraExecutor, LuminanceAnalyzer { luma ->
Log.d(TAG, "Average luminance: $luma")
})
// 绑定生命周期
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
2、自定义分析器
自定义的图像亮度分析器,继承ImageAnalysis.Analyzer,示例代码如下:
?
private class LuminosityAnalyzer(listener: LumaListener? = null) : ImageAnalysis.Analyzer {
private val listeners = ArrayList<LumaListener>().apply { listener?.let { add(it) } }
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
override fun analyze(image: ImageProxy) {
if (listeners.isEmpty()) {
image.close()
return
}
// 取出luminance plane,即Y分量
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
// 转成像素数组,取值范围0-255
val pixels = data.map { it.toInt() and 0xFF }
// 计算平均亮度
val luma = pixels.average()
// 结果回调
listeners.forEach { it(luma) }
// 关闭image
image.close()
}
}
七、扩展API
CameraX提供Extensions API接口,用于实现如下特效:
- 自动切换:根据周围场景自动调整最终图片。例如,扩展库会执行弱光检测,切换到弱光模式或 HDR 模式以拍摄照片;
- 焦外成像:焦外成像模式会使前景人物看起来更加清晰,并对照片背景进行模糊处理;
- 脸部修复:拍摄静态图片时修复脸部肤色;
- HDR:HDR 模式会在黑暗环境凸显亮度,提升画质;
- 夜间模式:在光线较暗的情况下(通常是在夜间)拍摄最佳的静态图片;
扩展库的框架图如下:
?
初始化扩展库过程如下:
fun initExtension() {
// 创建扩展管理器
val extensionsManager = ExtensionsManager.getInstanceAsync(context, cameraProvider).await()
// 选择摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 检测扩展库是否可用
if (extensionsManager.isExtensionAvailable(cameraSelector, ExtensionMode.BOKEH)) {
cameraProvider.unbindAll()
val bokehCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
cameraSelector, ExtensionMode.BOKEH)
// 创建ImageCapture和Preview实例
val imageCapture = ImageCapture.Builder().build()
val preview = Preview.Builder().build()
// 绑定生命周期
cameraProvider.bindToLifecycle(lifecycleOwner, bokehCameraSelector, imageCapture, preview)
}
}
至此,Android多媒体CameraX的相机预览、拍照、录像、图像分析、扩展库介绍完毕。
|