Jetpack之CameraX的使用
Camera2的使用#
- 已将 CameraX 依赖项加入到您的项目中。
- 已显示相机取景器(使用 Preview 用例)
- 已能够拍摄照片,并可将图像保存到存储空间(使用 ImageCapture 用例)
- 已实现对来自相机的画面帧进行实时分析(使用 ImageAnalysis 用例)
具体步骤#
- 1.配置依赖
//cameraX
implementation "androidx.camera:camera-core:1.1.0"
implementation "androidx.camera:camera-camera2:1.1.0"
implementation "androidx.camera:camera-lifecycle:1.1.0"
implementation "androidx.camera:camera-view:1.1.0"
- 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/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/action_area"
android:layout_width="match_parent"
android:layout_height="80dp"
app:layout_constraintBottom_toBottomOf="parent"
android:background="#50000000" />
<ImageView
android:id="@+id/take_photo_img"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/take_photo"
app:layout_constraintTop_toTopOf="@id/action_area"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/switch_camera"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/switch_camera"
android:layout_marginEnd="30dp"
app:layout_constraintTop_toTopOf="@id/action_area"
app:layout_constraintBottom_toBottomOf="@id/action_area"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/result_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:visibility="invisible" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 3.Camera的工具类
class EasyCamera(
val lifecycleOwner: LifecycleOwner,
val context: Context,
val previewView: PreviewView
) {
// CameraX 相关
var camera: Camera? = null
var cameraExecutor: ExecutorService? = null
var preview: Preview? = null
// 摄像头 前置/后置 默认后置
@LensFacing
var lensFacing: Int = CameraSelector.LENS_FACING_BACK
private var width = 0
private var height = 0
// 照片比例 默认16:9
var ratio: Int = AspectRatio.RATIO_16_9
// 旋转角度 默认无
var rotation: Int = 0
//相机提供者
var cameraProvider: ProcessCameraProvider? = null
var videoCapture: VideoCapture? = null
init {
cameraExecutor = Executors.newSingleThreadExecutor()
lifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event === Lifecycle.Event.ON_DESTROY) {
cameraProvider?.unbindAll()
cameraProvider?.shutdown()
cameraExecutor?.shutdown()
}
})
buildCamera()
camera?.cameraInfo?.cameraState?.observe(lifecycleOwner) { t ->
t?.let {
if (it.type != CameraState.Type.OPEN) {
cameraProvider?.unbindAll()
}
}
}
}
fun release() {
cameraProvider?.unbindAll()
}
fun switchCamera(@LensFacing lensFacing: Int) {
this.lensFacing = lensFacing
if (this.lensFacing != lensFacing) {
buildCamera()
}
}
/**
* 拍照
* 默认路径是应用外置存储空间
*/
fun takePicture(
callback: (Uri?, ImageCaptureException?) -> Unit
) {
val outputFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
String.format("IMG_%s%s", System.currentTimeMillis(), ".jpg")
)
takePicture(
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
outputFile,
null,
callback
)
}
/**
*拍照 自定义图片路径
*/
fun takePicture(
imageFile: File,
callback: (Uri?, ImageCaptureException?) -> Unit
) {
takePicture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, imageFile, null, callback)
}
/**
* 配置相机拍照图片的路径
* @param captureMode 相机模式 拍照模式:速度优先/质量优先 默认质量优先
* @param imageFile 图片输出文件
* @param imageAnalysis 图像实时分析
* @param callback 拍照完成的回调 主线程
*/
fun takePicture(
@ImageCapture.CaptureMode captureMode: Int,
imageFile: File,
imageAnalysis: ImageAnalysis? = null,
callback: (Uri?, ImageCaptureException?) -> Unit
) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
return
}
val imageCapture = ImageCapture.Builder()
.setCaptureMode(captureMode)
.setTargetAspectRatio(ratio)
.setTargetRotation(rotation)
.build()
try {
imageAnalysis?.let {
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview,
imageCapture, it
)
} ?: let {
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview,
imageCapture
)
}
setFocus(width.toFloat() / 2, height.toFloat() / 2)
val metadata = ImageCapture.Metadata().apply {
isReversedHorizontal = (lensFacing == CameraSelector.LENS_FACING_FRONT)
}
val options = ImageCapture.OutputFileOptions.Builder(imageFile)
.setMetadata(metadata)
.build()
imageCapture.takePicture(
options, cameraExecutor!!, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
previewView.post {
callback.invoke(outputFileResults.savedUri, null)
}
}
override fun onError(exception: ImageCaptureException) {
previewView.post {
callback.invoke(null, exception)
}
}
}
)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 录像
* @param videoFile 图片输出文件
* @param imageAnalysis 图像实时分析
* @param callback 录像完成的回调
*/
@SuppressLint("RestrictedApi")
fun startRecord(
imageAnalysis: ImageAnalysis? = null,
callback: (Uri?, Exception?) -> Unit
) {
val outputFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_MOVIES),
String.format("video_%s%s", System.currentTimeMillis(), ".mp4")
)
startRecord(outputFile, null, callback)
}
/**
* 录像
* @param videoFile 图片输出文件
* @param imageAnalysis 图像实时分析
* @param callback 录像完成的回调
*/
@SuppressLint("RestrictedApi")
fun startRecord(
videoFile: File,
imageAnalysis: ImageAnalysis? = null,
callback: (Uri?, Exception?) -> Unit
) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
return
}
videoCapture = VideoCapture.Builder()
.setTargetAspectRatio(ratio)//设置相机比例
.setAudioBitRate(1024)//设置音频的码率
.setAudioChannelCount(2)
.setAudioMinBufferSize(3 * 1024)
.setVideoFrameRate(25)//视频帧率 越高视频体积越大
.setBitRate(3 * 1024 * 1024)//设置视频的比特率 越大视频体积越大
.build()
try {
imageAnalysis?.let {
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview,
videoCapture, it
)
} ?: let {
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview,
videoCapture
)
}
setFocus(width.toFloat() / 2, height.toFloat() / 2)
val options = VideoCapture.OutputFileOptions.Builder(videoFile).build()
videoCapture!!.startRecording(
options,
cameraExecutor!!,
object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
previewView.post {
callback.invoke(outputFileResults.savedUri, null)
}
}
override fun onError(
videoCaptureError: Int,
message: String,
cause: Throwable?
) {
previewView.post {
callback.invoke(null, Exception(message, cause))
}
}
})
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 停止录制
*/
@SuppressLint("RestrictedApi")
fun stopRecord() {
videoCapture?.let {
it.stopRecording()
videoCapture = null
}
}
/**
* 图像分析
* @param analyzer 图像实时分析
*/
@SuppressLint("RestrictedApi")
fun analyzerCapture(
analyzer: ImageAnalysis.Analyzer
) {
try {
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(width, height))
.build().also {
it.setAnalyzer(Executors.newSingleThreadExecutor(), analyzer)
}
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview, imageAnalysis
)
setFocus(width.toFloat() / 2, height.toFloat() / 2)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun buildCamera() {
ratio = matchPreviewRatio()
height = width * ratio
setOrientationListener()
ProcessCameraProvider.getInstance(context).apply {
addListener({
cameraProvider = this.get()
preview = Preview.Builder()
.setTargetAspectRatio(ratio)
.setTargetRotation(rotation)
.build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
cameraProvider?.unbindAll()
camera = cameraProvider?.bindToLifecycle(
lifecycleOwner,
CameraSelector.Builder().requireLensFacing(lensFacing).build(),
preview
)
}, ContextCompat.getMainExecutor(context))
}
}
fun setFocus(x: Float, y: Float) {
val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
width.toFloat(), height.toFloat()
)
val autoFocusPoint = factory.createPoint(x, y)
camera?.cameraControl?.startFocusAndMetering(
FocusMeteringAction.Builder(
autoFocusPoint,
FocusMeteringAction.FLAG_AF
).apply {
//auto-focus every 1 seconds
setAutoCancelDuration(1, TimeUnit.SECONDS)
}.build()
)
}
/**
* 设置缩放比例
*/
@SuppressLint("RestrictedApi")
fun setZoomRatio(zoomRatio: Float) {
var future: ListenableFuture<Void>? = null
future = if (zoomRatio > getMaxZoomRatio()) {
camera?.cameraControl?.setZoomRatio(1.0f)
} else {
camera?.cameraControl?.setZoomRatio(zoomRatio)
}
future?.apply {
Futures.addCallback(future, object : FutureCallback<Void?> {
override fun onSuccess(result: Void?) {}
override fun onFailure(t: Throwable) {}
}, CameraXExecutors.directExecutor())
}
}
fun getZoomRatio(): Float {
return camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
}
fun getMaxZoomRatio(): Float {
return camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 0F
}
//预览比例
private fun matchPreviewRatio(): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).apply {
width = currentWindowMetrics.bounds.width()
height = currentWindowMetrics.bounds.height()
}
} else {
context.resources.displayMetrics.apply {
width = widthPixels
height = heightPixels
}
}
val previewRatio = width.coerceAtLeast(height).toDouble() / width.coerceAtMost(height)
return if (abs(previewRatio - AspectRatio.RATIO_4_3) <= abs(previewRatio - AspectRatio.RATIO_16_9)) {
AspectRatio.RATIO_4_3
} else AspectRatio.RATIO_16_9
}
//图片预览是否旋转角度
private fun setOrientationListener(): Int {
object : OrientationEventListener(previewView.context) {
override fun onOrientationChanged(orientation: Int) {
rotation = when (orientation) {
in 45..134 -> {
Surface.ROTATION_270
}
in 135..224 -> {
Surface.ROTATION_180
}
in 225..314 -> {
Surface.ROTATION_90
}
else -> {
Surface.ROTATION_0
}
}
}
}.enable()
return rotation
}
}
- 4.调用
PreviewView previewView = findViewById(R.id.preview_view);
EasyCamera easyCamera = EasyCamera(
lifecycleOwner = lifecycleOwner,
context = this.context,
previewView = previewView
)
//切换相机
easyCamera.switchCamera();
//拍照 file和analyzer 可有可无 无的话走默认配置
easyCamera.takePicture(file,analyzer,回调)
//录像 需要声音的权限 file和analyzer 可有可无 无的话走默认配置
easyCamera.startRecord(file,analyzer,回调)
//实时图像分析 可以自定义ImageAnalysis.Analyzer来做图像分析
analyzer = new QRCodeAnalyzer(easyCamera, 回调);//自定义的二维码扫描
easyCamera.analyzerCapture(analyzer);
analyzer.resetAnalyzer();//重新扫描识别
作者:kevin2022
出处:https://www.cnblogs.com/kevin2022/p/16665473.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
你可以在这里自定义其他内容
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!