書接上回。
首先還是看谷歌的官方文檔:
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}}}}
}
好了,就到這了。先概要看看就行了。