CameraX 是一个用于 Android 相机开发的 Jetpack 组件,它简化了相机功能的完成过程,并供给了一套一致的 API 接口,支撑搭载 Android 5.0 及以上的设备,保证各设备间的一致性,支撑大多数常见的相机用例,例如预览,图片拍照,图片剖析,视频拍照等。
增加依靠
val cameraxVersion = "1.2.1"
implementation("androidx.camera:camera-core:${cameraxVersion}")
implementation("androidx.camera:camera-camera2:${cameraxVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraxVersion}")
implementation("androidx.camera:camera-video:${cameraxVersion}")
implementation("androidx.camera:camera-view:${cameraxVersion}")
implementation("androidx.camera:camera-extensions:${cameraxVersion}")
需求的权限如下:
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
这些权限需求动态请求的,这儿不再赘述。其间,<uses-feature> 标签用于声明应用程序所需求的硬件或软件功能,这儿指相机功能,required 特点指定应用程序是否对该功能的要求是有必要的,false 表明相机功能是可选的。
预览
预览运用 PreviewView,这是一种能够剪裁,缩放和旋转以保证正确显现的 View,当相机处于活动状态时,图片预览会流式传输到 PreviewView 中的 Surface。
增加布局
<androidx.camera.view.PreviewView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
请求 ProcessCameraProvider,选择相机并绑定生命周期和用例即可,代码如下:
class CameraActivity : AppCompatActivity() {
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private lateinit var binding: ActivityCameraBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_camera)
// 请求 CameraProvider,并验证它能否在视图创立后成功初始化。
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
preview.setSurfaceProvider(binding.previewView.surfaceProvider)
// 绑定生命周期和用例
cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)
}, ContextCompat.getMainExecutor(this))
}
}
图片拍照
在上面的代码中 bindToLifecycle 增加个 ImageCapture 参数。
private var imageCapture: ImageCapture? = null
imageCapture = ImageCapture.Builder().build()
cameraProvider.bindToLifecycle(
this as LifecycleOwner,
cameraSelector,
imageCapture,
preview
)
然后履行拍照办法,将图片保存在相册中即可,拍照代码如下:
private fun takePhoto() {
//创立用于保存图片的 MediaStore 内容值,这儿运用时间戳,保证 MediaStore 中的显现名是仅有的。
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "img_${System.currentTimeMillis()}")
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")
}
}
// 创立一个 OutputFileOptions 对象,指定所需的输出内容,这儿输出保存在 MediaStore 中。
val outputOptions = ImageCapture.OutputFileOptions
.Builder(
contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
// 拍照
imageCapture?.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this@CameraActivity),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
Log.i(TAG, "onImageSaved")
}
override fun onError(exception: ImageCaptureException) {
Log.i(TAG, "onError: ${exception.message}")
}
})
}
图片剖析
能够运用 ImageAnalysis 进行图片剖析,完成 ImageAnalysis.Analyzer 接口的类中的 analyze 函数。
private class MyImageAnalyzer : ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) { // 在这儿编写图画剖析的详细逻辑,这儿做个简略演示。
// 宽高
val imageWidth = image.width
val imageHeight = image.height
// 依据需求处理每个平面的图画数据
image.planes.forEach {
// 获取图画平面的行跨度,即相邻两行之间的字节偏移量。
val rowStride = it.rowStride
// 获取图画平面的像素跨度,即相邻两个像素之间的字节偏移量。
val pixelStride = it.pixelStride
// 获取图画平面的数据缓冲区,能够通过该缓冲区读取或写入图画数据。
val buffer = it.buffer
// buffer.remaining 返回剩下可读取或写入的字节数。
val byteArray = ByteArray(buffer.remaining())
// 转化为字节数组
buffer.get(byteArray)
// 处理完图画后,开释资源。
buffer.clear()
}
image.close()
}
}
其间,planes 是一个数组,其间包含了多个图画平面。关于彩色图画,一般会有三个平面,分别对应赤色,绿色和蓝色通道。您能够通过 planes[0],planes[1],planes[2] 等进行访问。
有些人可能会问:什么是图画平面?其实,在相机图画捕获过程中,图画会以多个平面的办法存储,这种存储办法称为平面布局,每个平面都包含了图画数据的一部分。关于彩色图画,常见的平面布局是 YUV 或 RGBA。提到这俩咱们应该就清楚了,在处理相机图画时,需求依据详细的平面布局,将图画数据从每个平面提取出来,并进行相应的处理。
当运用 image.planes 来获取图画数据时,每个平面都包含一个字节缓冲区。例如,关于 RGBA 平面布局,image.planes[0].buffer 是指 R 平面的数据缓冲区,存储了图画的赤色通道信息,关于 YUV 平面布局,image.planes[0].buffer 一般是 Y 平面的数据缓冲区,存储了图画的亮度信息。
最终,将剖析器设置进去即可,如下所示:
val imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(ContextCompat.getMainExecutor(this), MyImageAnalyzer())
}
cameraProvider.bindToLifecycle(
this as LifecycleOwner,
cameraSelector,
imageCapture,
imageAnalyzer,
preview
)
视频拍照
捕获体系一般会录制视频流和音频流,对其进行紧缩,对这两个流进行多路复用,然后将生成的流写入磁盘。
视频拍照运用 VideoCapture,相同,咱们需求将其绑定到 Lifecycle,如下所示:
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
preview.setSurfaceProvider(binding.previewView.surfaceProvider)
val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.HIGHEST))
.build()
videoCapture = VideoCapture.withOutput(recorder)
cameraProvider.bindToLifecycle(
this as LifecycleOwner,
cameraSelector,
preview,
videoCapture
)
}, ContextCompat.getMainExecutor(this))
视频拍照办法如下:
private fun takeVideo() {
// 如果有正在进行的录制操作,请将其停止并开释当前的 recording
val curRecording = recording
if (curRecording != null) {
curRecording.stop()
recording = null
return
}
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "video_${System.currentTimeMillis()}")
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")
}
}
// 构建 MediaStoreOutputOptions 实例
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@CameraActivity,
Manifest.permission.RECORD_AUDIO
) == PermissionChecker.PERMISSION_GRANTED
) {
// 启用音频
withAudioEnabled()
}
}?.start(ContextCompat.getMainExecutor(this)) {
when (it) {
is VideoRecordEvent.Start -> {
Log.i(TAG, "indicates the start of recording")
}
is VideoRecordEvent.Finalize -> {
if (!it.hasError()) {
Log.d(TAG, "Video capture succeeded: ${it.outputResults.outputUri}")
} else {
recording?.close()
recording = null
Log.e(TAG, "Video capture ends with error: ${it.error}")
}
}
}
}
}
调用该办法即可进行视频采集了,录制的是 mp4 文件,如果想要停止录制,调用如下:
recording?.stop()
recording = null