CameraX的使用源码Demo在此https://github.com/android/camera-samples/tree/master/CameraXBasic,可无需直接下载使用。
个人使用过后感觉比Camera和Camera2 更加简单易用,且兼容性更好,不会出现常见的聚焦失败等问题。
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
在此代码实验室中,您将学习如何创建一个使用CameraX来显示取景器,拍照并分析来自相机的图像流的相机应用。
为此,我们将在CameraX中引入用例的概念,您可以将其用于各种相机操作,从显示取景器到实时分析帧。
先决条件
你会做什么
你需要什么
1… 打开build.gradle(Module: app)文件,并将CameraX依赖项添加到我们的应用Gradle文件中的“ 依赖项”部分中:
def camerax_version = "1.0.0-beta03"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha10"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
出现提示时,点击立即同步,我们将准备在我们的应用程序中使用CameraX。
让我们用替换默认布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="50dp"
android:scaleType="fitCenter"
android:text="Take Photo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="2dp" />
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.example.cameraxapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.util.concurrent.Executors
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
typealias LumaListener = (luma: Double) -> Unit
class MainActivity : AppCompatActivity() {
private var preview: Preview? = null
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null
private var camera: Camera? = null
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
// Setup the listener for take photo button
camera_capture_button.setOnClickListener { takePhoto() }
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun startCamera() {
// TODO
}
private fun takePhoto() {
// TODO
}
private fun allPermissionsGranted() = false
fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
return if (mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
}
在应用程序打开相机之前,需要获得用户的许可才能这样做。在此步骤中,您将实现相机权限。
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
添加操作android.hardware.camera.any可确保该设备具有摄像机。指定.any意味着它可以是前置摄像头或后置摄像头。
如果您android.hardware.camera不使用.any,则如果您的设备没有后置摄像头(例如大多数Chromebook),则无法使用。第二行添加了访问该摄像机的权限。
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) {
}
if (allPermissionsGranted()) {
startCamera()
}
else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}
在相机应用程序中,取景器用于让用户预览将要拍摄的照片。您可以使用CameraX Preview类实现取景器。
要使用Preview,您首先需要定义一个配置,然后使用它来创建用例的实例。生成的实例是您需要绑定到CameraX生命周期的实例。
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
preview = Preview.Builder()
.build()
// Select back camera
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview)
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this))
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
preview = Preview.Builder().build()
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
try {
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview)
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
}
catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
与相比,其他用例的工作方式非常相似Preview。首先,必须定义一个用于实例化实际用例对象的配置对象。要捕获照片,您需要实现takePhoto()方法,当按下捕获按钮时会调用该方法。
将此代码复制到takePhoto()方法中。
下面的要点将分解您刚刚复制的代码。
private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create timestamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Setup 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 savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
val imageCapture = imageCapture ?: return
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
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 savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
imageCapture = ImageCapture.Builder()
.build()
这显示了在方法中粘贴代码的位置:
private fun startCamera() {
...
preview = Preview.Builder()
.build()
// Paste image capture code here!
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
...
}
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture)
查看照片
6. 检查日志语句,您还将看到一条日志,宣布照片捕获成功。
2020-04-24 15:13:26.146 11981-11981/com.example.cameraxapp D/CameraXBasic: Photo capture succeeded: file:///storage/emulated/0/Android/media/com.example.cameraxapp/CameraXApp/2020-04-24-15-13-25-746.jpg
/storage/emulated/0/Android/media/com.example.cameraxapp/CameraXApp/2020-04-24-15-13-25-746.jpg
adb shell
cp [INSERT THE FILE FROM STEP 2 HERE] /sdcard/Download/photo.jpg
adb pull /sdcard/Download/photo.jpg
如果您运行的是Q或更低版本,则同时实现预览,图像捕获和图像分析将不适用于Android Studio的设备模拟器。我们建议使用真实的设备来测试代码实验室的这一部分。
使用该ImageAnalysis功能可以使您的相机应用程序更加有趣。它允许您定义实现该ImageAnalysis.Analyzer接口的自定义类,该类将与传入的摄像机帧一起调用。您无需担心管理相机会话状态甚至图像的问题。绑定到我们的应用程序所需的生命周期就足够了,就像其他可感知生命周期的组件一样。
private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
override fun analyze(image: ImageProxy) {
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
val luma = pixels.average()
listener(luma)
image.close()
}
}
随着我们的类实现ImageAnalysis.Analyzer接口,我们需要做的就是实例化的一个实例,LuminosityAnalyzer在ImageAnalysis像所有其他用例和更新startCamera()
函数再次调用之前CameraX.bindToLifecycle():
imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
Log.d(TAG, "Average luminosity: $luma")
})
}
这显示了在方法中粘贴代码的位置:
private fun startCamera() {
...
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// Paste image analyzer code here!
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
...
}
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
D/CameraXApp: Average luminosity: ...
您已成功从头开始将以下内容实现到新的Android应用中:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
相关链接: