前言
google推出Camera后,發現Camera功能簡單,難以滿足需求調用Camera各種效果,所以又推出了Camera2. Camera2功能強大但是使用十分麻煩,回調與冗余代碼太多,而且特別容易在釋放Camera上犯錯導致activty的內存泄露. 所以google推出了更簡單易用,但是功能也強大的CameraX.
因為CameraX的簡單可以幫助我們高效率開發,所以也是有學習的必要性.(Camera2了解就行,沒必要死磕浪費太多時間),CameraX有以下優勢:
- CameraX與Liftcycle結合,與Activity或者Fragment的生命周期捆綁,不要考慮攝像頭的釋放問題,減少了代碼的復雜度.
- CameraX兼容至 Android L (API 21)
- 依然支持Camera2的豐富攝像頭功能
添加依賴
// CameraX 核心庫使用 camera2 實現 implementation "androidx.camera:camera-camera2:1.0.0-beta03" // 可以使用CameraView implementation "androidx.camera:camera-view:1.0.0-alpha10" // 可以使用供應商擴展 implementation "androidx.camera:camera-extensions:1.0.0-alpha10" //camerax的生命周期庫 implementation "androidx.camera:camera-lifecycle:1.0.0-beta03"
獲取權限
跟以前一樣,需要動態授權一些必要權限
<!-- 相機相關 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
預覽攝像頭畫面
從最簡單的預覽攝像頭圖像開始,我們逐步了解使用方式,代碼如下:
布局要求使用PreviewView,作為SurfaceProvider
<androidx.camera.view.PreviewView android:id="@+id/previewView" android:layout_width="match_parent" android:layout_height="match_parent"/>
代碼:
class CameraXActivity : AppCompatActivity() { private val TAG = CameraXActivity::class.java.simpleName private lateinit var mPreviewView: PreviewView
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera_x2) mPreviewView = findViewById(R.id.previewView) startCameraPreview() } /** * 開始相機預覽 */ private fun startCameraPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { //用於將相機的生命周期綁定到生命周期所有者 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() //創建預覽 val preview = Preview.Builder().build() //選擇后置攝像頭 val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() try { //在重新綁定之前取消綁定 cameraProvider.unbindAll() //將生命周期,選擇攝像頭,預覽,綁定到相機 val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview) //設置預覽的View preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo)) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } }
特別簡單就完成了,而且無需考慮攝像頭的釋放
實現拍照
class CameraXActivity : AppCompatActivity() { private val TAG = CameraXActivity::class.java.simpleName private lateinit var mImageCapture: ImageCapture private lateinit var mImageAnalysis: ImageAnalysis private lateinit var mPreviewView: PreviewView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera_x2) mPreviewView = findViewById(R.id.previewView) startCameraPreview() takePhoto.setOnClickListener { //點擊后拍照 takePhoto() } } /** * 開始相機預覽 */ private fun startCameraPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build() //創建圖像捕捉 mImageCapture = ImageCapture.Builder().build() val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() try { cameraProvider.unbindAll() //請注意,這里新增了一個ImageCapture val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, mImageCapture) preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo)) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } /** * 拍照 */ private fun takePhoto() { //圖像的保存路徑與名稱 val photoFile = File(applicationContext.externalCacheDir?.path , SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(System.currentTimeMillis()) + ".jpg") // 創建圖像文件輸出選項 val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() //拍照,並且注冊拍照后的結果監聽 mImageCapture.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 = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } }) } }
拍照圖像旋轉
代碼的其他部分與上面的示例代碼一致, 我們只需要關注ImageCapture的創建與添加setTargetRotation
private fun createImageCapture():ImageCapture{ //創建圖像捕捉 mImageCapture = ImageCapture.Builder() .setTargetRotation(Surface.ROTATION_90)//設置旋轉角度,並且只能有4個旋轉方向屬性ROTATION_0/ROTATION_90/ROTATION_180/ROTATION_270 .build() return mImageCapture }
設置執行IO線程
private fun createImageCapture():ImageCapture{ //創建圖像捕捉 mImageCapture = ImageCapture.Builder() .setIoExecutor(Executors.newSingleThreadExecutor())//設置執行IO線程 .build() return mImageCapture }
設置捕捉模式
private fun createImageCapture():ImageCapture{ //創建圖像捕捉 mImageCapture = ImageCapture.Builder() //CAPTURE_MODE_MAXIMIZE_QUALITY 高畫質 //CAPTURE_MODE_MINIMIZE_LATENCY 低延遲 .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) .build() return mImageCapture }
設置閃光燈
private fun createImageCapture():ImageCapture{ //創建圖像捕捉 mImageCapture = ImageCapture.Builder() //FLASH_MODE_ON 閃光燈開啟 //FLASH_MODE_OFF 閃光燈關閉 //FLASH_MODE_AUTO 閃光燈自動 .setFlashMode(ImageCapture.FLASH_MODE_ON) .build() return mImageCapture }
設置寬高比
private fun createImageCapture():ImageCapture{ mImageCapture = ImageCapture.Builder() //RATIO_4_3 4比3 //RATIO_16_9 16比9 .setTargetAspectRatio(AspectRatio.RATIO_16_9) .build() return mImageCapture }
設置分辨率
下面還帖了一些注釋,這注釋的意思是,你可以隨便設置分辨率大小,但是真正的分辨率並不一定是你設置的數值,而是在攝像頭里獲取的分辨率列表中去取最近似值.
為什么會有這種說明? 我這里給沒有接觸過攝像頭開發的朋友說明一下:
手機的攝像頭的分辨率並不是可以隨便設置的,這需要取決於你開發的設備的攝像頭驅動的分辨率列表. 在以往開發Camera1和Camera2的時候我們需要自己獲取這份列表,從中選擇我們需要的分辨率. 在使用CameraX的時候他們幫我們簡化了這個篩選過程,你只需要設置目標分辨率,代碼會自動選擇近似分辨率
private fun createImageCapture(): ImageCapture { mImageCapture = ImageCapture.Builder() /* 目標分辨率嘗試建立圖像分辨率的最小界限。實際圖像分辨率將是尺寸上最接近的可用分辨率,該分辨率不小於由相機實現確定的目標分辨率。 但是,如果不存在等於或大於目標分辨率的分辨率,則將選擇最接近的小於目標分辨率的可用分辨率。 與提供的 {@link Size} 具有相同縱橫比的分辨率將在不同縱橫比的分辨率之前優先考慮。 */ .setTargetResolution(Size(1280, 720)) .build() return mImageCapture }
控制對焦
val factory = SurfaceOrientedMeteringPointFactory(width, height) val point = factory.createPoint(x, y) val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF) .addPoint(point2, FocusMeteringAction.FLAG_AE) // could have many // auto calling cancelFocusAndMetering in 5 seconds .setAutoCancelDuration(5, TimeUnit.SECONDS) .build() val future = cameraControl.startFocusAndMetering(action) future.addListener( Runnable { val result = future.get() // process the result } , executor)
實現錄像
private void takeVideo() { VideoCapture videoCapture = new VideoCaptureConfig.Builder() //設置寬高 .setTargetAspectRatio(aspectRatio(width, height)) //設置旋轉角度 .setTargetRotation(previewView.getDisplay().getRotation()) .build(); //錄像前必須解綁 cameraProvider.unbindAll(); //開啟相機預覽 preview.setSurfaceProvider(previewView.createSurfaceProvider()); //綁定生命周期,這里如果沒有參數preview,則只錄像,不顯示畫面 cameraProvider.bindToLifecycle(this, cameraSelector,preview, videoCapture); //視頻路徑 File file = getFile(".mp4"); //開始錄像 videoCapture.startRecording(file, ContextCompat.getMainExecutor(MainActivity.this), new VideoCapture.OnVideoSavedCallback() { @Override public void onVideoSaved(@NonNull File file) { Toast.makeText(MainActivity.this, file.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } @Override public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { Log.d(TAG, "onError: " + message); } }); //停止錄像,並且回調OnVideoSavedCallback btn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { videoCapture.stopRecording(); preview.clear(); } });
分析圖像
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), ImageAnalysis.Analyzer { image -> val rotationDegrees = image.imageInfo.rotationDegrees //旋轉角度 val format = image.format //格式 val width = image.width //寬 val height = image.height //高 val plane = image.planes[0] //PlaneProxy數據 val buffer = plane.buffer Log.e("ytzn", "rotationDegrees = $rotationDegrees") Log.e("ytzn", "format = $format") Log.e("ytzn", "width = $width") Log.e("ytzn", "height = $height") // insert your code here. }) cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)
CameraX 會生成 YUV_420_888
格式的圖片 在ImageFormat類里可以看到此格式
拓展功能
private fun setPreviewExtender(builder: Preview.Builder, cameraSelector: CameraSelector) { val extender = AutoPreviewExtender.create(builder) //自動模式 if (extender.isExtensionAvailable(cameraSelector)) { extender.enableExtension(cameraSelector) } //散景擴展 val bokehPreviewExtender = BokehPreviewExtender.create(builder) if (bokehPreviewExtender.isExtensionAvailable(cameraSelector)) { bokehPreviewExtender.enableExtension(cameraSelector) } //hdr擴展 val hdrPreviewExtender = HdrPreviewExtender.create(builder) if (hdrPreviewExtender.isExtensionAvailable(cameraSelector)) { hdrPreviewExtender.enableExtension(cameraSelector) } //美顏模式 val beautyPreviewExtender = BeautyPreviewExtender.create(builder) if (beautyPreviewExtender.isExtensionAvailable(cameraSelector)) { beautyPreviewExtender.enableExtension(cameraSelector) } //夜晚模式 val nightPreviewExtender = NightPreviewExtender.create(builder) if (nightPreviewExtender.isExtensionAvailable(cameraSelector)) { nightPreviewExtender.enableExtension(cameraSelector) } }
CameraX的一些問題
個人在開發過程中發現了一些問題,如下:
1.CameraX不支持外置攝像頭
2.一直沒找到跟Camera2一樣配置攝像頭拍照參數的方式
End