轻松实现 Android 相机功能:使用 CameraX 构建简单应用
媒介在智能手机不停进化的今天,影像功能已成为消费者选择手机时的紧张考量因素之一。各大手机厂商也越来越注重提升手机的影像表现。从最初的简单照相到如今的专业级摄影体验,手机相机的性能和图像处置惩罚技能都取得了飞跃性的发展。如今的智能手机不仅能拍摄高质量的照片,还能录制 4K 视频、实现人脸识别、举行实时图像分析,乃至通过增强实际(AR)技能提供更加丰富的用户体验。各大厂商也推出了各具特色的影像技能,例如:小米的徕卡影像、华为的红枫影像、vivo 的蔡司镜头、以及 OPPO 的哈苏影像。这些先进的功能背后,都离不开强大的相机开辟框架与库的支持。
然而,随着差别设备的涌现以及硬件差别和操纵体系版本的不停更新,开辟者在实现相机功能时面对着巨大的挑衅。传统的 Android 相机 API 不仅复杂,而且难以适配各种设备,开辟者每每必要耗费大量时间举行底层设置与优化。为了办理这些问题,Google 推出了 CameraX——一个专为 Android 开辟者计划的相机库。CameraX 以其简洁、高效和跨设备适配的优势,为开辟者提供了更为一致、易用的相机功能实现方案。
一、cameraX概述
1、 cameraX的简介
CameraX 是 Google 推出的一个 Android 相机库,旨在简化相机功能的开辟,提升跨设备兼容性,并提供更加一致的 API 接口。与传统的 Android 相机 API 相比,CameraX 更加易于使用,它通过封装底层的相机硬件操纵,使开辟者能够专注于实现业务逻辑和用户体验,而不必过多关心设备差别、性能优化和权限管理等复杂问题。
CameraX 提供了多个模块,包括 相机预览(Preview)、照相(ImageCapture)、视频录制(VideoCapture) 以及 图像分析(ImageAnalysis) 等,覆盖了大多数常见的相机应用场景。同时,CameraX 自动适配差别型号的设备,确保即便是在硬件设置差别较大的 Android 设备上,应用依然能够流畅运行。
该库与 Android Jetpack 无缝集成,能够与 Lifecycle 和 Permissions 库一起使用,帮助开辟者更方便地管理相机生命周期和权限哀求。此外,CameraX 还支持与其他 Google 库(如 ML Kit)配合,举行实时图像分析和机器学习应用,从而开辟了更广阔的应用场景。
2、cameraX的布局
在使用 CameraX时,提供的用例如下:
预览:继承用于显示预览的 Surface,例如 PreviewView。
图片分析:为分析(例如机器学习)提供 CPU 可访问的缓冲区。
图片拍摄:拍摄并生存照片。
视频拍摄:通过 VideoCapture 拍摄视频和音频
3、API模型
当我们使用cameraX时,必要指定以下内容:
[*]具有设置选项的所需用例。
[*]通过附加监听器来指定如何处置惩罚输出数据。
[*]通过将用例绑定到 Android 架构生命周期来指定目的流程,例如何时启用相机及何时生成数据。
我们可以接纳两种方式编cameraX应用:CameraController(最简单应用cameraX的方式)或 CameraProvider(具有更高的灵活性)。
CameraController
CameraController 在单个类中提供大多数 CameraX 核心功能。它只需少量设置代码,并且可自动处置惩罚相机初始化、用例管理、目的旋转、点按对焦、双指张合缩放等操纵。扩展 CameraController 的具体类为 :
LifecycleCameraController。(Kotlin)
// 获取 PreviewView 的引用,PreviewView 是用来显示相机预览的视图组件
val previewView: PreviewView = viewBinding.previewView
// 创建 LifecycleCameraController 实例,它是 CameraX 提供的一个简化的相机控制器
// 通过它可以方便地管理相机的生命周期和预览
var cameraController = LifecycleCameraController(baseContext)
// 将相机控制器与当前的生命周期绑定,通常是 Activity 或 Fragment。
// 这样相机的生命周期会自动跟随组件的生命周期进行管理,
// 当生命周期结束时,CameraX 会自动释放相机资源
cameraController.bindToLifecycle(this)
// 设置相机控制器使用后置摄像头,CameraSelector 用来选择前置或后置摄像头
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 将相机控制器绑定到 PreviewView 上,
// 这样相机的预览内容就会显示在 previewView 中,用户可以看到实时的相机画面
previewView.controller = cameraController
CameraController 的默认 UseCase 为 Preview、ImageCapture 和 ImageAnalysis。如需关闭 ImageCapture 或 ImageAnalysis,大概开启 VideoCapture,请使用 setEnabledUseCases() 方法。
CameraProvider
CameraProvider 仍然易于使用,但由于应用开辟者会处置惩罚更多设置,因此有更多机会自界说设置,例如在 ImageAnalysis 中启用输出图片旋转或设置输出图像格式。我们还可以使用自界说 Surface 举行相机预览以提高灵活性,而对于 CameraController,必要使用 PreviewView。如果现有的 Surface 代码已是应用的其他部分的输入,则使用该代码会更加高效。
我们可以接纳 set() 方法设置用例,并使用 build() 方法完成这些用例。每个用例对象都提供一组特定于该用例的 API。例如,图片拍摄用例会提供 takePicture() 方法调用。
应用没有在 onResume() 和 onPause() 中放置具体的启动和停止方法调用,而是使用 cameraProvider.bindToLifecycle() 指定要与相机关联的生命周期。之后,该生命周期会告知 CameraX 何时设置相机拍摄会话并确保相机状态随生命周期的转换相应地变化。
如需了解每个用例的实现步骤,请参阅实现预览、分析图片、图片拍摄和视频拍摄
预览用例会与 Surface 互动以展示预览。应用使用以下代码创建具有设置选项的用例:
// 创建 Preview 实例,用于显示相机的预览画面
val preview = Preview.Builder().build()
// 获取布局文件中的 PreviewView 控件,用于呈现相机的实时预览画面
val viewFinder: PreviewView = findViewById(R.id.previewView)
// 将相机的使用场景绑定到 Android 的生命周期管理中
// 这样相机的生命周期将与 lifecycleOwner(例如 Activity 或 Fragment)同步管理
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
// 使用 PreviewView 来创建 SurfaceProvider,它是显示相机预览的推荐方式
// 通过该方法将相机预览绑定到 PreviewView
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
4、要求
CameraX 具有以下最低版本要求:
Android API 级别 21
Android 架构组件 1.1.1
二、如何构建一个基于cameraX的简单相机
1. 创建项目
1、 在 Android Studio 中,创建一个新项目,并在出现提示时选择“Empty Activity”。
https://i-blog.csdnimg.cn/direct/61435c1f8d6b4638a817f89693cf4e6e.png
2、接下来,将应用命名为“CameraXApp”,然后确认软件包名称或将其更改为“com.android.example.cameraxapp”。选择 Kotlin 作为语言,并将最低 API 级别设置为 21(CameraX 所需的最低级别)。
https://i-blog.csdnimg.cn/direct/4cd948edadb54dc58367d63851527ae5.png
添加gradle依靠
1、打开 CameraXApp.app 模块的 build.gradle 文件,并添加 CameraX 依靠项:
dependencies {
def camerax_version = "1.1.0-beta01"
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-video:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
}
2、CameraX 必要一些属于 Java 8 的方法,因此我们必要相应地设置编译选项。在 android 代码块的末尾,紧跟在 buildTypes 之后,添加以下代码:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
3、此 Codelab 使用 ViewBinding,因此请使用以下代码(在 android{} 代码块末尾)启用它:
buildFeatures {
viewBinding true
}
创建 Codelab 布局
1、在此 Codelab 的界面中,我们使用了以下内容:
[*]CameraX PreviewView(用于预览相机图片/视频)。
[*]用于控制图片拍摄的尺度按钮。
[*]用于开始/停止视频拍摄的尺度按钮。
[*]用于放置 2 个按钮的垂直指南。
我们将默认布局更换为以下代码,从而:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/image_capture_button"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_marginBottom="50dp"
android:layout_marginEnd="50dp"
android:elevation="2dp"
android:text="@string/take_photo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toStartOf="@id/vertical_centerline" />
<Button
android:id="@+id/video_capture_button"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_marginBottom="50dp"
android:layout_marginStart="50dp"
android:elevation="2dp"
android:text="@string/start_capture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/vertical_centerline" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/vertical_centerline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent=".50" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局效果:
https://i-blog.csdnimg.cn/direct/5cadc19960574be68b5fe80ffacaa077.png
2、更新res/value/sstrings.xml资源文件:
<resources>
<string name="app_name">CameraXApp</string>
<string name="take_photo">Take Photo</string>
<string name="start_capture">Start Capture</string>
<string name="stop_capture">Stop Capture</string>
</resources>
设置mainActivity.kt
1、将 MainActivity.kt 中的代码更换为以下代码,但保留软件包名称不变。它包罗 import 语句、将要实例化的变量、要实现的函数以及常量。
package com.android.example.cameraxapp
import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.android.example.cameraxapp.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import android.widget.Toast
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.core.Preview
import androidx.camera.core.CameraSelector
import android.util.Log
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.video.FallbackStrategy
import androidx.camera.video.MediaStoreOutputOptions
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import androidx.camera.video.VideoRecordEvent
import androidx.core.content.PermissionChecker
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.Locale
// 类型别名,将 lambda 表达式定义为 LumaListener,用于亮度分析回调
typealias LumaListener = (luma: Double) -> Unit
class MainActivity : AppCompatActivity() {
// ViewBinding 对象,用于简化对 XML 布局文件视图的访问
private lateinit var viewBinding: ActivityMainBinding
// ImageCapture 对象,用于拍照操作
private var imageCapture: ImageCapture? = null
// VideoCapture 对象,用于录制视频,使用泛型 Recorder 指定录制器
private var videoCapture: VideoCapture<Recorder>? = null
// Recording 对象,用于管理录制会话
private var recording: Recording? = null
// ExecutorService,用于相机的后台线程操作,防止阻塞主线程
private lateinit var cameraExecutor: ExecutorService
// Activity 的生命周期方法 onCreate,当 Activity 被创建时调用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化视图绑定,将布局文件与代码绑定
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
// 请求相机权限,如果权限已授予,则启动相机,否则请求权限
if (allPermissionsGranted()) {
startCamera()// 权限已授予,启动相机
} else {
// 请求相机和音频录制的权限
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
// 设置拍照按钮的点击监听器,点击时调用 takePhoto() 方法
viewBinding.imageCaptureButton.setOnClickListener { takePhoto() }
// 设置视频录制按钮的点击监听器,点击时调用 captureVideo() 方法
viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }
// 初始化后台线程池,用于执行相机操作
cameraExecutor = Executors.newSingleThreadExecutor()
}
// 拍照功能的实现,当前为空,需要根据业务逻辑进行实现
private fun takePhoto() {}
// 视频录制功能的实现,当前为空,需要根据业务逻辑进行实现
private fun captureVideo() {}
// 启动相机并设置预览等功能,当前为空,需要根据业务逻辑进行实现
private fun startCamera() {}
// 检查所有必需权限是否被授予,如果全部权限都被授予,返回 true,否则返回 false
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
// 检查权限是否被授予
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}
// Activity 的生命周期方法 onDestroy,当 Activity 销毁时调用,释放资源
override fun onDestroy() {
super.onDestroy()
// 关闭后台线程,防止资源泄漏
cameraExecutor.shutdown()
}
// 静态常量 companion object,存储类的静态属性和方法
companion object {
private const val TAG = "CameraXApp"// 日志标签
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"// 文件命名格式
private const val REQUEST_CODE_PERMISSIONS = 10// 权限请求码
// 必需的权限列表,包括相机和录音权限,根据系统版本还可能需要存储权限
private val REQUIRED_PERMISSIONS =
mutableListOf (
Manifest.permission.CAMERA,// 相机权限
Manifest.permission.RECORD_AUDIO// 录音权限
).apply {
// 如果系统版本小于等于 Android 9 (API 28),需要存储权限
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}.toTypedArray()// 转换为数组形式
}
}
这段代码中体系已实现 onCreate()方法,供我们检查相机权限、启动相机、为照片和拍摄按钮设置 onClickListener(),以及实现 cameraExecutor。虽然体系已为您实现 onCreate(),但在代码中的startCamera()、takePhoto()和captureVideo()三个方法仍未实现,相机将无法正常工作。此时相机效果如下:
https://i-blog.csdnimg.cn/direct/ed07054e617d4d3099417f304118bf1c.jpeg
2、权限设置
当我们启动应用时必要得到用户授权才气打开相机;同时录制音频也必要麦克风权限;在 Android 9 § 及更低版本上,MediaStore 必要外部存储空间写入权限。在此步骤中,我们将实现这些必要的权限。
1、打开 AndroidManifest.xml,然后将以下代码行添加到 application 标记之前。
<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" />
添加 android.hardware.camera.any 可确保设备配有相机。指定 .any 表现它可以是前置摄像头,也可以是后置摄像头。
(如果您在未指定 .any 的情况下使用 android.hardware.camera,并且您的设备未配有后置摄像头(例如大多数 Chromebook),那么相机将无法正常运行。第二行代码会添加对该相机的使用权限。)
2、向mainActivity.kt添加以下代码:
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS)
{
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
}
}
这里解说此段代码的作用
[*]判断当前处置惩罚的是否是应用发起的权限哀求。
if (requestCode == REQUEST_CODE_PERMISSIONS)
[*]如果 allPermissionsGranted() 返回 true,表现用户授予了全部必要的权限。然后调用 startCamera() 方法来启动相机,由于现在已经具备了全部必要的权限(相机权限、录音权限等)。
if (allPermissionsGranted()) {
startCamera()
}
[*]如果用户没有授予全部权限,使用 Toast.makeText() 显示一条消息“用户未授予权限”之后调用 finish() 方法结束当前活动 (Activity),防止应用继续运行,由于相机等功能无法在没有权限的情况下正常工作。
else {
Toast.makeText(this,
"用户未授予权限",
Toast.LENGTH_SHORT).show()
finish()
}
3、运行相机
此时应用向用户哀求相机和录音权限。
https://i-blog.csdnimg.cn/direct/4f391327b53c4cdd8c8612b3e7f85f34.jpeg
https://i-blog.csdnimg.cn/direct/de238aa518ae42799dd5a2c2e121d230.jpeg
3实现Preview用例(实现取景器可预览照片)
在相机应用中,取景器用于让用户预览他们拍摄的照片。我们将使用 CameraX Preview 类实现取景器。
如需使用 Preview,我们首先必要界说一个设置,然后体系会使用该设置创建用例的实例。生成的实例就是我们绑定到 CameraX 生命周期的内容。
实现startCamera()方法
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
代码解说:
[*]获取 ProcessCameraProvider 实例,它是 CameraX 的核心类之一,用于毗连相机生命周期和应用的 LifecycleOwner(这里是 MainActivity)。通过cameraProviderFuture 异步获取 ProcessCameraProvider。
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
[*]向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。
cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this))
[*]在 Runnable 中,添加 ProcessCameraProvider。它用于将相机的生命周期绑定到应用进程中的 LifecycleOwner。
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
[*]初始化 Preview 对象,在其上调用 build,从取景器中获取 Surface 提供程序,然后在预览上举行设置。
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
}
[*]使用 CameraSelector 来选择相机,这里选择的是后置摄像头(DEFAULT_BACK_CAMERA)。
CameraSelector 可以用于选择后置或前置摄像头。
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
[*] cameraProvider.unbindAll()用于解绑全部之前的用例,确保每次相机启动时都处于干净的状态,以避免重复绑定。
cameraProvider.bindToLifecycle()将预览用例绑定到相机的生命周期上。this 指的是当前 Activity,它实现了 LifecycleOwner 接口,用于控制相机生命周期的管理。cameraSelector 确定使用哪一个相机(如前置或后置摄像头),preview 是将要绑定的用例。
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview)
此时运行相机,我们能看见相机预览
https://i-blog.csdnimg.cn/direct/0ac194a0fe0e438cb0a0b1eadd534da0.jpeg
4、 实现 ImageCapture 用例
其他用例与 Preview 非常相似。首先,我们界说一个设置对象,该对象用于实例化实际用例对象。若要拍摄照片,我们必要实现 takePhoto() 方法,该方法会在用户按下 photo 按钮时调用。
1、实现takephoto()方法
private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create time stamped name and MediaStore entry.
val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
.format(System.currentTimeMillis())
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
}
}
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions
.Builder(contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues)
.build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun
onImageSaved(output: ImageCapture.OutputFileResults){
val msg = "Photo capture succeeded: ${output.savedUri}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
}
)
}
1、获取可修改的 ImageCapture 实例:
val imageCapture = imageCapture ?: return
首先,获取对 ImageCapture 用例的引用。如果用例为 null,请退出函数。如果在设置图片拍摄之前点按“photo”按钮,它将为 null。如果没有 return 语句,应用会在该用例为 null 时崩溃。
2、创建带有时间戳的文件名和 MediaStore 条目:
val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
.format(System.currentTimeMillis())
使用 SimpleDateFormat 生成一个带有当前时间戳的文件名,确保每次照相的文件名都是唯一的,避免覆盖从前的照片。
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
}
}
创建了一个 ContentValues 对象,包罗文件的元数据信息:
[*]DISPLAY_NAME:照片的显示名称。
[*]MIME_TYPE:文件类型,这里是 JPEG 图像。
[*]RELATIVE_PATH:在 Android 版本大于 Android 9 (Pie) 时,指定照片生存到 Pictures/CameraX-Image 目录。
3、创建输出选项 (OutputFileOptions):
val outputOptions = ImageCapture.OutputFileOptions
.Builder(contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues)
.build()
创建 OutputFileOptions 对象,用于指定图像生存的位置和文件的元数据:
[*]contentResolver 用于访问设备的内容提供者 (MediaStore)。
[*]MediaStore.Images.Media.EXTERNAL_CONTENT_URI 指定图像将生存到外部存储空间的默认图片目录。
[*]contentValues 包罗前面界说的文件信息,如文件名和路径。
4、调用 takePicture() 方法照相:
mageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "拍照失败: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val msg = "成功拍照: ${output.savedUri}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
}
)
takePicture() 是 ImageCapture 用例的核心方法,用于执行照相操纵。
通报了三个参数:
[*]outputOptions:指定照片的生存路径和文件信息。
[*]ContextCompat.getMainExecutor(this):确保回调在主线程中执行(主线程负责 UI 更新)。
[*]回调 OnImageSavedCallback:
[*]onError():当照片拍摄失败时调用,捕获并记录错误信息(如存储空间不敷或相机故障),使用 Log.e() 输堕落误日志。
[*]onImageSaved():当照片拍摄成功时调用,返回效果包罗 savedUri,即照片生存的位置。这里通过 Toast 向用户显示成功消息,并使用 Log.d() 记录成功日志。
2、修改startCamera()方法
首先,将代码 imageCapture = ImageCapture.Builder().build()添加至startCamera()方法中的
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
背面,
然后,在 try 代码块中更新对 bindToLifecycle() 的调用,以包罗新的用例。
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture)
3、实现效果
1、拍摄成功提示
https://i-blog.csdnimg.cn/direct/df8c36b1bddb43e7842a3367349d63f4.jpeg
2、拍摄的照片信息
包罗以下信息:
[*]拍摄时间
[*]照片分辨率及巨细
[*]拍摄参数
[*]存储路径
https://i-blog.csdnimg.cn/direct/f596ed621f2548039586f4d4c63df194.jpeg
5、视频录制功能( 实现 VideoCapture 用例)
CameraX 在 1.1.0-alpha10 版中添加了 VideoCapture 用例,并且从那以后一直在改进。请留意,VideoCapture API 支持很多视频捕获功能,因此,为了使此 Codelab 易于管理,此 Codelab 仅演示如何在 MediaStore 中捕获视频和音频。
1、实现captureVideo()方法:该方法可以控制 VideoCapture 用例的启动和停止。
// Implements VideoCapture use case, including start and stop capturing.
private fun captureVideo() {
val videoCapture = this.videoCapture ?: return
viewBinding.videoCaptureButton.isEnabled = false
val curRecording = recording
if (curRecording != null) {
// Stop the current recording session.
curRecording.stop()
recording = null
return
}
// create and start a new recording session
val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
.format(System.currentTimeMillis())
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
}
}
val mediaStoreOutputOptions = MediaStoreOutputOptions
.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build()
recording = videoCapture.output
.prepareRecording(this, mediaStoreOutputOptions)
.apply {
if (PermissionChecker.checkSelfPermission(this@MainActivity,
Manifest.permission.RECORD_AUDIO) ==
PermissionChecker.PERMISSION_GRANTED)
{
withAudioEnabled()
}
}
.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
when(recordEvent) {
is VideoRecordEvent.Start -> {
viewBinding.videoCaptureButton.apply {
text = getString(R.string.stop_capture)
isEnabled = true
}
}
is VideoRecordEvent.Finalize -> {
if (!recordEvent.hasError()) {
val msg = "Video capture succeeded: " +
"${recordEvent.outputResults.outputUri}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
.show()
Log.d(TAG, msg)
} else {
recording?.close()
recording = null
Log.e(TAG, "Video capture ends with error: " +
"${recordEvent.error}")
}
viewBinding.videoCaptureButton.apply {
text = getString(R.string.start_capture)
isEnabled = true
}
}
}
}
}
1. 获取 VideoCapture 实例:
val videoCapture = this.videoCapture ?: return
videoCapture 是一个 VideoCapture 实例,负责视频录制。
如果 videoCapture 为空(例如未初始化),函数将直接返回。
2. 检查是否有正在举行的录制:
if (curRecording != null) {
// Stop the current recording session.
curRecording.stop()
recording = null
return
}
recording 生存当前的录制会话。如果存在正在举行的录制,会停止当前的录制并将 recording 设置为 null,结束录制操纵。
3.创建视频文件的元数据和存储位置:
val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
.format(System.currentTimeMillis())
val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") }} 4. 界说 MediaStoreOutputOptions 作为输出选项:
val mediaStoreOutputOptions = MediaStoreOutputOptions
.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build()
创建了 MediaStoreOutputOptions,它指定了视频将生存到外部存储的 Movies 目录,使用 contentValues 设定文件元数据。
5.准备并启动录制:
recording = videoCapture.output
.prepareRecording(this, mediaStoreOutputOptions)
.apply {
if (PermissionChecker.checkSelfPermission(this@MainActivity,
Manifest.permission.RECORD_AUDIO) ==
PermissionChecker.PERMISSION_GRANTED)
{
withAudioEnabled()
}
}
.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
when(recordEvent) {
is VideoRecordEvent.Start -> {
viewBinding.videoCaptureButton.apply {
text = getString(R.string.stop_capture)
isEnabled = true
}
}
is VideoRecordEvent.Finalize -> {
if (!recordEvent.hasError()) {
val msg = "Video capture succeeded: " +
"${recordEvent.outputResults.outputUri}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
.show()
Log.d(TAG, msg)
} else {
recording?.close()
recording = null
Log.e(TAG, "Video capture ends with error: " +
"${recordEvent.error}")
}
viewBinding.videoCaptureButton.apply {
text = getString(R.string.start_capture)
isEnabled = true
}
}
}
}
prepareRecording:准备开始录制,传入生存位置的输出选项。
withAudioEnabled():在检测到麦克风权限被授予的情况下,开启音频录制。
start():启动录制,并在主线程中监听录制事件,通过 recordEvent 回调处置惩罚开始与结束的情况。
效果展示
在录制开始前,录制按钮为startCapture
https://i-blog.csdnimg.cn/direct/5c591ae8b0d44354b4680100c0b73233.jpeg
录制开始后,按钮转变为stopCapture
https://i-blog.csdnimg.cn/direct/fffe16098fbf4d8a9f353734cd622092.jpeg
录制结束后,弹出提示 Video Capture succeededhttps://i-blog.csdnimg.cn/direct/728366ab7b5842a5b6847de294e342d4.jpeg
录制的视频包罗以下信息
[*]录制时间
[*]文件信息
[*]存储路径
https://i-blog.csdnimg.cn/direct/f6e2dff4c75c4499812f5f38a27c98b5.jpeg
总结
在这篇文章中,我们简单介绍了 Android 的 jetpack库中的CameraX API,并展示了如何通过简洁的步骤搭建一个简单的 CameraX 相机应用。CameraX 是一个强大且易于使用的库,它极大地简化了相机相干的开辟任务,并提供了丰富的功能,如图片和视频的拍摄、生命周期的绑定等。通过使用 CameraX,我们不仅能够快速实现相机功能,还能兼容差别的 Android 设备,提升应用的稳固性和用户体验。希望通过本教程,各人能够对 CameraX 有更深入的了解,并能够在自己的项目中轻松集成这一工具,打造出更加美满的相机功能。
参考文献
本文参考并引用CameraX 官方文档
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]