使用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 用法。
CameraX仿一甜相机
- 1、导入相关库
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}"
- 2、绑定LifeCycle生命周期并开启预览流
fun openCamera(){
val cameraProviderFuture = ProcessCameraProvider.getInstance(mLifecycleOwner!!)
cameraProviderFuture.addListener({
// 绑定生命周期
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview 预览流
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
)
- 4、拍照
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)
}
}
)
}
- 5、录像
@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}"
)
}
}
}
}
}
- 6、聚焦
聚焦分为三种模式
//使用PreviewView。用于自动聚焦,在previewView中创建一个坐标点
previewView.setOnTouchListener((view, motionEvent) -> {
val meteringPoint = previewView.meteringPointFactory
.createPoint(motionEvent.x, motionEvent.y)
…
}
//使用DisplayOrientedMeteringPointFactory如果SurfaceView / TextureView用于
//预览。请注意,如果预览在视图中缩放或裁剪,
//正确转换坐标是应用程序的责任
//这样工厂的宽度和高度代表完整的预览FOV。
//和(x,y)传入创建MeteringPoint可能需要调整
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
surfaceView.display,
camera.cameraInfo,
surfaceView.width,
surfaceView.height
)
//使用SurfaceOrientedMeteringPointFactory如果点指定在
//图形分析ImageProxy。
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
imageWidth,
imageHeight,
imageAnalysis)
基于以上的point 创建方式,可以封装为自动聚焦和手动聚焦
/**
* 聚焦
* @param auto 聚焦模式
*/
@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!!))
}
- 7、切换摄像头
/**
* 切换镜头
*/
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
)
}
- 8、缩放
/**
* 缩放
*/
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))
}
}
}
- 9、闪光灯
//闪光灯模式
val flashMode =
if (cameraParams?.mSplashOn == true && cameraParams?.mFacingFront == false) {
ImageCapture.FLASH_MODE_ON
} else {
ImageCapture.FLASH_MODE_OFF
}
imageCapture?.flashMode = flashMode
- 10、照明、补光
cameraControl?.enableTorch(cameraParams?.torchSwitch!!)
- 11、Extensions 扩展程序使用
以下是Extensions 支持的模式。
public final class ExtensionMode {
/**正常无效果 */
public static final int NONE = 0;
/**
焦外成像
*/
public static final int BOKEH = 1;
/**
HDR
*/
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 {
//获取manager
val extensionsManager = ExtensionsManager.getInstanceAsync(mLifecycleOwner!!, cameraProvider!!).await()
//判断设备是否支持扩展程序
if (extensionsManager.isExtensionAvailable(
cameraSelector,
cameraParams?.extensionMode!!
)
) {
//如果支持,则传入对应的mode 模式,如BOKEH
Log.d(TAG, "支持" + cameraParams?.extensionMode)
val extensionId = extensionsManager.getExtensionEnabledCameraSelector(
cameraSelector,
cameraParams?.extensionMode!!
)
startPreView = true
bindCameraId(extensionId)
} else {
startPreView = false
Log.d(TAG, "不支持" + cameraParams?.extensionMode)
}
}
/**
* 绑定相机id
*/
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 支持一下吧。
后续有支持的设备后,会更新效果图上来。