和你一起終身學習,這裏是程序員 Android
經典好文推薦,通過閱讀本文,您將收穫以下知識點:
一、Camera的預備知識
二、CameraX是什麼,能解決什麼問題
三、 CameraX如何使用
四、CameraX的一些思考
基於官網demo,增加了對焦、手勢縮放、手電、閃光燈等操作
一、Camera的預備知識
熟悉的大佬可以跳過
Surface、SurfaceView、SurfaceHolder這三個是啥
Surface是什麼?一句話來說Surface 是一塊用於填充圖像數據的內存空間。
可以再深入一點,瞭解下它包含的東西:咱後面可以重點關注下Buffer轉紋理的細節實際上用openGL修改流我們下一章分析
SurfaceView是什麼?一句話來描述的話那就是:它一個可以顯示surface的view!在App端它仍在View hierachy中,但在WMS中(可以理解爲Server端),它與宿主窗口是分離的。這樣的好處是對這個Surface的渲染可以放到單獨線程去做,不會影響主線程對事件的響應。缺點也很明顯,它的顯示也不受View的屬性控制,所以不能進行平移,縮放等變換(7.0之前)。
SurfaceHolder是什麼?它是一個接口,給持有surface的對象使用,可以控制surface的大小和格式,編輯surface中的像素,以及監聽surface的變化,這個接口通過SurfaceView獲得。下面這張圖可以說明一切
二、CameraX是什麼,能解決什麼問題
Jetpack的一個支持庫,最低版本要求Android5.0
默認的相機功能還是Camera2的能力,當然API都變了,同時提供CameraX Extensions拓展庫可以添加各種特效,例如人像、HDR、夜間和美顏模式(從上圖也可以看出,這是依賴OEM的)
綁定生命週期,所以Camera本身無需在生命週期中調用什麼onPause onResume之類的樣板代碼,且忘記後會造成各種問題
抹平設備兼容性問題,無需在代碼庫中添加設備專屬代碼
三、 CameraX如何使用
第一步
引入依賴
// CameraX core library
def camerax_version = '1.0.0-rc03'
// def camerax_version = '1.1.0-alpha03'目前最新版,但是爲了穩定我們還是選擇rc版
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 extensions[可選]拓展庫可實現人像、HDR、夜間和美顏、濾鏡但依賴於OEM
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle library[可選]避免手動在生命週期釋放和銷燬數據
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class[可選]最佳實踐,最好用裏面的PreviewView,它會自行判斷用SurfaceView還是TextureView來實現
implementation 'androidx.camera:camera-view:1.0.0-alpha23'
第二步
添加布局控件
<androidx.camera.view.PreviewView
android:id="@+id/view_finder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
第三步
初始化cameraProviderFuture並得到cameraProvider
/** Initialize CameraX, and prepare to bind the camera use cases */
//UseCase實際上是一個抽象類,相機中最核心的幾個類的父類ImageAnalysis/ImageCapture/Preview/VideoCapture
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// 選擇默認攝像頭給後續使用
lensFacing = when {
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else -> throw IllegalStateException("Back and front camera are unavailable")
}
// Enable or disable switching between cameras
updateCameraSwitchButton()
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}
得到cameraProvider後初始化Preview、ImageCapture、ImageAnalysis可以看到他們3個是單獨配置的,所以完全解耦。最後可以根據需要設置0個或多個到camera中
/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
// Preview
preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// ImageCapture
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// ImageAnalysis
imageAnalyzer = ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setImageQueueDepth(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetRotation(rotation)
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
//這裏拿到字節數組可以爲 所 欲 爲 !
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
})
}
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
//這裏拿到camera對象可以取出兩個重要的對象來用
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
//用來聚焦、手勢、閃光燈、手電等操作
mCameraInfo = camera?.cameraInfo
mCameraControl = camera?.cameraControl
initCameraListener()
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}
然後是拍照
// Get a stable reference of the modifiable image capture use case
//takePicture有重載方法,源碼的註釋寫的比較清楚了:
/*
* We need to chain the following callbacks to save the image to disk:
*
* +-----------------------+
* | |
* |ImageCapture. |
* |OnImageCapturedCallback|
* | |
* +-----------+-----------+
* |
* |
* +-----------v-----------+ +----------------------+
* | | | |
* | ImageSaver. | | ImageCapture. |
* | OnImageSavedCallback +------> OnImageSavedCallback |
* | | | |
* +-----------------------+ +----------------------+
*/
imageCapture?.let { imageCapture ->
imageCapture.takePicture(
outputOptions, cameraExecutor, 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")
}
})
}
第四步
手電筒、閃光燈、前後攝像頭、聚焦、手勢
手電筒:
//通過拿到的Camera接口的兩個方法拿到後進行設置即可
cameraControl = camera.cameraControl
cameraInfo = camera.cameraInfo
....
private fun toggleTorch() {
if (cameraInfo?.torchState?.value == TorchState.ON) {
cameraControl?.enableTorch(false)
} else {
cameraControl?.enableTorch(true)
}
}
閃光燈模式
mImageCapture.flashMode = ImageCapture.FLASH_MODE_ON
切換攝像頭
lensFacing = when {
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else -> throw IllegalStateException("Back and front camera are unavailable")
}
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
聚焦和手勢,由於新版的PreviewView已經被final修飾,無法被繼承然後重寫方法了,所以只能通過setOnTouchListener進行手勢監聽
val zoomState = mCameraInfo?.zoomState
val cameraXPreviewViewTouchListener = CameraXPreviewViewTouchListener(requireContext())
cameraXPreviewViewTouchListener.setCustomTouchListener(object : CameraXPreviewViewTouchListener.CustomTouchListener {
override fun zoom(delta: Float) {
zoomState?.value?.let {
val currentZoomRatio = it.zoomRatio
mCameraControl?.setZoomRatio(currentZoomRatio * delta)
}
}
override fun click(x: Float, y: Float) {
val factory = viewFinder.meteringPointFactory
val point = factory.createPoint(x, y)
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
.setAutoCancelDuration(3, TimeUnit.SECONDS)
.build()
focus_view.startFocus(Point(x.toInt(), y.toInt()))
val future = mCameraControl?.startFocusAndMetering(action)
future?.let {
it.addListener({
try {
val result = it.get() as FocusMeteringResult
if (result.isFocusSuccessful) {
focus_view.onFocusSuccess()
} else {
focus_view.onFocusFailed()
}
} catch (e: Exception) {
}
}, cameraExecutor)
}
}
override fun doubleClick(x: Float, y: Float) {
// 雙擊放大縮小
zoomState?.value?.let {
val currentZoomRatio = it.zoomRatio
if (currentZoomRatio > it.minZoomRatio) {
mCameraControl?.setLinearZoom(0f)
} else {
mCameraControl?.setLinearZoom(0.5f)
}
}
}
override fun longClick(x: Float, y: Float) = Unit
})
view_finder.setOnTouchListener(cameraXPreviewViewTouchListener)
四、CameraX的一些思考
優點
下面我們項目代碼中兼容性問題以及相應的special code可以去掉了
代碼在解耦層面做的優秀,以及鏈式的數據解析都更容易開發者使用
可拓展的美顏、濾鏡、人像、HDR可以較爲輕量的實現
缺點
目前還在rc3中,穩定性難說
雖然使用的能力是基於camera2的,但是API都變掉了(估計是爲了他們內部解耦),這意味着對本身項目的侵入性是較大的。
//這種代碼可以消失了
if (forceUseCamera1 || CameraHelper.getInstance(getContext()).shouldUseCamera1()) {//只使用Camera1的方案
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1 (for previous experience)", Build.VERSION.SDK_INT);
return new Camera1(callbackBridge, preview, context, ratio);
} else {//根據版本可能使用Camera2的方案
if (Build.VERSION.SDK_INT < 21) {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1", Build.VERSION.SDK_INT);
return new Camera1(callbackBridge, preview, context, ratio);
} else if (Build.VERSION.SDK_INT < 23) {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2", Build.VERSION.SDK_INT);
return new Camera2(callbackBridge, preview, context, ratio);
} else {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2Api23", Build.VERSION.SDK_INT);
return new Camera2Api23(callbackBridge, preview, context, ratio);
}
}
參考鏈接:https://blog.csdn.net/u012346890/article/details/116020542
至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!