本頁面介紹 CameraX 的架構,包括其結構、搭配 API 使用的方式、生命週期,以及合併用途的方式。
CameraX 架構
您可以使用 CameraX,經由稱為用途的抽象層,連接裝置的相機。可用的用途如下:
- 預覽:可藉由介面顯示預覽畫面,例如
PreviewView
。 - 圖片分析:提供 CPU 可存取的緩衝區,進行機器學習等分析。
- 圖片擷取:擷取並儲存相片。
- 影片擷取:使用
VideoCapture
擷取影片和音訊。
這些用途可以合併,並同時啟用。舉例來說,應用程式可透過預覽用途,讓使用者查看相機所看到的圖片,也能運用圖片分析用途,判斷相片中的人物是否在微笑,並透過圖片擷取用途,在偵測到笑容時拍照。
API 模型
如要使用這個程式庫,請指定下列項目:
- 具有設定選項的所需用途。
- 如何透過附加事件監聽器來處理輸出資料。
- 將用途繫結至 Android 架構生命週期,藉此指定預計的流程,比如何時啟用相機和產生資料。
有 2 種方法可以編寫 CameraX 應用程式:如要以最簡單的方式使用 CameraX,則建議採用 CameraController
;如需更多彈性,就適合使用 CameraProvider
。
CameraController
CameraController
可在單一類別中提供大部分的 CameraX 核心功能。這個方法只需編寫少量程式碼,而且能自動處理相機初始化、用途管理、目標旋轉、輕觸對焦、雙指撥動縮放等操作。擴充 CameraController
的具體類別為 LifecycleCameraController
。
Kotlin
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
CameraController
預設的 UseCase
為 Preview
、ImageCapture
和 ImageAnalysis
。如要關閉 ImageCapture
或 ImageAnalysis
,或是啟用 VideoCapture
,請使用 setEnabledUseCases()
方法。
如要進一步瞭解 CameraController
,請參閱 QR code 掃描器範例,或觀看 CameraController
基本概念影片。
CameraProvider
CameraProvider
仍易於使用,但由於應用程式開發人員會處理更多設定,因此會提供更多可自訂的設定,例如啟用輸出圖片旋轉功能,或設定 ImageAnalysis
中的輸出圖片格式。如要獲得更多彈性,您也可以運用自訂的相機預覽 Surface
;若採用的是 CameraController,則必須使用 PreviewView
。如果現有的 Surface
程式碼已是應用程式其他部分的輸入內容,就適合使用這些程式碼。
您可以使用 set()
方法設定用途,並利用 build()
方法完成用途。每個用途物件都提供一組用途專屬 API,例如圖片擷取用途提供 takePicture()
方法呼叫。
應用程式不會在 onResume()
和 onPause()
中放置特定的啟動和停止方法呼叫,而會使用 cameraProvider.bindToLifecycle()
,指定要與相機建立關聯的生命週期。該生命週期就會告知 CameraX 何時應設定相機擷取工作階段,並確保相機狀態隨著生命週期的轉換而適當變更。
如需各個用途的實作步驟,請參閱「導入預覽」、「圖片分析」、「圖片擷取」以及「影片擷取」。
為顯示畫面,預覽用途會與 Surface
互動。應用程式會使用下列程式碼,建立具有設定選項的用途:
Kotlin
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
如需更多程式碼範例,請參閱官方的 CameraX 範例應用程式。
CameraX 生命週期
CameraX 會觀察生命週期,判斷何時應開啟相機、建立擷取工作階段,以及何時應停止和關閉。用途 API 提供方法呼叫和回呼,可監控進度。
如「合併用途」章節所述,部分用途組合可以繫結至單一生命週期。如果應用程式需要支援無法合併的用途,您可以採用下列任一做法:
- 將相容的用途組成多個片段,然後在片段之間切換
- 建立自訂生命週期元件,並使用該元件手動控制相機生命週期
如果會拆分檢視畫面和相機用途的生命週期擁有者 (例如使用自訂生命週期或保留片段),則必須利用 ProcessCameraProvider.unbindAll()
,確保所有用途已與 CameraX 取消繫結,或逐一取消繫結各個用途。另外,將用途繫結至生命週期時,可以讓 CameraX 管理何時開始和結束擷取工作階段,以及何時取消繫結用途。
如果所有相機功能都對應至單一生命週期感知元件 (例如 AppCompatActivity
或 AppCompat
片段) 的生命週期,那麼在繫結全部所需用途的情況下使用該元件的生命週期,就能確保啟用生命週期感知元件時可使用並安全捨棄相機功能,而不會消耗任何資源。
自訂 LifecycleOwner
針對進階用途,您可以建立自訂 LifecycleOwner
,讓應用程式明確控管 CameraX 工作階段生命週期,而不是將該生命週期綁定至標準 Android LifecycleOwner
。
以下程式碼範例說明如何建立簡易的自訂 LifecycleOwner:
Kotlin
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
利用這個 LifecycleOwner
,應用程式可在程式碼中的所需位置放置狀態轉換作業。如要進一步瞭解如何在應用程式中實作這項功能,請參閱「實作自訂 LifecycleOwner」。
並行用途
用途可以並行運作。雖然用途可依序繫結至生命週期,但如要繫結所有用途,建議利用對 CameraProcessProvider.bindToLifecycle()
的單一呼叫。如要進一步瞭解設定變更的最佳做法,請參閱「處理設定變更」。
在下列程式碼範例中,應用程式會指定兩個用途應同時建立並運作,也會指定兩個用途都要使用的生命週期,讓兩個用途都隨著該生命週期啟動和停止。
Kotlin
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Java
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
CameraX 允許同時使用 Preview
、VideoCapture
、ImageAnalysis
和 ImageCapture
的一個例項。此外,
- 每種用途都可獨立運作。舉例來說,應用程式可以只錄影,但不使用預覽用途。
- 啟用擴充功能後,系統只保證
ImageCapture
和Preview
的組合可正常運作。視原始設備製造商 (OEM) 實作項目而定,您可能無法另外新增ImageAnalysis
,也無法為VideoCapture
用途啟用擴充功能。詳情請參閱擴充功能參考文件。 - 視相機功能而定,部分相機可在解析度較低的模式下支援用途組合,而無法在解析度較高的模式下支援相同的組合。
- 在相機硬體等級為
FULL
以下的裝置上,結合Preview
、VideoCapture
和ImageCapture
或ImageAnalysis
時,CameraX 可能會為Preview
和VideoCapture
複製相機的PRIV
串流。這種稱為「串流共用」的複製功能可同時使用這些功能,但會增加處理需求。因此,延遲時間可能會稍微增加,電池續航力也會降低。
您可以從 Camera2CameraInfo
取得支援的硬體級別。舉例來說,以下程式碼會檢查預設的後置鏡頭是否為 LEVEL_3
裝置:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
權限
應用程式會需要 CAMERA
權限。如要將圖片儲存至檔案,還會需要 WRITE_EXTERNAL_STORAGE
權限 (搭載 Android 10 以上版本的裝置除外)。
如要進一步瞭解如何設定應用程式的權限,請參閱「要求應用程式權限」。
相關規定
CameraX 的最低版本需求如下:
- Android API 級別 21
- Android 架構元件 1.1.1
針對生命週期感知活動,請使用 FragmentActivity
或 AppCompatActivity
。
宣告依附元件
如要在 CameraX 上新增依附元件,必須在專案中新增 Google Maven 存放區。
請按照以下範例,開啟專案的 settings.gradle
檔案,並新增 google()
存放區:
Groovy
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Kotlin
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
請將以下內容新增到 Android 區塊的結尾:
Groovy
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Kotlin
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
請將以下程式碼新增至應用程式各模組的 build.gradle
檔案:
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
如要進一步瞭解如何設定應用程式來符合需求條件,請參閱「宣告依附元件」。
CameraX 與 Camera2 的互通性
CameraX 是以 Camera2 為基礎建構而成,同時也公開了在 Camera2 實作中讀取及寫入屬性的多種方式。詳情請參閱互通套件相關說明。
如要進一步瞭解 CameraX 如何設定 Camera2 屬性,請使用 Camera2CameraInfo
讀取基礎 CameraCharacteristics
。您也可以選擇使用以下兩種方式寫入基礎 Camera2 屬性:
使用
Camera2CameraControl
,可設定基礎CaptureRequest
的屬性,例如自動對焦模式。使用
Camera2Interop.Extender
擴充 CameraXUseCase
。這樣做可設定 CaptureRequest 的屬性,就像Camera2CameraControl
一樣。此外,還會提供一些額外控制項,例如設定串流用途,根據使用情境調整相機效能。詳情請參閱「使用串流用途提升效能」。
以下程式碼範例會使用串流用途,調整視訊通話的效能。使用 Camera2CameraInfo
擷取視訊通話串流用途是否可用。接著,使用 Camera2Interop.Extender
設定基礎串流用途。
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
其他資源
如要進一步瞭解 CameraX,請參閱下列其他資源。
程式碼研究室
程式碼範例