Jetpack - CameraX

官方介绍

参考文章

一、概念

CameraX 解决了由于碎片化问题在不同机型上保持一致性体验的麻烦, 只需要关心 API 调用,不需要对不同机型做适配。CameraX 把不同的功能,如 预览、拍照等功能封装成不同的 UseCase,这样就可以根据业务的需要,来绑定不同的 UseCase 来实现具体的功能。Camera2 库是更底层开发用到的,它的上一代 Camera 库已废弃。

PreviewView 控件一种可以剪裁、缩放和旋转以确保正确显示的 View,当相机处于活动状态时,图片预览会流式传输到 PreviewView 中的 Surface。
CameraSelector选择或切换镜头(切换需要重新绑定)。
UserCasePreview 预览接收用于预览的 Surface,如 PreviewView。
ImageCapture 图片拍摄拍摄并保存图片,可以设置大小、曝光度。
VideoCapture 视频拍摄拍摄视频和音频。
ImageAnalysis 分析提供可访问的缓冲区,用来图像识别(人脸、二维码)、机器学习。

1.1 需求划分

Intent

基本拍摄

使用 Intent 方式跳转自带相机来拍照/录像,详见Activity Result API

CameraController

简单使用

在单个类中提供大多数 CameraX 核心功能。它只需少量设置代码,并且可自动处理相机初始化、用例管理、目标旋转、点按对焦、双指张合缩放等操作。扩展 CameraController 的具体类为 LifecycleCameraController。

CameraProvider

进阶使用

Intent 代码举例

class MainActivity : AppCompatActivity() {
    val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->
        //TODO...
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        takePictureLauncher.launch(null)
    }
}

CameraController 代码举例

private fun initCameraX() {
    previewView = binding.previewView
    cameraController = LifecycleCameraController(this)
    cameraController.bindToLifecycle(this)
    cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    previewView.controller = cameraController
    //其它可配置项详见 CameraProvider 的 ImageCapture,举例2个
    cameraController.imageCaptureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
    cameraController.imageCaptureFlashMode = ImageCapture.FLASH_MODE_AUTO
}

fun takePhoto() {
    //切换模式
    cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE or CameraController.IMAGE_ANALYSIS)
    //后续API调用步骤详见 CameraController 的 ImageCapture,一样的。
}
fun startRecording() {
    //切换模式
    cameraController.setEnabledUseCases(CameraController.VIDEO_CAPTURE)
}

二、初始化

2.1 添加依赖

最新版本

def camerax_version = "1.4.0-beta02"
implementation "androidx.camera:camera-camera2:$camerax_version"
//避免手动在生命周期释放和销毁数据
implementation "androidx.camera:camera-lifecycle:$camerax_version"
//用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现
implementation "androidx.camera:camera-view:$camerax_version"
//可实现人像、HDR、夜间和美颜、滤镜但依赖于OEM
implementation "androidx.camera:camera-extensions:$camerax_version"

2.2 权限声明

//any可以使用设备上的任何一个摄像头,确保设备配有相机。
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="28" />

3.3 日志打印(可选配置)

class APP : Application(), CameraXConfig.Provider {
    override fun getCameraXConfig(): CameraXConfig {
        return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
            .setMinimumLoggingLevel(Log.ERROR)
            .build()
    }
}

三、预览 Preview

  1. 将 PreviewView 添加到布局中。
  2. 获取 CameraProvider 并检查可用性。
  3. 创建 CameraSelector 选择镜头。
  4. 创建 Preview 用例并连接到 PreviewView。
  5. 绑定用例和生命周期。
<androidx.camera.view.PreviewView
	android:id="@+id/previewView"
	android:layout_width="match_parent"
	android:layout_height="match_parent"/>
private fun startCamera() {
    //获取实例
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    //添加监听器,摄像头准备好后执行(监听CameraX初始化完成)
    //传入一个Runnable和一个主线程运行的Executor
    cameraProviderFuture.addListener({
        val cameraProvider = cameraProviderFuture.get() //用来绑定生命周期
        //设置CameraSelector(或直接赋值CameraSelector.DEFAULT_BACK_CAMERA)
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
        //设置Preview用例
        val preview = Preview.Builder().build().apply {
                surfaceProvider = previewView.surfaceProvider  //设置预览的控件,previewView是xml中的PreviewView控件的id
            }
        try {
            //重新绑定前先解绑所有用例
            //取消当前已经绑定的摄像头设备,释放它们的资源,以便其他应用或者进程可以使用这些摄像头设备。
            //这个方法通常在摄像头应用程序退出或者暂停时调用,以确保摄像头设备不会一直占用系统资源。
            cameraProvider.unbindAll()
            //与当前生命周期绑定,以便在应用程序暂停或停止时自动释放相机资源
            //传入 LifecycleOwner、CameraSelector、以及N个用例(UserCase)
            //返回一个Camera对象,进而可以获取CameraInfo(用来查询信息状态)和CameraControl(用来对所有输出结果产生统一影响的操作)对象
            val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
        } catch (e: Exception) {
            Log.e("CameraX", "绑定用例失败", e)
        }
    }, mainExecutor)    //或写成ContextCompat.getMainExecutor(this)
}

3.1 帧率

setTargetFrameRate()

public Builder setTargetFrameRate(Range<Integer> targetFrameRate)

取值范围:30-60FPS。

val preview = Preview.Builder()
    .setTargetFrameRate(Range(30, 60)) //帧率范围:30-60 FPS
    .build()

3.2 旋转方向

默认情况下,摄像头的旋转角度会设置为与显示屏旋转角度保持一致,来确保生成的输出与预览中看到的一致。

setTargetRotation()

public Builder setTargetRotation(int rotation)

CameraX 会自动处理设备旋转,也可以手动调整。

监听方式一:旋转后会崩溃报错调用方法的对象为null。20251016

val preview = Preview.Builder()
    .setTargetRotation(previewView.display.rotation) // 根据屏幕方向设置旋转
    .build()
//在 Activity 中监听设备旋转并更新预览方向
override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    updatePreviewRotation()
}

private fun updatePreviewRotation() {
    val rotation = previewView.display.rotation
    preview.targetRotation = rotation
}

监听方式二

override fun onCreate(savedInstanceState: Bundle?) {
    val orientationEventListener = object : OrientationEventListener(this) {
        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
            preview.targetRotation = rotation
        }
    }
    orientationEventListener.enable() //记得在onDestroy()中关闭
}

3.3 宽高比(已过时)

setTargetAspectRatio()

public Builder setTargetAspectRatio(int aspectRatio)

RATIO_DEFAULT:自适应(默认)

RATIO_16_9:十六比九。

RATIO_4_3:四比三。

val preview = Preview.Builder()
    .setTargetAspectRatio(AspectRatio.RATIO_16_9) // 设置 16:9 宽高比
    .build()

3.4 预览分辨率(已过时)

setTargetResolution()

public Builder setTargetResolution(Size resolution)

CameraX 会自动选择最接近的分辨率,,也可以手动设置。

val preview = Preview.Builder()
    .setTargetResolution(Size(1280, 720)) //预览分辨率
    .build()

四、拍照 ImageCapture

  1. 创建 ImageCapture 用例,抽取成全局变量并绑定用例。
  2. 创建 OutputFileOptions,指定文件存储路径(MediaStore、File、OutputStream)。
  3. 调用 takePicture() 函数并处理回调。
//抽取成成员变量
private var imageCapture: ImageCapture? = null
//同Preview一样在cameraProviderFuture中添加用例并绑定到生命周期
private fun startCamera() {
    imageCapture = ImageCapture.Builder().build()
    val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
}
private fun takePhoto() {
    val imageCapture = imageCapture ?: return
    //存储路径和参数
    val time = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(System.currentTimeMillis())
    val fileName = "CameraX_${time}"    //这里不用加格式后缀
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")    //指定文件格式会自动加后缀
        //Android10以后可以指定相对目录(否则默认保存在Picture根目录)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/CameraX")
        } 
    }
    //保存在 MediaStore 中,以便其他应用可以显示它
    //重载还可以使用 File 或 OutputStream 的方式输出
    val outputOptions = ImageCapture.OutputFileOptions.Builder(
        contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
    ).build()
    //拍照后的回调函数
    imageCapture.takePicture(outputOptions, mainExecutor, object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            //可以拿到Uri
            val uri = outputFileResults.savedUri
        }
        override fun onError(exception: ImageCaptureException) {
            TODO("Not yet implemented")
        }
    })
}
//举例使用File保存,但不会被添加到媒体数据库中,需使用 MediaScannerConnection.scanFile()
val file = File(getExternalFilesDir(Environment.DIRECTORY_DCIM), fileName)
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()

4.1 拍摄模式

setCaptureMode()

public Builder setCaptureMode(int captureMode)

CAPTURE_MODE_MINIMIZE_LATENCY:优先降低拍照延迟。

CAPTURE_MODE_MAXIMIZE_QUALITY:优先提高照片质量。

CAPTURE_MODE_ZERO_SHUTTER_LAG:更低延迟。

val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

4.2 闪光灯模式

setFlashMode()

public Builder setFlashMode(int flashMode)

FLASH_MODE_OFF:关闭闪光灯。

FLASH_MODE_ON:开启闪光灯。

FLASH_MODE_AUTO:根据环境光线自动决定是否开启闪光灯。

val imageCapture = ImageCapture.Builder()
    .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
    .build()
//也可以动态修改
imageCapture.flashMode = ImageCapture.FLASH_MODE_ON

4.3 输出分辨率(已过时)

setTargetResolution()

public Builder setTargetResolution(Size resolutio)

可以控制输出照片的尺寸。

4.4 宽高比(已过时)

setTargetAspectRatio()

public Builder setTargetAspectRatio(int aspectRatio)

不同设备的摄像头支持的宽高比可能不同,使用 RATIO_DEFAULT 可以自动选择最适合当前设备的宽高比。

RATIO_DEFAULT:自适应(默认)

RATIO_16_9:十六比九。

RATIO_4_3:四比三。

val imageCapture = ImageCapture.Builder()
    .setTargetAspectRatio(AspectRatio.RATIO_DEFAULT)
    .build()
//获取设备默认宽高比
cameraProviderFuture.addListener({
    val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
    val cameraInfo = camera.cameraInfo
    val supportedAspectRatios = cameraInfo.supportedAspectRatios
    if (supportedAspectRatios.contains(AspectRatio.RATIO_4_3)) {
        Log.d("CameraX", "设备支持 4:3 宽高比")
    }
    if (supportedAspectRatios.contains(AspectRatio.RATIO_16_9)) {
        Log.d("CameraX", "设备支持 16:9 宽高比")
    }
}, ContextCompat.getMainExecutor(this))

4.5 旋转方向

setTargetRotation()

public Builder setTargetRotation(int rotation)

val imageCapture = ImageCapture.Builder()
    .setTargetRotation(previewView.display.rotation) // 根据屏幕方向设置旋转
    .build()

4.6 JEPG质量

setJpegQuality()

public Builder setJpegQuality(int jpegQuality)

取值范围:1-100。

val imageCapture = ImageCapture.Builder()
    .setJpegQuality(90) //取值范围1-100
    .build()

五、录像 VideoCapture

Recoder是与 VideoCapture 紧密耦合的 VideoOutput 实现。 Recorder 用于执行视频和音频捕获操作。应用通过 Recorder 创建录制对象。
PendingRecording 会配置录制对象,同时提供启用音频和设置事件监听器等选项。您必须使用 Recorder 来创建 PendingRecording。PendingRecording 不会录制任何内容。
Recording会执行实际录制操作。您必须使用 PendingRecording 来创建 Recording。
//抽取成成员变量
private var videoCapture: VideoCapture<Recorder>? = null
//同Preview一样在cameraProviderFuture中添加用例并绑定到生命周期
private fun startCamera() {
    val recorder = Recorder.Builder()
        .setQualitySelector(QualitySelector.from(Quality.FHD))
        .build()
    videoCapture = VideoCapture.withOutput(recorder)
    val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, videoCapture)
}
//抽取成成员变量
private var recoding: Recording? = null

fun recoding(context: Context) {
    val videoCapture = videoCapture ?: return
    //正在录制就停止
    recoding?.let { 
        it.stop()
        recoding = null
    }

    val contentResolver = context.contentResolver
    val time = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(System.currentTimeMillis())
    val fileName = "CameraX_${time}"
    val contentValues = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
        put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
    }
    val outputOptions = MediaStoreOutputOptions.Builder(
        contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    ).setContentValues(contentValues).build()

    recoding = videoCapture.output
        .prepareRecording(context, outputOptions)
        .withAudioEnabled()
        .start(context.mainExecutor) { event ->

        }
}

六、分析 ImageAnalysis

七、Camera

CameraProvider 绑定生命周期时会返回一个 Camera 对象,进而获取 CameraInfo 和 CameraControl 对象。

val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
val cameraInfo = camera.cameraInfo
val cameraControl = camera.cameraControl

7.1 曝光补偿

setExposureCompensationIndex()

setExposureCompensationIndex(int value)

控制照片的亮度,值通常是一个范围。通过 CameraInfo 获取。

val range = cameraInfo.exposureState.exposureCompensationRange
if (range.contains(1)) {
    cameraControl.setExposureCompensationIndex(1) // 设置曝光补偿值
}

7.2 预览的缩放比

setZoomRatio()

ListenableFuture<Void> setZoomRatio(float ratio)

设置预览画面的缩放比例,以放大或缩小预览内容。

cameraControl.setZoomRatio(2.0f) // 设置缩放比例为 2.0

八、PreviewView

8.1 缩放

setScaleType()

提供了几种缩放类型,用于控制预览画面的显示方式:

FILL_CENTER:填充整个视图,可能会裁剪部分画面。

FIT_START:保持宽高比,可能会在顶部或左侧留出空白。

FIT_CENTER:保持宽高比,可能会在上下或左右留出空白。

FIT_END:保持宽高比,可能会在底部或右侧留出空白。

previewView.scaleType = PreviewView.ScaleType.FIT_CENTER
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值