1. CameraX架构

看官方文档 CameraX架构
有如下这一段话

使用CameraX,借助名为"用例"的抽象概念与设备的相机进行交互。

  • 预览 : 接受用于显示预览的Surface,例如PreviewView
  • 图片分析 : 为分析 (例如机器学习) 提供CPU可访问的缓冲区
  • 图片拍摄 : 拍摄并保存图片
  • 视频拍摄 : 通过VideoCapture拍摄视频和音频

不同用例可以组合使用,也可以同时处于活跃状态。
例如,应用中可以加入预览用例,以便让用户查看进入相机视野的画面
加入图片分析用例,以确定照片里的人物是否在微笑
还可以加入图片拍摄用例,以便在人物微笑时拍摄照片

第一次看的时候,一脸懵逼,“用例”,是个什么鬼玩意。

后来,研究了一下,知道"用例"的英文原文叫做Use Case,CameraX中的每一项操作,对应着一种UseCase

  • 预览 : Preview.java
  • 图片分析 : ImageAnalysis.java
  • 图片拍摄 : ImageCapture.java
  • 视频拍摄 : VideoCapture.java

可以看到,这几个类都是继承自UseCase.java类的

public final class Preview extends UseCase {//...
}
public final class ImageAnalysis extends UseCase {//...
}
public final class ImageCapture extends UseCase {//...
}
public final class VideoCapture extends UseCase {//...
}

接下来让我们来尝试使用一下。

2. 前置操作

首先,我们需要新建一个项目,然后引入依赖

// CameraX core library using the camera2 implementation
def camerax_version = "1.2.0-alpha02" //1.2.0-alpha02
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX VideoCapture library
implementation "androidx.camera:camera-video:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to additionally add CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"

AndroidManifest.xml里添加权限

<!--摄像头权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--具备摄像头-->
<uses-feature android:name="android.hardware.camera.any" />
<!--存储图像或者视频权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--录制音频权限-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

别忘了申请权限

ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO), 123)

3. 预览 : Preview.java

首先修改activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/camera_container"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/black"><androidx.camera.view.PreviewViewandroid:id="@+id/previewView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

修改MainActivity.kt

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var cameraProvider: ProcessCameraProviderprivate var preview: Preview? = nullprivate var camera: Camera? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)//TODO 省略了权限申请,具体看文章中 "前置操作" 部分setUpCamera(binding.previewView)}private fun setUpCamera(previewView: PreviewView) {val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =ProcessCameraProvider.getInstance(this)cameraProviderFuture.addListener({try {cameraProvider = cameraProviderFuture.get()bindPreview(cameraProvider, previewView)} catch (e: Exception) {e.printStackTrace()}}, ContextCompat.getMainExecutor(this))}private fun bindPreview(cameraProvider: ProcessCameraProvider,previewView: PreviewView) {//解除所有绑定,防止CameraProvider重复绑定到Lifecycle发生异常cameraProvider.unbindAll()preview = Preview.Builder().build()camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview)preview?.setSurfaceProvider(previewView.surfaceProvider)}
}

看下效果

4. 图像分析 : ImageAnalysis.java

图像分析用例ImageAnalysis为应用提供可实时分析的图像数据,我们可以对这些图像执行图像处理、计算机视觉或机器学习推断。

val imageAnalysis = ImageAnalysis.Builder()// enable the following line if RGBA output is needed.// .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888).setTargetResolution(Size(1280, 720)).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->val rotationDegrees = imageProxy.imageInfo.rotationDegrees// insert your code here.// 在这里处理图片的解析,比如解析成二维码之类的...// after done, release the ImageProxy objectimageProxy.close()
})

在调用cameraProvider.bindToLifecycle()时,进行传入

cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis
)

5. 拍照 : ImageCapture.java

5.1 仅拍照

这里,我们需要先创建一个imageCapture

imageCapture = ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)//.setTargetAspectRatio(screenAspectRatio)//.setTargetRotation(binding.previewView.display.rotation).build()

然后,在调用cameraProvider.bindToLifecycle()时,进行传入

camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture
)

增加takePicture()方法进行拍照

//进行拍照
private fun takePicture() {imageCapture?.let { imageCapture ->val mainExecutor = ContextCompat.getMainExecutor(this)imageCapture.takePicture(mainExecutor, object : ImageCapture.OnImageCapturedCallback() {override fun onCaptureSuccess(image: ImageProxy) {super.onCaptureSuccess(image)}override fun onError(exception: ImageCaptureException) {super.onError(exception)}})// 让画面闪一下,营造拍照的感觉// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedbinding.root.postDelayed({binding.root.foreground = ColorDrawable(Color.WHITE)binding.root.postDelayed({ binding.root.foreground = null }, 50L)}, 100L)}}
}

5.2 拍照并保存到本地存储

我们也可以拍照后,保存到本地存储中

/** Helper function used to create a timestamped file */
private fun createFile(baseFolder: File, format: String, extension: String) =File(baseFolder, SimpleDateFormat(format, Locale.US).format(System.currentTimeMillis()) + extension)/** Use external media if it is available, our app's file directory otherwise */
fun getOutputDirectory(context: Context): File {val appContext = context.applicationContextval mediaDir = context.externalMediaDirs.firstOrNull()?.let {File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() }}return if (mediaDir != null && mediaDir.exists())mediaDir else appContext.filesDir
}companion object {private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"private const val PHOTO_EXTENSION = ".jpg"
}//进行拍照并保存到本地
private fun takePictureSaveToDisk() {imageCapture?.let { imageCapture ->// Create output file to hold the imageval photoFile = createFile(getOutputDirectory(this), FILENAME, PHOTO_EXTENSION)Log.i(TAG, "photoFile:$photoFile")// Setup image capture metadataval metadata = ImageCapture.Metadata().apply {// Mirror image when using the front cameraisReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT}// Create output options object which contains file + metadataval outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()// Setup image capture listener which is triggered after photo has been takenimageCapture.takePicture(outputOptions,ContextCompat.getMainExecutor(this),object : ImageCapture.OnImageSavedCallback {override fun onError(exc: ImageCaptureException) {Log.e(TAG, "Photo capture failed: ${exc.message}", exc)}override fun onImageSaved(output: ImageCapture.OutputFileResults) {val savedUri = output.savedUri ?: Uri.fromFile(photoFile)Log.d(TAG, "Photo capture succeeded: $savedUri")// Implicit broadcasts will be ignored for devices running API level >= 24// so if you only target API level 24+ you can remove this statementif (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {application.sendBroadcast(Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri))}// If the folder selected is an external media directory, this is// unnecessary but otherwise other apps will not be able to access our// images unless we scan them using [MediaScannerConnection]val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(savedUri.toFile().extension)MediaScannerConnection.scanFile(application,arrayOf(savedUri.toFile().absolutePath),arrayOf(mimeType)) { _, uri ->Log.d(TAG, "Image capture scanned into media store: $uri")}}})// 让画面闪一下,营造拍照的感觉// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedbinding.root.postDelayed({binding.root.foreground = ColorDrawable(Color.WHITE)binding.root.postDelayed({ binding.root.foreground = null }, 50L)}, 100L)}}
}

然后,我们可以在相册里找到这张图片了,图片的真实位置位于/storage/emulated/0/Android/media/你的包名/项目名/中。

6. 视频录制 : VideoCapture.java

视频录制用的是VideoCapture

videoCapture = VideoCapture.Builder()//.setTargetRotation(previewView.getDisplay().getRotation()).setVideoFrameRate(25).setBitRate(3 * 1024 * 1024).build()

在调用cameraProvider.bindToLifecycle()时,进行传入。

camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture
)

需要注意的是,videoCapture无法和imageAnalysisimageCapture一起使用。
如果同一个页面中这几个功能融合在一起,则需要通过标志位来进行判断。

if (isVideo) {mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,preview, videoCapture);
} else {mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,preview, imageCapture, imageAnalysis);
}

开始录制

private val RECORDED_FILE_NAME = "recorded_video"
private val RECORDED_FILE_NAME_END = "video/mp4"@SuppressLint("RestrictedApi")
private fun startRecording() {//TODO 这里省略了RECORD_AUDIO、PERMISSION_GRANTED权限的判断val contentValues = ContentValues()contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,RECORDED_FILE_NAME + "_" + System.currentTimeMillis())contentValues.put(MediaStore.MediaColumns.MIME_TYPE, RECORDED_FILE_NAME_END)val outputFileOptions = VideoCapture.OutputFileOptions.Builder(getContentResolver(),MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues).build()videoCapture.startRecording(outputFileOptions,ContextCompat.getMainExecutor(this),object : VideoCapture.OnVideoSavedCallback {override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {Log.i(TAG, "视频保存成功:${outputFileResults.savedUri}")}override fun onError(videoCaptureError: Int,message: String,cause: Throwable?) {Log.i(TAG, "当出现异常 cause:$cause")}})
}

停止视频录制

videoCapture.stopRecording()

当我们执行停止视频录制之后,就可以在相册里看到多了一个录制的视频了。

介绍了CameraX里一些常用的UseCase,我们接下来来看下CameraX中的其他一些功能。

7. 切换前后摄像头

我们之前使用cameraProvider.bindToLifecycle()的时候,有一个参数是CameraSelector
CameraX默认给我们提供了前置摄像头和后置摄像头的CameraSelector

public final class CameraSelector {@NonNullpublic static final CameraSelector DEFAULT_FRONT_CAMERA =new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build();@NonNullpublic static final CameraSelector DEFAULT_BACK_CAMERA =new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();//...
}

我们去切换摄像头的时候,就是重新调用一下bindPreview方法,传入新的cameraSelector值就好了

private fun bindPreview(cameraProvider: ProcessCameraProvider,previewView: PreviewView,cameraSelector : CameraSelector) {// 解除所有绑定,防止CameraProvider重复绑定到Lifecycle发生异常cameraProvider.unbindAll()preview = Preview.Builder().build()camera = cameraProvider.bindToLifecycle(this,cameraSelector, preview)preview?.setSurfaceProvider(previewView.surfaceProvider)}

CameraX还为我们提供了判断前置/后置摄像头是否存在的方法

/** Returns true if the device has an available back camera. False otherwise */
private fun hasBackCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}/** Returns true if the device has an available front camera. False otherwise */
private fun hasFrontCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
}

看下效果

8. 对焦

当点击androidx.camera.view.PreviewView的时候,去调用CameraX的对焦方法startFocusAndMetering()就好了。

onCreate()中添加如下代码

binding.previewView.setOnTouchListener { view, event ->val action = FocusMeteringAction.Builder(binding.previewView.getMeteringPointFactory().createPoint(event.getX(), event.getY())).build();showTapView(event.x.toInt(), event.y.toInt())camera?.getCameraControl()?.startFocusAndMetering(action)true
}

增加showTapView()

private fun showTapView(x: Int, y: Int) {val popupWindow = PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT)val imageView = ImageView(this)imageView.setImageResource(R.drawable.ic_focus_view)popupWindow.contentView = imageViewpopupWindow.showAsDropDown(binding.previewView, x, y)binding.previewView.postDelayed({ popupWindow.dismiss() }, 600)binding.previewView.playSoundEffect(SoundEffectConstants.CLICK)
}

看下效果

9. 缩放

通过GestureDetector监听缩放事件,然后在回调的时候进行执行如下代码,就可以使用双指放大缩小图像

override fun zoom(delta: Float) {val zoomState = camera?.cameraInfo?.zoomStatezoomState?.value?.let {val currentZoomRatio = it.zoomRatiocamera?.cameraControl?.setZoomRatio(currentZoomRatio * delta)}
}

缩放操作具体详见这篇文章 Android使用CameraX实现相机快速实现放大缩小

10. 本文Demo下载

本文的源码Demo详见 : Android CameraX Demo : 实现预览/拍照/录制视频/图片分析/对焦/切换摄像头等操作

推荐阅读 我的另一篇关于Camera的文章 : Android 从零开发一个简易的相机App

参考
Android Developer | CameraX
新技术介绍来了,CameraX 一统江湖?

Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作相关推荐

  1. Android视频编辑器(二)预览、录制视频加上水印和美白磨皮效果

    前言      这是视频编辑器系列的第二篇文章,在上篇文章中,我们讲解了利用OpenGl和SurfaceView进行视频预览,MediaCodec和MeidaMuxer进行视频录制和断点续录.而这篇主 ...

  2. android拍照功能无预览,Android快速实现无预览拍照功能

    本文实例为大家分享了Android实现无预览拍照功能的具体代码,供大家参考,具体内容如下 实现思路: 把预览的SurfaceView的宽高设置为肉眼看不出的值,例如0.1dp,其他的就是自定义相机的标 ...

  3. Android视频编辑器(一)通过OpenGL预览、录制视频以及断点续录等

    前言 如今的视频类app可谓是如日中天,火的不行.比如美拍.快手.VUE.火山小视频.抖音小视频等等.而这类视频的最基础和核心的功能就是视频录制和视频编辑功能.包括了手机视频录制.美白.加滤镜.加水印 ...

  4. Android的camerax预览拍照

    前面讲了,camera1和camera2的简单使用.从前文可以看出来,API 的调用还是比较繁琐的,而且像一些最佳尺寸,角度等还得自己算.由于camera1支持的特性很少,camera2流程又很繁琐, ...

  5. android 摄像头花屏_关于Android 4.4相机预览、录像花屏的问题的解决方法

    关于Android 4.4相机预览.录像花屏的问题的解决方法 系统: lc android4.4 在做前后摄像头录像的时候,发现会出现花屏的时候,但不是必现,可能会在某一次重启之后会出现,而且出现之后 ...

  6. 【流媒體】Android 实时视频采集—Camera预览采集

    [流媒體]Android 实时视频采集-Cameara预览采集 SkySeraph Mar 26th 2012  SZ Email:skyseraph00@163.com 更多精彩请直接访问SkySe ...

  7. 谷歌推Android 8.1开发者预览版,Pixel 2神秘芯片终于派上用场了

    夏乙 编译整理 量子位 出品 | 公众号 QbitAI 量子位上周报道过,Google在新手机Pixel 2体内藏了个彩蛋:定制移动芯片Pixel Visual Core.这个芯片正在休眠,它的威力要 ...

  8. 骁龙camera预览拍照录像流程 api1和api2

    首先说api1的预览流程 CameraActivity的onCreate先区分是不是安全模式(锁屏进入就是安全模式),如果是安全模式会设置flag indowManager.LayoutParams. ...

  9. Android 使用 CameraX 快速实现仿微信短视频录制

    Android 使用 CameraX 快速实现仿微信短视频录制(轻触拍照.长按录像) https://github.com/ldlywt/MyCameraX 微信短视频android端 https:/ ...

最新文章

  1. 4个开源在线调查工具
  2. java list 截取部分数据_Java List.subList()方法:获取列表中指定范围的子列表
  3. 人工智能缺陷与误觉:让机器产生幻觉的「怪异事件」
  4. Solaris 10绝非儿戏
  5. word无法打开请去应用商店_word文档打不开的4种解决方法
  6. Java04异常、断言、日志和调试
  7. 华为云服务器实战 之 Gitlab安装与配置使用
  8. 如何为prel语言加载模块_WebAssembly 系列(第四部分)- 创建和使用WebAssembly模块...
  9. 手慢无!小米5G手机已抢光
  10. 2015计算机类专业课类试卷,2015计算机专业知识试题.doc
  11. java环境安装1359错误_解决The valid characters are defined in RFC 7230 and RFC 3986错误问题...
  12. 51单片机程序0到60计数个位逢5和6不显示,十位逢1不显示http://www.51hei.com/bbs/mcu-h24-1.html
  13. 如何复制出计算机缓存中的歌曲,怎么把哔哩哔哩里缓存的视频中的音频单独提取出来。...
  14. socket编程:recv()函数详解
  15. 手写操作系统(2)——代码是怎么运行的?
  16. termux安装numpy,matplotlib,pandas
  17. 计算机一级vlookup函数的使用方法,电子档Excel中vlookup函数的使用方法(图解详细说明)...
  18. Android 悬浮窗的使用(2)
  19. SOCKET【3】-select+getsockopt客户端检测connect是否成功
  20. 【点云配准-4PCS(2008)】4-Points Congruent Sets for Robust Pairwise Surface Registration

热门文章

  1. OPENFILER构建软iSCSI multipath实现多路径聚合(一)
  2. 一寸等于多少厘米,一寸照片尺寸是多少?
  3. 《程序开发心理学》几点核心理解
  4. dm服务器未能启动,DM 达梦数据库 创建服务 无法创建目录_REPLACE_SELF_DM_HOME 错误解决方法...
  5. android微信图片选择框架,Android仿微信图片选择器ImageSelector使用详解
  6. C++实现快速打印乘法口诀表
  7. CSS深入理解之line-height
  8. Matlab数字图像处理学习记录【2】——亮度变换与空间滤波
  9. 超好看的粒子效果文字动画特效HTML5源码
  10. 【iOS】网络操作与AFNetworking