使用CameraX简化Androin相机的开发

使用CameraX简化Androin相机的开发

简介

CameraX 是一个 Jetpack 支持库,它利用Camera2的功能但使用的是更为简单且基于用例的方法,该方法具有生命周期感知能力。CameraX提供了一致并且易于使用的API界面,最低兼容到Android 5.0(API 21)。

CameraX从以下几个方面改善了开发体验:

易用性

CameraX内置多个用例,使用极少的代码即可实现较为复杂的功能,同时无需处理不同设备之间的细微差别。一些基本用例如下所示:

  • 预览:在显示屏上显示图片
  • 图片分析:无缝访问缓冲区,以便在算法中使用,例如传入MLKit
  • 图片拍摄:简单快速的保存图片

一致性

CameraX致力于解决不同设备和不同厂商对于API不一致的兼容性问题,比如宽高比、屏幕方向、旋转、预览大小和高分辨率图片大小等。

新的相机体验

CameraX 有一个名为 Extensions 的可选插件,只需两行代码,便可借助该插件使用与设备自带的原生相机应用相同的特性和功能。首批可用功能包括人像、HDR、夜间模式和美颜。这些功能可在受支持的设备上使用。

引入CameraX

1
2
3
4
5
6
7
8
9
// CameraX core library using the camera2 implementation
def camerax_version = "1.0.0-beta01"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to use the CameraX view class
implementation "androidx.camera:camera-view:1.0.0-alpha08"
// If you want to use the CameraX extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha08"
// If you want to use the CameraX lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<androidx.constraintlayout.widget.ConstraintLayout
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"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
androidx.constraintlayout.widget.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
class App : Application(),CameraXConfig.Provider {

override fun onCreate() {
super.onCreate()
//...
}

override fun getCameraXConfig(): CameraXConfig {
return Camera2Config.defaultConfig()
}

}

实现相机预览

CameraX提供了相机预览的用例,我们可以通过少量的代码即可实现相机的预览操作,PreviewView用于显示相机预览,该用例通过与Surface的交互实现显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<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"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

androidx.constraintlayout.widget.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class MainActivity : AppCompatActivity() {
private lateinit var binding: MainActivityBinding

private lateinit var cameraProviderFuture: ListenableFuture

private lateinit var previewView: PreviewView


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
previewView = binding.previewView

cameraProviderFuture = ProcessCameraProvider.getInstance(this)

if (allPermissionsGranted()) {
previewView.post { startCamera() }
} else {
// request permission
}
}

/**
* 开启相机
*/
private fun startCamera() {
//预览相机
imagePreview = Preview.Builder().apply {
setTargetAspectRatio(AspectRatio.RATIO_16_9)
setTargetRotation(previewView.display.rotation)
}.build()


val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
val camera = cameraProvider.bindToLifecycle(this, cameraSelector,imagePreview)
}, ContextCompat.getMainExecutor(this))
}
//...
}

使用ImageAnalysis进行图片分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

/**
* 开启相机
*/
private fun startCamera() {
//...
//图片分析
imageAnalysis = ImageAnalysis.Builder().apply {
setImageQueueDepth(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
}.build()
//...


val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
val camera = cameraProvider.bindToLifecycle(this, cameraSelector,imagePreview,imageAnalysis)
}, ContextCompat.getMainExecutor(this))
}

/**
* 光强度分析
*/
private class LuminosityAnalyzer : ImageAnalysis.Analyzer {
private var lastAnalyzedTimestamp = 0L

/**
* Helper extension function used to extract a byte array from an
* image plane buffer
*/
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 currentTimestamp = System.currentTimeMillis()
// Calculate the average luma no more often than every second
if (currentTimestamp - lastAnalyzedTimestamp >=
TimeUnit.SECONDS.toMillis(1)) {
// Since format in ImageAnalysis is YUV, image.planes[0]
// contains the Y (luminance) plane
val buffer = image.planes[0].buffer
// Extract image data from callback object
val data = buffer.toByteArray()
// Convert the data into an array of pixel values
val pixels = data.map { it.toInt() and 0xFF }
// Compute average luminance for the image
val luma = pixels.average()
// Log the new luma value
Log.d("CameraX", "Average luminosity: $luma")
// Update timestamp of last analyzed frame
lastAnalyzedTimestamp = currentTimestamp
}
image.close()
}
}

拍照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

/**
* 开启相机
*/
private fun startCamera() {
//...
//拍照
imageCapture = ImageCapture.Builder().apply {
setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
}.build()


val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
val camera = cameraProvider.bindToLifecycle(this, cameraSelector,imagePreview,imageAnalysis,imageCapture)
}, ContextCompat.getMainExecutor(this))
}

/**
* 拍照
*/
private fun takePicture() {
val file = createFile(
outputDirectory,
FILENAME,
PHOTO_EXTENSION
)
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture.takePicture(outputFileOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val msg = "Photo capture succeeded: ${file.absolutePath}"
previewView.post {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
}
}

override fun onError(exception: ImageCaptureException) {
exception.printStackTrace()
val msg = "Photo capture failed: ${exception.message}"
previewView.post {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
}
}
})
}

闪光灯和缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 切换闪光灯状态
*/
private fun toggleTorch() {
if (cameraInfo.torchState.value == TorchState.ON) {
cameraControl.enableTorch(false)
} else {
cameraControl.enableTorch(true)
}
}

/**
* 监听闪光灯状态
*/
private fun setTorchStateObserver() {
cameraInfo.torchState.observe(this, Observer { state ->
if (state == TorchState.ON) {
binding.cameraTorchButton.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_flash_on_24dp
)
)
} else {
binding.cameraTorchButton.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_flash_off_24dp
)
)
}
})
}

/**
* 监听缩放状态
*/
private fun setZoomStateObserver() {
cameraInfo.zoomState.observe(this, Observer { state ->
// state.linearZoom
// state.zoomRatio
// state.maxZoomRatio
// state.minZoomRatio
Log.d(TAG, "${state.linearZoom}")
})
}

// 控制缩放
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
if (linearZoom <= 0.9) {
linearZoom += 0.1f
}
cameraControl.setLinearZoom(linearZoom)
true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (linearZoom >= 0.1) {
linearZoom -= 0.1f
}
cameraControl.setLinearZoom(linearZoom)
true
}
else -> super.onKeyDown(keyCode, event)
}
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×