CameraX 架構

本頁面介紹 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 預設的 UseCasePreviewImageCaptureImageAnalysis。如要關閉 ImageCaptureImageAnalysis,或是啟用 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 管理何時開始和結束擷取工作階段,以及何時取消繫結用途。

如果所有相機功能都對應至單一生命週期感知元件 (例如 AppCompatActivityAppCompat 片段) 的生命週期,那麼在繫結全部所需用途的情況下使用該元件的生命週期,就能確保啟用生命週期感知元件時可使用並安全捨棄相機功能,而不會消耗任何資源。

自訂 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 允許同時使用 PreviewVideoCaptureImageAnalysisImageCapture 的一個例項。此外,

  • 每種用途都可獨立運作。舉例來說,應用程式可以只錄影,但不使用預覽用途。
  • 啟用擴充功能後,系統只保證 ImageCapturePreview 的組合可正常運作。視原始設備製造商 (OEM) 實作項目而定,您可能無法另外新增 ImageAnalysis,也無法為 VideoCapture 用途啟用擴充功能。詳情請參閱擴充功能參考文件
  • 視相機功能而定,部分相機可在解析度較低的模式下支援用途組合,而無法在解析度較高的模式下支援相同的組合。
  • 在相機硬體等級為 FULL 以下的裝置上,結合 PreviewVideoCaptureImageCaptureImageAnalysis 時,CameraX 可能會為 PreviewVideoCapture 複製相機的 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\<CameraInfo\> 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

針對生命週期感知活動,請使用 FragmentActivityAppCompatActivity

宣告依附元件

如要在 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 屬性:

以下程式碼範例會使用串流用途,調整視訊通話的效能。使用 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,請參閱下列其他資源。

程式碼研究室

  • 開始使用 CameraX
  • 程式碼範例

  • CameraX 範例應用程式