学习Android CameraX 顺便翻译一下 官方给的说明
翻译来源CameraX 使用入门 该链接可能需要科学上网
额 前面那几章权限管理 就比较简单了 不翻了 放上布局文件吧 很简单 一个按钮一个 预览的View
<?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">
<Button
android:id="@+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitCenter"
android:elevation="2dp"
android:text="Take Photo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
当然AndroidManifest.xml需要注册权限
<!-- 这个貌似是 app需要相机的类型 此处是任何相机 还可以有其他类型 比如需要聚焦 --!>
<uses-feature android:name="android.hardware.Camera.any"/>
<uses-permission android:name="android.permission.CAMERA"/>
以下是翻译正文
前三章 如何构建项目以及权限管理 略
4. 实现预览用例
在相机应用程序中,取景器用于让用户预览将要拍摄的照片。你可以使用Camerax Preview类来实现一个取景器。
要使用Preview,您首先需要定义一个配置,然后用它来创建用例的一个实例。你需要绑定到CameraX生命周期的结果实例。
1.复制代码到startCamera()函数
下面的分析您刚才复制的代码。
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.Builder().build()
//select back camera
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
try {
//unbide 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))
}
- 创建一个ProcessCameraProvider。这用于将相机的生命周期绑定到生命周期所有者。这允许你不用担心打开和关闭相机,因为CameraX是生命周期感知的
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
- 添加一个侦听器到cameraProviderFuture。添加一个Runnable作为一个参数,我们稍后将填充它。添加ContextCompat.getMainExecutor() 作为第二个参数,它返回在主线程上运行的执行程序。
cameraProviderFuture.addListener(Runnable {},ContextCompat.getMainExecutor(this))
- 在这个Runalbe 添加一个ProcessCameraProvider,它用于将相机的生命周期绑定到应用程序进程中的LifecycleOwner。
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
- 初始化你的Preview
preview = Preview.Builder().build()
- 创建一个CameraSelector对象和使用CameraSelector.Builder.requireLensFacing()函数来打开你想要的镜头
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK)
- 创建一个try块。在那个块中,确保没有东西绑定到你的cameraProvider,然后绑定你的cameraSelector和preview对象到cameraProvider。将取景器的surface提供程序附加到preview。
try {
//unbide use cases before rebinding
cameraProvider.unbindAll()
//bind use cases to camera
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
}
- 这段代码有几种可能失败的情况,比如应用程序不再是焦点。如果出现故障,将此代码包装在catch块中以进行日志记录。
catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
2.运行APP,然后你就能看到相机的预览画面
5. 实现ImageCapture用例
与预览相比,其他用例的工作方式非常类似。首先,您必须定义一个用于实例化实际用例对象的配置对象。要捕获照片,您需要实现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.CHINA)
.format(System.currentTimeMillis()) + ".JPG"
)
//Create output pptions object which constins 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(exception: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exception.message}", exception)
}
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo caputre succeeded: $savedUri"
Toast.makeText(baseContext,msg,Toast.LENGTH_SHORT).show()
Log.d(TAG,msg)
}
}
)
}
- 首先,获取对ImageCapture用例的引用。如果用例为空,则从函数中返回。如果在设置图像捕获之前点击photo按钮,则该值为null。如果没有return语句,应用程序如果为null就会崩溃。(为啥是崩溃? 就是拍照没反应吧。。。)
val imageCapture = imageCapture ?: return
- 接下来创建一个文件来保存图像。添加一个时间戳,这样文件名将是唯一的。
//Create timestamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.CHINA)
.format(System.currentTimeMillis()) + ".JPG"
)
- 创建一个outputFileoptions对象。在这个对象中,您可以指定有关您希望输出如何的内容。您希望将输出保存到我们刚刚创建的文件中,因此添加您的photoFile。
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
- 在imageCapture对象上调用takePicture()。传入输出、执行程序和映像保存时的回调。接下来将填写回调。
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback{}
)
- 在图像捕获失败或保存图像捕获失败的情况下,添加一个错误情况以记录它失败。
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exception.message}", exception)
}
- 如果捕捉没有失败,照片拍摄成功!将照片保存到前面创建的文件中,向用户表示祝贺,并打印一条日志语句。
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo caputre succeeded: $savedUri"
Toast.makeText(baseContext,msg,Toast.LENGTH_SHORT).show()
Log.d(TAG,msg)
}
- 转到startCamera()方法,并在“预览代码”下复制这段代码
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()
...
- 最后,在try块中更新对bindToLifecycle()的调用,包括新的用例:
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
- 重新运行应用程序并按下拍照。您将在屏幕上看到一个Toast,并在日志中看到一条消息。
6. 实现ImageAnalysis用例
如果运行Q或更低,那么同时实现预览、图像捕获和图像分析将不适用于Android Studio的设备模拟器。我们建议使用真实的设备来测试这部分代码。
让你的相机应用程序更有趣的一个好方法是使用ImageAnalysis功能。它允许您定义一个实现ImageAnalysis的自定义类。分析器接口,它将被调用与传入的摄像机帧。您将不必担心管理相机会话状态或甚至处理图像;绑定到我们想要的生命周期就足够了,就像其他生命周期感知组件一样。
- 将这个分析器作为main .kt中的一个内部类添加进来。
这个分析器记录图像的平均亮度。要创建分析程序,您需要在实现ImageAnalysis的类中重写analyze函数。分析仪的接口。
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接口,所有我们需要做的是在ImageAnalysis中实例化一个LuminosityAnalyzer的实例,就像所有其他的用例一样,在调用CameraX.bindToLifecycle()之前,再次更新’ startCamera() '函数:
- 在startCamera()方法中,在imageCapture()代码下添加此代码。
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()
...
}
- 更新bindToLifecycle()调用cameraProvider以包含imageAnalyzer。
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
- 现在运行应用程序!它大约每秒在logcat中生成一个类似的消息。
D/CameraXApp: Average luminosity: ...
翻译结束
最后把整个MainActivity.kt放上来
package city.carcate.u.myapplication
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.ContactsContract
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/**
*
*/
class MainActivity : AppCompatActivity() {
private var preview: Preview? = null
private var imageCapture: ImageCapture? = 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)
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PREMISSIONS
)
}
//setup the listener for take photo button
camera_capture_button.setOnClickListener { takePhoto() }
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
}
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.Builder().build()
imageCapture = ImageCapture.Builder().build()
//select back camera 选择后置摄像头
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
try {
//unbide use cases before rebinding 先解绑一下
cameraProvider.unbindAll()
//bind use cases to camera
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
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.CHINA)
.format(System.currentTimeMillis()) + ".JPG"
)
//Create output pptions object which constins 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(exception: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exception.message}", exception)
}
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo caputre succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
}
)
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
private 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
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PREMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_LONG)
.show()
finish()
}
}
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-mm-ss-SSS" //这个时间怪怪的 正常不是这个吗 yyyy-MM-dd HH:mm:ss
private const val REQUEST_CODE_PREMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
}
还有app配置文件下引入的库
// CameraX core library
def camerax_version = '1.0.0-beta03'
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 extensions
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'
// CameraX Extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha10"