Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程
Posted 梦想改变生活
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程相关的知识,希望对你有一定的参考价值。
使用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 支持一下吧。
后续有支持的设备后,会更新效果图上来。
代码已上传:
https://github.com/ljlstudio/KtMvvm/tree/master/demo/src/main/java/com/kt/ktmvvm/jetpack/camerax
Android开发笔记(一百八十二)使用CameraX录像
通过CameraX实现录像功能的话,初始化相机的步骤与拍照时大同小异,区别在于增加了对视频捕捉器VideoCapture的处理。需要修改的代码主要有三个地方,分别说明如下。
第一个地方是在AndroidManifest.xml里补充声明录音权限,完整的权限声明配置如下所示:
<!-- 相机 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 录音 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
第二个地方是在重置相机的resetCamera方法中,构建完图像捕捉器对象,还要构建视频捕捉器对象,并设置视频的宽高比例、视频帧率、比特率(视频每秒录制的比特数)、旋转角度等录制参数。视频捕捉器的构建代码示例如下:
if (mCameraMode == MODE_RECORD) // 录像
// 构建一个视频捕捉器
mVideoCapture = new VideoCapture.Builder()
.setTargetAspectRatio(mAspectRatio) // 设置宽高比例
.setVideoFrameRate(60) // 设置视频帧率
.setBitRate(3 * 1024 * 1024) // 设置比特率
.setTargetRotation(rotation) // 设置旋转角度
.setAudioRecordSource(MediaRecorder.AudioSource.MIC)
.build();
第三个地方是在绑定摄像头的bindCamera方法中,对于录像操作来说,需要把视频捕捉器绑定到相机提供器的生命周期,而非绑定图像捕捉器。绑定视频捕捉器的代码片段示例如下:
if (captureMode == MODE_RECORD) // 录像
// 把相机选择器、预览视图、视频捕捉器绑定到相机提供器的生命周期
Camera camera = mCameraProvider.bindToLifecycle(
mOwner, mCameraSelector, mPreview, mVideoCapture);
初始化相机之后,即可调用视频捕捉器的startRecording方法开始录像,或者调用stopRecording方法停止录像。录像代码示例如下:
private String mVideoPath; // 视频保存路径
private int MAX_RECORD_TIME = 15; // 最大录制时长,默认15秒
// 获取视频的保存路径
public String getVideoPath()
return mVideoPath;
// 开始录像
public void startRecord(int max_record_time)
MAX_RECORD_TIME = max_record_time;
bindCamera(MODE_RECORD); // 绑定摄像头
mVideoPath = String.format("%s/%s.mp4", mMediaDir, DateUtil.getNowDateTime());
VideoCapture.Metadata metadata = new VideoCapture.Metadata();
// 构建视频捕捉器的输出选项
VideoCapture.OutputFileOptions options = new VideoCapture.OutputFileOptions.Builder(new File(mVideoPath))
.setMetadata(metadata).build();
// 开始录像动作
mVideoCapture.startRecording(options, mExecutorService, new VideoCapture.OnVideoSavedCallback()
@Override
public void onVideoSaved(VideoCapture.OutputFileResults outputFileResults)
mHandler.post(() -> bindCamera(MODE_PHOTO));
mStopListener.onStop("录制完成的视频路径为"+mVideoPath);
@Override
public void onError(int videoCaptureError, String message, Throwable cause)
mHandler.post(() -> bindCamera(MODE_PHOTO));
mStopListener.onStop("录制失败,错误信息为:"+cause.getMessage());
);
// 限定时长到达之后自动停止录像
mHandler.postDelayed(() -> stopRecord(), MAX_RECORD_TIME*1000);
// 停止录像
public void stopRecord()
mVideoCapture.stopRecording(); // 视频捕捉器停止录像
当然,录像功能也要先在布局文件中添加CameraXView节点。为了方便观察当前已录制的时长,还可以在布局文件中添加计时器节点Chronometer。接着给Java代码补充CameraXView对象的初始化以及录像动作,其中关键代码示例如下:
private CameraXView cxv_preview; // 声明一个增强相机视图对象
private Chronometer chr_cost; // 声明一个计时器对象
private ImageView iv_record; // 声明一个图像视图对象
private boolean isRecording = false; // 是否正在录像
// 初始化相机
private void initCamera()
// 打开增强相机,并指定停止录像监听器
cxv_preview.openCamera(this, CameraXView.MODE_RECORD, (result) ->
runOnUiThread(() ->
chr_cost.setVisibility(View.GONE);
chr_cost.stop(); // 停止计时
iv_record.setImageResource(R.drawable.record_start);
iv_record.setEnabled(true);
isRecording = false;
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
);
);
// 处理录像动作
private void dealRecord()
if (!isRecording)
iv_record.setImageResource(R.drawable.record_stop);
cxv_preview.startRecord(15); // 开始录像
chr_cost.setVisibility(View.VISIBLE);
chr_cost.setBase(SystemClock.elapsedRealtime()); // 设置计时器的基准时间
chr_cost.start(); // 开始计时
isRecording = !isRecording;
else
iv_record.setEnabled(false);
cxv_preview.stopRecord(); // 停止录像
运行测试App,打开录像界面的初始效果如下图所示,此时除了预览画面外,界面下方还展示录制按钮。
点击录制按钮开始录像,正在录像的界面如下图所示,此时录制按钮换成了暂停图标,其上方也跳动着已录制时长的数字。
以上是关于Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程的主要内容,如果未能解决你的问题,请参考以下文章