使用cameraX 仿一甜相机
前言
1、导入相关库 2、绑定LifeCycle生命周期并开启预览流 3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉 4、拍照 5、录像 6、聚焦 7、切换摄像头 8、缩放 9、闪光灯 10、照明、补光 11、Extensions 扩展程序使用
前言
CameraX 是jetpack 组件库中的一个非常重要的API,不同于Camera和Camera2,CameraX 在api解耦性上做出了非常大的调整。其中:
- 1、新增了生命周期绑定管理,解决了老版本Camera的内存泄漏问题
- 2、分辨率自动找寻最接近匹配问题,解决了开发者手动去查询支持分辨率列表(从中去找寻最匹配的分辨率)
- 3、增加ImageAnalysis图像分析,可以在图像输出前对像素进行YUV 转换并且预处理等操作
- 4、增加了 Extensions 扩展程序包括(AUTO、HDR、焦外成像(BOKEH)、夜景(NIGHT)、脸部照片修复(FACE_RETOUCH))等模式,当然,目前国内手机厂商都还没兼容当前的扩展模式,大部分还只有三星手机支持。期待
手机厂商抓紧适配,减少不必要的算法融合。
以上的一些新增内容,足够我们去替换老板本的Camera或者Camera2 Api。下面就是CameraX 的基础用法和Extensions 用法。
def camerax_version = "1.2.0-alpha04"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-video:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
fun openCamera(){
val cameraProviderFuture = ProcessCameraProvider.getInstance(mLifecycleOwner!!)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(preview?.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(
mLifecycleOwner!!, cameraSelector, preview
)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(mLifecycleOwner!!))
}
- 3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
camera = cameraProvider.bindToLifecycle(
mLifecycleOwner!!, cameraSelector, imageCapture, preview
)
override fun takePhoto() {
val imageCapture = imageCapture ?: return
val name = SimpleDateFormat("yyyy-MM-dd", Locale.US)
.format(System.currentTimeMillis())
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
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")
}
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(
mLifecycleOwner?.contentResolver!!,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(mLifecycleOwner!!),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val msg = "Photo capture succeeded: ${output.savedUri}"
Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
}
)
}
@SuppressLint("CheckResult")
override fun takeVideo() {
val videoCapture = this.videoCapture ?: return
if (recording != null) {
recording?.stop()
recording = null
return
}
val name = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)
.format(System.currentTimeMillis())
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
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")
}
}
val mediaStoreOutputOptions = mLifecycleOwner?.contentResolver?.let {
MediaStoreOutputOptions
.Builder(it, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build()
}
recording = videoCapture.output
.prepareRecording(mLifecycleOwner!!, mediaStoreOutputOptions!!)
.apply {
if (ActivityCompat.checkSelfPermission(
mLifecycleOwner!!,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
withAudioEnabled()
}
}
.start(ContextCompat.getMainExecutor(mLifecycleOwner!!)) { recordEvent ->
when (recordEvent) {
is VideoRecordEvent.Start -> {
Toast.makeText(mLifecycleOwner, "开始录制", Toast.LENGTH_SHORT)
.show()
}
is VideoRecordEvent.Finalize -> {
if (!recordEvent.hasError()) {
val msg = "Video capture succeeded: " +
"${recordEvent.outputResults.outputUri}"
Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT)
.show()
Log.d(TAG, msg)
} else {
recording?.close()
recording = null
Log.e(
TAG, "Video capture ends with error: " +
"${recordEvent.error}"
)
}
}
}
}
}
聚焦分为三种模式
previewView.setOnTouchListener((view, motionEvent) -> {
val meteringPoint = previewView.meteringPointFactory
.createPoint(motionEvent.x, motionEvent.y)
…
}
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
surfaceView.display,
camera.cameraInfo,
surfaceView.width,
surfaceView.height
)
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
imageWidth,
imageHeight,
imageAnalysis)
基于以上的point 创建方式,可以封装为自动聚焦和手动聚焦
@SuppressLint("RestrictedApi")
override fun focus(x: Float, y: Float, auto: Boolean) {
cameraControl?.cancelFocusAndMetering()
val createPoint: MeteringPoint = if (auto) {
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
preview?.display!!,
camera?.cameraInfo!!,
preview?.width?.toFloat()!!,
preview?.height?.toFloat()!!
)
meteringPointFactory.createPoint(x, y)
} else {
val meteringPointFactory = preview?.meteringPointFactory
meteringPointFactory?.createPoint(x, y)!!
}
val build = FocusMeteringAction.Builder(createPoint, FLAG_AF)
.setAutoCancelDuration(3, TimeUnit.SECONDS)
.build()
val future = cameraControl?.startFocusAndMetering(build)
future?.addListener({
try {
if (future.get().isFocusSuccessful) {
Log.e(TAG, "聚焦成功")
} else {
Log.e(TAG, "聚焦失败")
}
} catch (e: Exception) {
Log.e(TAG, "异常" + e.message)
}
}, ContextCompat.getMainExecutor(mLifecycleOwner!!))
}
override fun switchCamera() {
mFacingFront = !mFacingFront
cameraProvider?.unbindAll()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(if (mFacingFront) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK)
.build()
imageCapture = ImageCapture.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
camera = cameraProvider?.bindToLifecycle(
mLifecycleOwner!!,
cameraSelector,
imageCapture,
videoCapture,
mPreView
)
}
override fun zoom(out: Boolean) {
val zoomState = camera?.cameraInfo?.zoomState
val zoomRatio: Float? = zoomState?.value?.zoomRatio
val maxZoomRatio: Float? = zoomState?.value?.maxZoomRatio
val minZoomRatio: Float? = zoomState?.value?.minZoomRatio
if (out) {
if (zoomRatio!! < maxZoomRatio!!) {
cameraControl?.setZoomRatio((zoomRatio + zoomCoefficient))
}
} else {
if (zoomRatio!! > minZoomRatio!!) {
cameraControl?.setZoomRatio((zoomRatio - zoomCoefficient))
}
}
}
val flashMode =
if (cameraParams?.mSplashOn == true && cameraParams?.mFacingFront == false) {
ImageCapture.FLASH_MODE_ON
} else {
ImageCapture.FLASH_MODE_OFF
}
imageCapture?.flashMode = flashMode
cameraControl?.enableTorch(cameraParams?.torchSwitch!!)
以下是Extensions 支持的模式。
public final class ExtensionMode {
public static final int NONE = 0;
public static final int BOKEH = 1;
public static final int HDR = 2;
public static final int NIGHT = 3;
public static final int FACE_RETOUCH = 4;
public static final int AUTO = 5;
使用方式
mLifecycleOwner?.lifecycleScope?.launch {
val extensionsManager = ExtensionsManager.getInstanceAsync(mLifecycleOwner!!, cameraProvider!!).await()
if (extensionsManager.isExtensionAvailable(
cameraSelector,
cameraParams?.extensionMode!!
)
) {
Log.d(TAG, "支持" + cameraParams?.extensionMode)
val extensionId = extensionsManager.getExtensionEnabledCameraSelector(
cameraSelector,
cameraParams?.extensionMode!!
)
startPreView = true
bindCameraId(extensionId)
} else {
startPreView = false
Log.d(TAG, "不支持" + cameraParams?.extensionMode)
}
}
private fun bindCameraId(cameraSelector: CameraSelector) {
try {
cameraProvider?.unbindAll()
camera = cameraProvider?.bindToLifecycle(
mLifecycleOwner!!,
cameraSelector,
mPreView,
imageCapture,
videoCapture
)
if (!isInt) {
isInt = true
callBack?.ratioCallBack(cameraParams?.mRatioType)
}
cameraControl = camera?.cameraControl
focus(preview?.width?.div(2f)!!, preview?.height?.div(2f)!!, true)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}
由于本人的设备不支持扩展程序,所以就没有展示效果了,希望国能厂商能升级ROM 支持一下吧。 后续有支持的设备后,会更新效果图上来。 代码已上传: https://github.com/ljlstudio/KtMvvm/tree/master/demo/src/main/java/com/kt/ktmvvm/jetpack/camerax
|