安卓 Jetpack 系列之 CameraX
1.添加依赖
打开项目的 settings.gradle
文件并添加 google()
代码库,如下所示:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
将以下内容添加到 Android 代码块的末尾:
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
}
将以下内容添加到应用的每个模块的 build.gradle
文件中:
dependencies {
// CameraX core library using the camera2 implementation
val camerax_version = "1.2.0-alpha02"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
// If you want to additionally use the CameraX Lifecycle library
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
// If you want to additionally use the CameraX VideoCapture library
implementation("androidx.camera:camera-video:${camerax_version}")
// If you want to additionally use the CameraX View class
implementation("androidx.camera:camera-view:${camerax_version}")
// If you want to additionally add CameraX ML Kit Vision Integration
implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
// If you want to additionally use the CameraX Extensions library
implementation("androidx.camera:camera-extensions:${camerax_version}")
}
2.配置
内容太多,有需要请看原文
2.1 CameraXConfig
CameraXConfig 需要和Application一起使用
class CameraApplication : Application(), CameraXConfig.Provider {
override fun getCameraXConfig(): CameraXConfig {
return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setMinimumLoggingLevel(Log.ERROR).build()
}
}
CameraXConfig可以配置以下内容
2.1.1 摄像头限制器
限制应用只能使用设备的某个摄像头
2.1.2 线程
CameraXConfig
让应用可通过 CameraXConfig.Builder.setCameraExecutor()
和 CameraXConfig.Builder.setSchedulerHandler()
设置将会使用的后台线程。
2.1.3 摄像头执行器
摄像头执行器用于所有内部摄像头平台 API 调用,以及来自这些 API 的回调。CameraX 会分配和管理内部 Executor
来执行这些任务。但是,如果您的应用需要更严格的线程控制,请使用 CameraXConfig.Builder.setCameraExecutor()
。
2.1.4 调度器处理程序
调度器处理程序用于按固定的时间间隔调度内部任务,例如在摄像头不可用时再次尝试打开该摄像头。该处理程序不执行作业,而是仅将作业分派给摄像头执行器。有时,该处理程序还用于需要 Handler
进行回调的旧版 API 平台上。在这些情况下,回调仍仅直接分派给摄像头执行器。CameraX 会分配和管理内部 HandlerThread
来执行这些任务,但您可以将其替换为 CameraXConfig.Builder.setSchedulerHandler()
。
2.1.5 日志记录
CameraXConfig.Builder.setMinimumLoggingLevel(int)
为您的应用设置适当的日志记录级别。
2.2 自动选择
CameraX 会根据运行您的应用的设备自动提供专用的功能。例如,如果您未指定分辨率或指定的分辨率不受支持,CameraX 会自动确定要使用的最佳分辨率。所有这些操作均由库进行处理,无需您编写设备专属代码。
CameraX 的目标是成功初始化相机会话。这意味着 CameraX 会根据设备功能降低分辨率和宽高比。降低的原因可能是:
- 设备不支持请求的分辨率。
- 设备存在兼容性问题,例如需要特定分辨率才能正常运行的旧设备。
- 在某些设备上,特定格式仅适用于特定宽高比。
- 对于 JPEG 或视频编码,设备首选“最近的 mod16”。如需了解详情,请参阅
SCALER_STREAM_CONFIGURATION_MAP
。
尽管 CameraX 会创建并管理会话,但您应始终在代码中检查用例输出所返回的图像大小,并进行相应调整。
2.3 旋转
设置相机的旋转角度
2.4 剪裁矩形
设置视口,CameraX 可保证一个组中的所有用例的剪裁矩形都指向摄像头传感器中的同一个区域
2.5 选择摄像头
获取所有可用的摄像头,并选择相应用的摄像头
2.6 相机分辨率
您可以选择允许 CameraX 根据设备功能、设备支持的硬件级别、用例和所提供的宽高比组合设置图片分辨率。或者,您也可以在支持相应配置的用例中设置特定目标分辨率或特定宽高比。
2.6.1 自动分辨率
2.6.2 指定分辨率
2.7 控制相机输出
CameraX 不仅让您可以视需要为每个单独的用例配置相机输出,还实现了以下接口,从而支持所有绑定用例中常见的相机操作:
- 利用
CameraControl
,您可以配置常用的相机功能。 - 利用
CameraInfo
,您可以查询这些常用相机功能的状态。
以下是 CameraControl 支持的相机功能:
- 变焦
- 手电筒
- 对焦和测光(点按即可对焦)
- 曝光补偿
3.实现预览
3.1.1请求Camera权限
最重要的事情,先申请CAMERA
权限
val activityResultLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
//具体情况具体处理
if (result.all { it.value }) {
//是否授权
initCameraProvider()
} else {
ToastUtils.showLong("需要一些必要的权限才能正常使用功能")
}
}
activityResultLauncher.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
)
3.1.2 将 PreviewView 添加到布局
<FrameLayout
android:id="@+id/container">
<androidx.camera.view.PreviewView
android:id="@+id/previewView" />
</FrameLayout>
3.1.3 请求 CameraProvider
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.common.util.concurrent.ListenableFuture
class MainActivity : AppCompatActivity() {
private lateinit var cameraProviderFuture : ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
}, ContextCompat.getMainExecutor(requireContext()))
}
}
判断是否有前后摄像头
private fun hasBackCamera(): Boolean {
return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}
private fun hasFrontCamera(): Boolean {
return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
}
绑定摄像头实现预览
val rotation = fragmentCameraBinding.viewFinder.display.rotation
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
preview = Preview.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation)
.build()
//绑定前先解绑
cameraProvider.unbindAll()
//绑定到LifecycleOwner
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(fragmentCameraBinding.viewFinder.surfaceProvider)
观察cameraState
camera?.cameraInfo?.cameraState?.observe(viewLifecycleOwner) { cameraState ->
//摄像头是否打开,或者是否打开失败
}
camera?.cameraInfo?.torchState?.observe(viewLifecycleOwner) {
//摄像头灯光状态
}
camera?.cameraInfo?.zoomState?.observe(viewLifecycleOwner) {
//摄像头缩放状态
}
3.1.4 如果机器有三个摄像头怎么处理
CameraSelector
类里定义了前后摄像头相关的常量LENS_FACING_FRONT
LENS_FACING_BACK
DEFAULT_FRONT_CAMERA
DEFAULT_BACK_CAMERA
public final class CameraSelector {
/** A camera on the device facing the same direction as the device's screen. */
public static final int LENS_FACING_FRONT = 0;
/** A camera on the device facing the opposite direction as the device's screen. */
public static final int LENS_FACING_BACK = 1;
/** A static {@link CameraSelector} that selects the default front facing camera. */
@NonNull
public static final CameraSelector DEFAULT_FRONT_CAMERA =
new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build();
/** A static {@link CameraSelector} that selects the default back facing camera. */
@NonNull
public static final CameraSelector DEFAULT_BACK_CAMERA =
new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();
}
但有些机器会有三个摄像头,前彩色/后彩色/前黑白 三个摄像头。
所以就不能用new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT/LENS_FACING_BACK).build()
来选择第三个摄像头了
处理方式
1.判断是否有摄像头
cameraManager = requireContext().getSystemService(Context.CAMERA_SERVICE) as CameraManager
packageManager = requireContext().packageManager
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
return
}
2.判断有多少个摄像头
cameraProvider.availableCameraInfos
3.根据cameraProvider.availableCameraInfos
里的cameraSelector
创建新的CameraSelector
var cameraSelector: CameraSelector = CameraSelector.Builder.fromSelector(cameraProvider.availableCameraInfos[currentCameraSelectorIndex].cameraSelector).build()
Preview.Builder()的一些配置
setTargetAspectRatio 设定目标长宽比 RATIO_4_3 RATIO_16_9之间选择
setTargetRotation 设置目标旋转 ROTATION_0,ROTATION_90,ROTATION_180,ROTATION_270
setDefaultResolution 设置默认的分辨率
setMaxResolution 设置最大分辨率
setSupportedResolutions 设置支持的分辨率
setBackgroundExecutor 设置将用于后台任务的默认执行程序。
setDefaultSessionConfig() 设置默认会话配置
setDefaultCaptureConfig 设置默认捕获配置
setSessionOptionUnpacker 设置会话选项Unpacker 解包器
setCaptureOptionUnpacker
setSurfaceOccupancyPriority 设置SurfaceOccupancy优先级
setCameraSelector 设置 CameraSelector
setUseCaseEventCallback
setIsRgba8888SurfaceRequired
setImageInfoProcessor
setCaptureProcessor
setZslDisabled
3.1.5 处理摄像头旋转镜像等问题
使用preview?.targetRotation
设置摄像头画面旋转
dataBinding?.rotation?.setOnClickListener {
ToastUtils.showShort("${preview?.targetRotation}")
val cur = preview?.targetRotation
LogUtils.d("cur preview?.targetRotation:${preview?.targetRotation}")
preview?.targetRotation = when (cur) {
Surface.ROTATION_0 -> Surface.ROTATION_90
Surface.ROTATION_90 -> Surface.ROTATION_180
Surface.ROTATION_180 -> Surface.ROTATION_270
Surface.ROTATION_270 -> Surface.ROTATION_0
else -> Surface.ROTATION_0
}
}
PreviewView
会根据机器,自动选择预览的view是使用 SurfaceView
还是 TextureView
如果是TextureView
可以这样处理 左右镜像,上下镜像
//左右镜像
fun TextureView.transformLeftRightMirror() {
val matrix = this.getTransform(Matrix())
matrix.postScale(-1f, 1f)
matrix.postTranslate(this.width.toFloat(),0f)
this.setTransform(matrix)
}
//上下镜像
fun TextureView.transformTopBottomMirror() {
val matrix = this.getTransform(Matrix())
matrix.postScale(1f, -1f)
matrix.postTranslate(0f, this.height.toFloat())
this.setTransform(matrix)
}
3.16 摄像头控制
灯光开关
cameraControl?.enableTorch(cameraInfo?.torchState?.value == 0)
画面剪切
可设置范围
val maxZoomRatio = cameraInfo?.zoomState?.value?.maxZoomRatio ?: 0f
val minZoomRatio = cameraInfo?.zoomState?.value?.minZoomRatio ?: 0f
//maxZoomRatio等于minZoomRatio时,相当于不能调整
cameraControl?.setZoomRatio(value)
可设置范围 0~1
cameraControl?.setLinearZoom(value / 100.0f)
设定曝光补偿指数
可设置范围
val valueFrom = cameraInfo?.exposureState?.exposureCompensationRange?.lower?.toFloat() ?: 0f
val valueTo = cameraInfo?.exposureState?.exposureCompensationRange?.upper?.toFloat() ?: 1f
//相等时,相当于不能调整
cameraControl?.setExposureCompensationIndex(value.toInt())
对焦和测光
previewView?.setOnTouchListener { v, event ->
if (camera == null) {
return@setOnTouchListener true
}
if (event.action != MotionEvent.ACTION_UP) {
return@setOnTouchListener true
}
val meteringPoint = dataBinding?.previewView?.meteringPointFactory?.createPoint(event.x, event.y)
?: return@setOnTouchListener true
LogUtils.d("meteringPoint:$meteringPoint")
val action = FocusMeteringAction.Builder(meteringPoint)
.setAutoCancelDuration(3, TimeUnit.SECONDS)
.build()
val isFocusMeteringSupported = camera?.cameraInfo?.isFocusMeteringSupported(action) ?: false
LogUtils.d("meteringPoint:$meteringPoint")
LogUtils.d("isFocusMeteringSupported:$isFocusMeteringSupported")
if (!isFocusMeteringSupported) {
return@setOnTouchListener false
}
LogUtils.d("isStartFocusAndMetering:${isStartFocusAndMetering}")
if (isStartFocusAndMetering) {
return@setOnTouchListener true
}
isStartFocusAndMetering = true
val result = cameraControl?.startFocusAndMetering(action)
result?.addListener({
try {
isStartFocusAndMetering = false
val r = result?.get()
LogUtils.d("isFocusSuccessful:${r.isFocusSuccessful}")
LogUtils.d("startFocusAndMetering result:${r}")
} catch (e: Exception) {
e.printStackTrace()
}
}, ContextCompat.getMainExecutor(requireContext()))
return@setOnTouchListener true
}
4.分析图片
这一个功能就是会把摄像头的每一帧画面获取到,并加以处理。
操作模式
当应用的分析流水线无法满足 CameraX 的帧速率要求时,您可以将 CameraX 配置为通过以下其中一种方式丢帧:
- 非阻塞(默认):在该模式下,执行器始终会将最新的图像缓存到图像缓冲区(与深度为 1 的队列相似),与此同时,应用会分析上一个图像。如果 CameraX 在应用完成处理之前收到新图像,则新图像会保存到同一缓冲区,并覆盖上一个图像。 请注意,在这种情况下,
ImageAnalysis.Builder.setImageQueueDepth()
不起任何作用,缓冲区内容始终会被覆盖。您可以通过使用STRATEGY_KEEP_ONLY_LATEST
调用setBackpressureStrategy()
来启用该非阻塞模式。如需详细了解执行器的相关影响,请参阅STRATEGY_KEEP_ONLY_LATEST
的参考文档。 - 阻塞:在该模式下,内部执行器可以向内部图像队列添加多个图像,并仅在队列已满时才开始丢帧。系统会在整个相机设备上进行屏蔽:如果相机设备具有多个绑定用例,那么在 CameraX 处理这些图像时,系统会屏蔽所有这些用例。例如,如果预览和图像分析都已绑定到某个相机设备,那么在 CameraX 处理图像时,系统也会屏蔽相应预览。您可以通过将
STRATEGY_BLOCK_PRODUCER
传递到setBackpressureStrategy()
来启用阻塞模式。此外,您还可以通过使用 ImageAnalysis.Builder.setImageQueueDepth() 来配置图像队列深度。
val imageAnalysis = ImageAnalysis.Builder()
// enable the following line if RGBA output is needed.
//ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888
// .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
//可以获取到图片的宽高,格式等,
// insert your code here.
...
//如果直接配置成 OUTPUT_IMAGE_FORMAT_RGBA_8888格式,可以把数据转成bitmap显示到ImageView上,
//OUTPUT_IMAGE_FORMAT_YUV_420_888也可以转成bitmap只是麻烦一点
if(imageProxy.planes.isNotEmpty()) {
var bitmap = Bitmap.createBitmap(imageProxy.width, imageProxy.height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(imageProxy.planes[0].buffer)
//把bitmap显示到ImageView上
showImageAnalysisImage(bitmap)
}
// after done, release the ImageProxy object
//如果不close代表一帧没有处理完,就不会开始处理下一帧。
imageProxy.close()
})
cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)
5.图片拍摄
val imageCapture = ImageCapture.Builder()
//CAPTURE_MODE_MAXIMIZE_QUALITY
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetRotation(view.display.rotation)
.build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageCapture,
imageAnalysis, preview)
fun onClick() {
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(File(...)).build()
imageCapture.takePicture(outputFileOptions, cameraExecutor,
object : ImageCapture.OnImageSavedCallback {
override fun onError(error: ImageCaptureException)
{
// insert your code here.
}
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
// insert your code here.
}
})
}
6.Extensions API 供应商扩展
CameraX 提供了一个 Extensions API,用于使用制造商在各种 Android 设备(手机、平板电脑等)上实现的特效
- 自动:根据周围场景自动调整最终图片。例如,供应商库实现可能会执行弱光检测,并且可以切换到弱光模式或 HDR 模式以拍摄照片,库也可能会在拍摄肖像照时自动应用脸部照片修复模式。自动模式会根据供应商库实现选择模式。
- 焦外成像:焦外成像模式会使前景人物看起来更加清晰,并对照片背景进行模糊处理。它通常用于拍摄与大镜头相机所生成的照片类似的人物肖像。
- 脸部照片修复:拍摄静态图片时修复脸部肤色、几何图形等。
- HDR(高动态范围):HDR 模式会拍摄可在最终图片中呈现较大场景亮度范围的照片。例如,在明亮窗户前拍摄某个物体的照片时,使用 HDR 模式可让该物体和窗外的场景都处于可见状态,而在正常模式下,则无法让这两者都呈现良好的曝光效果。但代价是,HDR 模式通常需要更长的时间来拍摄单张图片,用户无法控制,并且可能还需要其他工件,具体取决于所使用的 HDR 方法。
- 夜间:在光线较暗的情况下(通常是在夜间)拍摄最佳的静态图片。
相应的特效类定义在
@RequiresApi(21)
public final class ExtensionMode {
public static final int NONE = 0;
public static final int BOKEH = 1;
public static final int HDR = 2;
public static final int NIGHT = 3;
public static final int FACE_RETOUCH = 4;
public static final int AUTO = 5;
@IntDef({NONE, BOKEH, HDR, NIGHT, FACE_RETOUCH, AUTO})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public @interface Mode {
}
private ExtensionMode() {
}
}
使用
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
fun onCreate() {
// Create a camera provider
val cameraProvider : ProcessCameraProvider = ... // Get the provider instance
lifecycleScope.launch {
// Create an extensions manager
val extensionsManager =
ExtensionsManager.getInstanceAsync(context, cameraProvider).await()
// Select the camera
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// Query if extension is available.
if (extensionsManager.isExtensionAvailable(
cameraSelector,
ExtensionMode.BOKEH
)
) {
// Unbind all use cases before enabling different extension modes.
cameraProvider.unbindAll()
// Retrieve extension enabled camera selector
val bokehCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
cameraSelector,
ExtensionMode.BOKEH
)
// Bind image capture and preview use cases with the extension enabled camera selector.
val imageCapture = ImageCapture.Builder().build()
val preview = Preview.Builder().build()
cameraProvider.bindToLifecycle(
lifecycleOwner,
bokehCameraSelector,
imageCapture,
preview
)
}
}
}