設定選項

您可以逐一設定 CameraX 的用途,控管用途相關作業的不同面向。

舉例來說,如果是拍照用途,您可以設定目標顯示比例和閃光燈模式。詳情請參閱以下程式碼範例:

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

除了設定選項之外,部分用途也提供 API,能以動態方式變更用途建立後的設定。如需個別用途的特定設定資訊,請參閱「導入預覽」、「圖片分析」以及「圖片拍攝」。

CameraXConfig

為了方便起見,CameraX 提供內部執行程式和處理常式這類預設設定,對大多數使用情況而言都很合適。不過,如果您的應用程式有特殊需求,或者您想自訂這類設定,則可使用 CameraXConfig 介面。

透過 CameraXConfig,應用程式可執行以下操作:

使用模式

如要瞭解如何使用 CameraXConfig,請參閱以下程序說明:

  1. 使用自訂設定來建立 CameraXConfig 物件。
  2. Application 中實作 CameraXConfig.Provider 介面,並在 getCameraXConfig() 中傳回 CameraXConfig 物件。
  3. 根據此處的說明,在 AndroidManifest.xml 檔案新增 Application 類別。

舉例來說,您可以參考下列程式碼範例,將 CameraX 設為只記錄錯誤訊息:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

如果應用程式需要在完成 CameraX 設定後瞭解設定資訊,請於本機保留一份 CameraXConfig 物件副本。

鏡頭限制程式

首次叫用 ProcessCameraProvider.getInstance() 期間,CameraX 會列舉並查詢裝置上可用鏡頭的特色。由於 CameraX 需要與硬體元件通訊,因此所有鏡頭可能都需要不少時間才能完成這項程序,尤其在低階裝置上更是如此。如果應用程式僅使用裝置上的特定鏡頭 (例如預設前置鏡頭),您可以將 CameraX 設為忽略其他鏡頭,藉此縮短應用程式所用鏡頭的啟動延遲時間。

如果傳遞給 CameraXConfig.Builder.setAvailableCamerasLimiter()CameraSelector 篩除某個鏡頭,CameraX 運作時就會假定該鏡頭不存在。舉例來說,以下程式碼會限制應用程式只能使用裝置的預設後置鏡頭:

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

執行緒

許多建構 CameraX 的平台 API 都需要封鎖與硬體之間的處理序間通訊 (IPC),而這類通訊有時可能需要數百毫秒的回應時間。因此,CameraX 只會從背景執行緒呼叫這些 API,這樣主執行緒就不會遭到封鎖,UI 也能順暢運作。為了讓這類通訊保持透明,CameraX 會在內部管理這些背景執行緒。不過,部分應用程式需要嚴格控管執行緒。CameraXConfig 可讓應用程式設定要透過 CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler() 使用哪個背景執行緒。

鏡頭執行程式

鏡頭執行程式適用於所有內部 Camera Platform API 呼叫,以及來自這些 API 的回呼。CameraX 會分配及管理內部 Executor,以便執行這些工作。但是,如果應用程式需要更嚴格的執行緒控管機制,請使用 CameraXConfig.Builder.setCameraExecutor()

排程器處理常式

排程器處理常式用於按固定的時間間隔排定內部工作,例如在鏡頭無法使用時再次嘗試開啟鏡頭。這個處理常式不會執行工作,只會分派工作給鏡頭執行程式。有時,這個處理常式還會用於需要 Handler 進行回呼的舊版 API 平台。在這類情況下,回呼仍僅會直接分派給鏡頭執行程式。雖然 CameraX 會分配及管理內部 HandlerThread 以執行這些工作,但您也可以使用 CameraXConfig.Builder.setSchedulerHandler() 覆寫 CameraX。

記錄

為了避免在正式版程式碼中產生詳細訊息,最佳做法是利用 CameraX 記錄功能,讓應用程式過濾 Logcat 訊息。CameraX 依據最詳細到最嚴重的區別,提供四種記錄層級:

  • Log.DEBUG (預設)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

如需這些記錄層級的詳細說明,請參閱 Android Log 說明文件。建議您使用 CameraXConfig.Builder.setMinimumLoggingLevel(int) 為應用程式設定適當的記錄等級。

自動選取

CameraX 會根據執行應用程式的裝置,自動提供專屬功能。舉例來說,如果您未指定解析度,或是系統不支援您指定的解析度,CameraX 會自動決定適用的最佳解析度。這些操作全由程式庫負責處理,因此您無需編寫裝置專用的程式碼。

CameraX 的目標是能夠順利初始化相機工作階段。也就是說,CameraX 會根據裝置功能調降解析度和顯示比例。調降的原因可能是:

  • 裝置不支援要求的解析度。
  • 裝置有相容性問題,例如舊版裝置須在特定解析度下才能正確運作。
  • 在部分裝置上,某些格式僅可在特定顯示比例下使用。
  • 對於 JPEG 或影片編碼來說,裝置會優先採用「最接近 mod16」的設定。詳情請參閱 SCALER_STREAM_CONFIGURATION_MAP

雖然 CameraX 會建立及管理工作階段,仍請您隨時在程式碼中檢查用途輸出內容上傳回的圖片大小,並據此做出調整。

旋轉

根據預設,在用途建立期間,相機旋轉角度會設為與螢幕旋轉角度一致。在這種預設情況下,CameraX 會產生輸出內容,讓應用程式與您預期在預覽畫面中看見的一致。您可以在設定用途物件時傳入目前的螢幕方向,或在建立這類物件後,以動態方式傳入目前的螢幕方向,藉此將旋轉角度變更為自訂值,以便支援多螢幕裝置。

透過配置設定,應用程式可以指定目標旋轉角度。這樣一來,也就可以使用用途 API 的方法 (例如 ImageAnalysis.setTargetRotation()) 更新旋轉設定,即使生命週期處於執行狀態也沒問題。如果應用程式已鎖定為直向模式 (這樣旋轉時就不會發生重新設定的情形),但相片或分析用途仍需得知裝置當前的旋轉角度,那麼您或許可以採用這個做法。舉例來說,如果需要瞭解旋轉角度,才能以正確方向進行臉部偵測,或將相片設定為橫向或直向,就很適合這麼做。

系統儲存所拍相片的資訊時,可能不會加入旋轉資訊。不過,EXIF 資料包含旋轉資訊,所以圖片庫應用程式才能在圖片儲存後,以正確的螢幕方向顯示圖片。

如要以正確的螢幕方向顯示預覽資料,您可以使用 Preview.PreviewOutput() 的中繼資料輸出內容,藉此建立轉換作業。

以下程式碼範例顯示如何設定螢幕方向事件的旋轉角度:

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

根據所設的旋轉角度,每個用途若不是直接旋轉圖片資料,就是會向非旋轉圖片資料的使用者提供旋轉中繼資料。

  • Preview:系統會提供中繼資料輸出內容,以便透過 Preview.getTargetRotation() 得知目標解析度的旋轉設定。
  • ImageAnalysis:系統會提供中繼資料輸出內容,以便得知相對於螢幕座標的圖片緩衝區座標。
  • ImageCapture:修改圖片 EXIF 中繼資料、緩衝區,或同時修改兩者,從而反映旋轉設定。修改的值視 HAL 實作而定。

裁剪矩形

根據預設,裁剪矩形會是完整的緩衝區矩形。您可以使用 ViewPortUseCaseGroup 自訂這個矩形。將用途分組並設定可視區域後,CameraX 就能確保群組中所有用途的裁剪矩形均指向鏡頭感應器中的同一個區域。

以下程式碼片段顯示如何使用這兩個類別:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort 會定義向使用者顯示的緩衝區矩形。接著,CameraX 會根據可視區域的屬性和附加用途,計算裁剪矩形可能的最大尺寸。一般來說,為了達到「所見即所得」的效果,您可以根據預覽用途設定可視區域。使用 PreviewView 即可輕鬆取得可視區域。

以下程式碼片段顯示如何取得 ViewPort 物件:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

上述範例假設 PreviewView 的調整類型設為 FILL_CENTER 這個預設值,因此應用程式從 ImageAnalysisImageCapture 取得的內容會與使用者在 PreviewView 看到的一致。將裁剪矩形和旋轉角度套用到輸出緩衝區之後,所有用途的圖片都會變成一樣,但解析度可能不同。如要進一步瞭解如何套用轉換資訊,請參閱「轉換輸出內容」。

選取鏡頭

CameraX 會自動選取最適合的鏡頭,以滿足應用程式的需求和用途。如果您不想使用系統預選的鏡頭,可以採取下列做法:

以下程式碼範例說明如何建立 CameraSelector 來選擇要使用哪個裝置:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

一併選取多個鏡頭

從 CameraX 1.3 開始,您也可以一併選取多個鏡頭。舉例來說,您可以繫結前置和後置鏡頭,以便同時從兩個角度拍照或錄影。

使用並行鏡頭功能時,裝置可以同時操作兩部鏡頭方向不同的相機,或同時操作兩個後置鏡頭。以下程式碼顯示如何在呼叫 bindToLifecycle 時設定兩個鏡頭,以及如何從傳回的 ConcurrentCamera 物件取得這兩個鏡頭物件。

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

相機解析度

您可以選擇讓 CameraX 在設定圖片解析度時一併考量裝置功能、支援的硬體等級、用途和提供的顯示比例。或者,您也可以根據支援該設定的用途,設定特定的目標解析度或顯示比例。

自動解析度

CameraX 可以根據 cameraProcessProvider.bindToLifecycle() 中指定的用途自動判斷最佳解析度設定。如果可能的話,請在單一 bindToLifecycle() 呼叫的單一工作階段中指定需一併執行的全部用途。CameraX 會考量裝置支援的硬體等級,以及所涵蓋的裝置專屬變異數 (裝置超過或不符合可用的串流設定時),並根據繫結的用途組合來決定解析度。這麼做是為了讓應用程式能在各種裝置上運作,同時盡量減少裝置專屬的程式碼路徑。

圖片拍攝和圖片分析用途的預設顯示比例為 4:3。

用途提供可設定的顯示比例,讓應用程式能依據 UI 設計指定所需的顯示比例。系統會產生 CameraX 輸出內容,以配合要求的顯示比例 (盡可能接近裝置支援的數據)。如果無法支援完全相符的解析度,系統會選取最能滿足所選條件的解析度。因此,應用程式會指定相機在應用程式中的顯示方式,而 CameraX 則會判斷最佳的相機解析度設定,以便在不同裝置上滿足這一要求。

舉例來說,應用程式可以執行下列任一操作:

  • 根據用途指定 4:3 或 16:9 的目標解析度
  • 指定自訂解析度,CameraX 會設法找出最符合的項目
  • 指定 ImageCapture 的裁剪顯示比例

CameraX 會自動選擇內部 Camera2 介面的解析度。下表列出這些解析度:

用途 內部介面解析度 輸出資料解析度
預覽 顯示比例:讓指定目標與設定相符的最佳解析度。 內部介面解析度。系統會提供中繼資料,讓 View 根據目標顯示比例進行裁剪、縮放及旋轉。
預設解析度:這是最高預覽解析度,或符合預覽顯示比例的裝置慣用解析度上限。
最大解析度:這是預覽尺寸,指的是符合裝置螢幕解析度或 1080p (1920x1080) 的最佳尺寸,以較小者為準。
圖片分析 顯示比例:讓指定目標與設定相符的最佳解析度。 內部介面解析度。
預設解析度:預設的目標解析度設定為 640x480。同時調整目標解析度和對應的顯示比例,即可得出支援效果最佳的解析度。
最大解析度:這是相機裝置輸出的最大解析度,採用 YUV_420_888 格式 (擷取自 StreamConfigurationMap.getOutputSizes())。目標解析度預設為 640x480,因此如果您需要大於 640x480 的解析度,就必須使用 setTargetResolution()setTargetAspectRatio(),以取得最接近的支援解析度。
圖片擷取 顯示比例:與設定最相符的顯示比例。 內部介面解析度。
預設解析度:這是可用的最高解析度,或符合 ImageCapture 顯示比例的裝置慣用解析度上限。
最大解析度:相機裝置的最大輸出解析度,採用 JPEG 格式。如要擷取這項資訊,請使用 StreamConfigurationMap.getOutputSizes()

指定解析度

使用 setTargetResolution(Size resolution) 方法建構用途時,您可以設定具體的解析度,如以下程式碼範例所示:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

您不能針對同一用途一併設定目標顯示比例和目標解析度。這樣做會導致在建構設定物件時擲回 IllegalArgumentException

按照目標旋轉角度旋轉支援的大小後,請在座標架構中表示解析度 Size。舉例來說,如果裝置的自然螢幕方向為直向,並且採用自然目標旋轉角度,便可在要求直向圖片時指定 480x640 解析度;而相同的裝置旋轉 90 度並以橫向螢幕方向為目標時,則可指定 640x480。

目標解析度會嘗試制定圖片解析度的下限。實際圖片解析度會是最接近的可用解析度,其大小不可小於相機實作情形決定的目標解析度。

不過,如果沒有任何解析度等於或大於目標解析度,系統會從小於目標解析度的可用解析度中選擇最接近的解析度。當解析度與提供的 Size 具有相同的顯示比例時,優先順序會在顯示比例不同的解析度前面。

CameraX 會根據要求套用最合適的解析度。如果應用程式的主要需求是滿足顯示比例,請只指定 setTargetAspectRatio,CameraX 會根據裝置決定適合的明確解析度。如果主要需求是為了提高圖片處理效率才指定解析度,例如根據裝置處理能力來處理小型或中型圖片,則請使用 setTargetResolution(Size resolution)

如果應用程式需要精確的解析度,請參閱 createCaptureSession() 中的資料表,判斷各個硬體等級所支援的最高解析度。如要查看目前裝置所支援的特定解析度,請參閱 StreamConfigurationMap.getOutputSizes(int)

如果應用程式在 Android 10 以上版本中運作,您可以透過 isSessionConfigurationSupported() 驗證特定的 SessionConfiguration

控管相機輸出內容

除了讓您依個別用途的需求來設定相機輸出內容,CameraX 還能實作以下介面,以便支援所有繫結用途中常見的相機作業:

以下是 CameraControl 支援的相機功能:

  • 變焦
  • 手電筒
  • 對焦和測光 (觸控對焦)
  • 曝光補償

取得 CameraControl 和 CameraInfo 的例項

您可以使用 ProcessCameraProvider.bindToLifecycle() 傳回的 Camera 物件擷取 CameraControlCameraInfo 例項。下列程式碼範例說明:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

舉例來說,您可以在呼叫 bindToLifecycle() 後提交變焦和其他 CameraControl 作業。當您停用或刪除用於繫結相機例項的活動後,CameraControl 就無法繼續執行作業,且會傳回失敗的 ListenableFuture

Camera

變焦

CameraControl 提供兩個可以更改變焦等級的方法:

  • setZoomRatio() 會根據變焦比例設定變焦。

    比例必須介於 CameraInfo.getZoomState().getValue().getMinZoomRatio()CameraInfo.getZoomState().getValue().getMaxZoomRatio() 的範圍之間。否則,函式會傳回失敗的 ListenableFuture

  • setLinearZoom() 可使用 0 到 1.0 的線性變焦值,設定目前的變焦操作。

    線性變焦的優點在於能保證視野 (FOV) 會隨著變焦設定改變而縮放,因此非常適合與 Slider 檢視畫面併用。

CameraInfo.getZoomState() 會傳回目前變焦狀態的 LiveData。在相機初始化,或使用 setZoomRatio()setLinearZoom() 設定變焦等級的情況下,該值會隨之變化。不論您呼叫這兩種方法的哪一種,都可設定支援 ZoomState.getZoomRatio()ZoomState.getLinearZoom() 的值。如果想在滑桿旁邊顯示變焦比例文字,這就十分有幫助。只要觀察 ZoomState LiveData,即可同時更新兩者,無需進行轉換。

當具有指定變焦值的重複要求完成時,這兩個 API 傳回的 ListenableFuture 可讓應用程式選擇是否接收通知。此外,如果您在上一個變焦作業尚在執行時設定了新變焦值,上一個變焦作業的 ListenableFuture 會立即失敗。

手電筒

CameraControl.enableTorch(boolean) 會啟用或停用手電筒 (也稱為閃光燈)。

CameraInfo.getTorchState() 可用於查詢目前的手電筒狀態。您可以檢查 CameraInfo.hasFlashUnit() 傳回的值,確認手電筒功能是否可用。如果不行,呼叫 CameraControl.enableTorch(boolean) 會導致傳回的 ListenableFuture 立即完成,並顯示失敗的結果,同時將手電筒狀態設為 TorchState.OFF

啟用手電筒後,無論 flashMode 設定為何,手電筒在拍照和錄影期間都會保持開啟。只有當手電筒停用時,ImageCapture 中的 flashMode 才會產生作用。

聚焦與測光

CameraControl.startFocusAndMetering() 會根據指定的 FocusMeteringAction 設定 AF/AE/AWB 測光區域,藉此觸發自動對焦和曝光測光功能。許多相機應用程式經常會以此方式實作「觸控對焦」功能。

MeteringPoint

首先,請使用 MeteringPointFactory.createPoint(float x, float y, float size) 建立 MeteringPointMeteringPoint 代表相機 Surface 上的單一點。該點資料會以正規化格式儲存,因此可輕鬆轉換為感應器座標,進而指定 AF/AE/AWB 區域。

MeteringPoint 的大小介於 0 到 1 之間,預設大小為 0.15f。呼叫 MeteringPointFactory.createPoint(float x, float y, float size) 時,CameraX 會根據提供的size,以 (x, y) 為中心點建立矩形區域。

以下程式碼演示如何建立 MeteringPoint

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering 和 FocusMeteringAction

如要叫用 startFocusAndMetering(),應用程式必須建構 FocusMeteringAction,其中包含一或多個 MeteringPoints,由 FLAG_AFFLAG_AEFLAG_AWB 這些選用測光模式組合而成。以下程式碼演示了此用法:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

如以上程式碼所示,startFocusAndMetering() 使用的 FocusMeteringAction 包含一個 AF/AE/AWB 測光區域的 MeteringPoint,以及另一個 AF 和 AE 專用的 MeteringPoint。

CameraX 會在內部將其轉換為 Camera2 MeteringRectangles,並設定對應的 CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS 參數來擷取要求。

並非每部裝置都支援 AF/AE/AWB 和多個測光區域,因此 CameraX 會盡可能執行 FocusMeteringAction。CameraX 會按照測光點加入的順序,盡可能使用所有 MeteringPoint。在 MeteringPoint 全數加入之後,超出數量限制的 MeteringPoint 將遭到 CameraX 忽略。比方說,如果 FocusMeteringAction 在僅支援 2 個 MeteringPoint 的平台上提供了 3 個 MeteringPoint,CameraX 只會使用前 2 個 MeteringPoint,並忽略最後一個 MeteringPoint

曝光補償

如果應用程式需要在自動曝光 (AE) 輸出結果之外微調曝光值 (EV),就很適合使用曝光補償。系統會使用下列方式結合曝光補償值,判斷目前圖片條件下所需的曝光量:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX 提供 Camera.CameraControl.setExposureCompensationIndex() 函式,可將曝光補償設為索引值。

索引值如為正值,圖片看起來會更明亮,而負值則會讓圖片變暗。應用程式可以根據下節所述的 CameraInfo.ExposureState.exposureCompensationRange() 查詢支援範圍。如果值在支援範圍內,那麼當拍攝要求成功啟用該值時,傳回的 ListenableFuture 便會完成;如果指定的索引值超出支援範圍,則 setExposureCompensationIndex() 會導致傳回的 ListenableFuture 立即完成,並顯示失敗結果。

CameraX 只會保留最新未處理的 setExposureCompensationIndex() 要求;而若在上一個要求執行前便多次呼叫該函式,會導致要求遭到取消。

如要設定曝光補償索引值,並記錄執行曝光變更要求時的回呼,請參閱以下程式碼片段:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)

舉例來說,以下程式碼會使用目前的 ExposureState 值來初始化曝光 SeekBar 的設定:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

其他資源

如要進一步瞭解 CameraX,請參閱下列其他資源。

程式碼研究室

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

  • CameraX 範例應用程式
  • 開發人員社群

    Android CameraX 討論群組