书接上回。
首先还是看谷歌的官方文档:
https://developer.android.com/media/camera/camerax?hl=zh-cn
https://developer.android.com/codelabs/camerax-getting-started?hl=zh-cn#1
注:这里大部分内容也来自谷歌文档。
官方文档用的是Kotlin,和Java也大差不差。看看流程就好。
API 级别设置为 21。
我觉得其实从App这个层面,用camera1,camera2还是camerax其实都并不重要,很多重大的改变对app层应该都是不可见的。为什么还要折腾上层呢?这个就要问谷歌的设计师了。。。
1 环境配置
在Gardle中增加依赖,我理解就是增加底层库,这几个应该就是camerax所使用的Framework的so,下面应该是AIDL调用的HAL。这个部分和底层驱动关系就很大了,后面还会单独写一篇。
dependencies {def camerax_version = "1.1.0-beta01"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}"
}
同时要增加Java8和viewBinding的支持。
compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}buildFeatures {viewBinding true}
在Layout的activity_main.xml中使用PreviewView。这个应该是camerax的控件。
<androidx.camera.view.PreviewViewandroid:id="@+id/viewFinder"android:layout_width="match_parent"android:layout_height="match_parent" />
增加两个按键,分别是takephoto和capturevideo,并增加按键事件。
// Set up the listeners for take photo and video capture buttonsviewBinding.imageCaptureButton.setOnClickListener { takePhoto() }viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }
在AndroidManifest.xml中增加权限。
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28" />
在运行时会让你授权。
刚开始运行时,要检查权限。
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults:IntArray) {if (requestCode == REQUEST_CODE_PERMISSIONS) {if (allPermissionsGranted()) {startCamera()} else {Toast.makeText(this,"Permissions not granted by the user.",Toast.LENGTH_SHORT).show()finish()}}
}
2 CameraX调用代码
主要用到的的几个包:
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
2.1 提供图像预览:
大体的流程就是首先取得surface,然后使用cameraProvider.bindToLifecycle,将surface作为参数传进去。
private fun startCamera() {val cameraProviderFuture = ProcessCameraProvider.getInstance(this)cameraProviderFuture.addListener({// Used to bind the lifecycle of cameras to the lifecycle ownerval cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()// Previewval preview = Preview.Builder().build().also {it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)}// Select back camera as a defaultval cameraSelector = CameraSelector.DEFAULT_BACK_CAMERAtry {// Unbind use cases before rebindingcameraProvider.unbindAll()// Bind use cases to cameracameraProvider.bindToLifecycle(this, cameraSelector, preview)} catch(exc: Exception) {Log.e(TAG, "Use case binding failed", exc)}}, ContextCompat.getMainExecutor(this))
}
2.2 拍照:
可以看到,基本上就是围绕着imageCapture.takePicture这个方法。name,contentValues,outputOptions都是作为参数传进去。
private fun takePhoto() {// Get a stable reference of the modifiable image capture use caseval imageCapture = imageCapture ?: return// Create time stamped name and MediaStore entry.val name = SimpleDateFormat(FILENAME_FORMAT, 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")}}// Create output options object which contains file + metadataval outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver,MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues).build()// Set up 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 funonImageSaved(output: ImageCapture.OutputFileResults){val msg = "Photo capture succeeded: ${output.savedUri}"Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()Log.d(TAG, msg)}})
}
2.3 拍视频:
基本就是围绕着videoCapture.output。name,contentValues,mediaStoreOutputOptions都是作为参数使用。在output中,好像是使用了lambda函数,弄了一些内置行为。
// Implements VideoCapture use case, including start and stop capturing.
private fun captureVideo() {val videoCapture = this.videoCapture ?: returnviewBinding.videoCaptureButton.isEnabled = falseval curRecording = recordingif (curRecording != null) {// Stop the current recording session.curRecording.stop()recording = nullreturn}// create and start a new recording sessionval name = SimpleDateFormat(FILENAME_FORMAT, Locale.US).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 = MediaStoreOutputOptions.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues).build()recording = videoCapture.output.prepareRecording(this, mediaStoreOutputOptions).apply {if (PermissionChecker.checkSelfPermission(this@MainActivity,Manifest.permission.RECORD_AUDIO) ==PermissionChecker.PERMISSION_GRANTED){withAudioEnabled()}}.start(ContextCompat.getMainExecutor(this)) { recordEvent ->when(recordEvent) {is VideoRecordEvent.Start -> {viewBinding.videoCaptureButton.apply {text = getString(R.string.stop_capture)isEnabled = true}}is VideoRecordEvent.Finalize -> {if (!recordEvent.hasError()) {val msg = "Video capture succeeded: " +"${recordEvent.outputResults.outputUri}"Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()Log.d(TAG, msg)} else {recording?.close()recording = nullLog.e(TAG, "Video capture ends with error: " +"${recordEvent.error}")}viewBinding.videoCaptureButton.apply {text = getString(R.string.start_capture)isEnabled = true}}}}
}
好了,就到这了。先概要看看就行了。