书接上回。
首先还是看谷歌的官方文档:
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_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
在Layout的activity_main.xml中使用PreviewView。这个应该是camerax的控件。
<androidx.camera.view.PreviewView
android: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 buttons
viewBinding.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 owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.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 case
val 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 + metadata
val 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 taken
imageCapture.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 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 ?: return
viewBinding.videoCaptureButton.isEnabled = false
val curRecording = recording
if (curRecording != null) {
// Stop the current recording session.
curRecording.stop()
recording = null
return
}
// create and start a new recording session
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, "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 = null
Log.e(TAG, "Video capture ends with error: " +
"${recordEvent.error}")
}
viewBinding.videoCaptureButton.apply {
text = getString(R.string.start_capture)
isEnabled = true
}
}
}
}
}
好了,就到这了。先概要看看就行了。