CameraX 1.1 有哪些新的特性發布?

語言: CN / TW / HK

CameraX 是一個 Jetpack 支援庫,旨在幫助您簡化相機應用的開發工作。它提供一致且易用的 API 介面,適用於大多數 Android 裝置,並可向後相容至 Android 5.0 (API 級別 21)。我們將在本文中介紹 CameraX 1.1 的多項功能,比如影片功能。

如果您更喜歡通過影片瞭解此內容,請 點選此處 檢視。

CameraX 概覽

CameraX 是一個為了簡化編寫相機應用而設計的支援庫,它所提供的高階 API 可以讓開發者專注於和使用者互動而非相機的內部實現。我們一直在探索並修復其背後複雜的相容性問題,讓每個新版本都得以在更多的裝置上穩定執行。

何時使用 CameraX 或 Camera2,這取決於您期望更快的開發速度或是想要更高的自定義程度。

  • CameraX 可以很方便地實現普通照片影片的拍攝功能,而 Camera2 則可以對拍攝流程進行特殊控制,例如實現多重曝光或全手動捕獲;
  • CameraX 旨在消除不同裝置間的差異並在不同裝置上進行了測試,而 Camera2 則需要應用來管理不同裝置間的差異並測試其行為;
  • CameraX 提升了程式碼開發速度,讓您更專注於使用者介面和體驗流程,而 Camera2 則用於更深入地開發以創造基於相機的定製功能;
  • CameraX 釋出新版本頻繁,而 Camera2 則隨著 Android 的版本而更新;
  • CameraX 可以在您不熟悉相機的情況下也能夠進行開發,而 Camera2 則需要您對相機的專業知識有更深層次的瞭解。

CameraX 基於主要的使用場景來構建,比如實時預覽相機、檢索緩衝區以進行分析和拍攝照片,在 CameraX 1.1 版本中還加入了影片拍攝功能。我們來看一個簡單的 CameraX 示例:

fun bindPreview(cameraProvider : ProcessCameraProvider) {
    // 使用 CameraX 建立 Preview 用例
    var preview : Preview = Preview.Builder().build()

    // 建立 cameraSelector,它會在裝置上搜索所需的相機
    var cameraSelector : CameraSelector = CameraSelector.Builder()
        // 在本例中,我們選擇搜尋後置相機
        .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

    // 從 CameraX 的 CameraView 包中獲取 previewView 的控制代碼
    // 利用此方法可以輕鬆的將相機內容新增到檢視上
    preview.setSurfaceProvider(previewView.getSurfaceProvider())

    // 將 preview 與其生命週期繫結
    var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner,
                                cameraSelector, preview)
}

△ CameraX 程式碼示例

CameraX 是生命週期感知型元件,這意味著它將自動處理應用的生命週期事件來實現開始、停止、暫停和恢復。現在,應用啟動時螢幕上便會顯示實時預覽。

我們已於 2021 年 5 月釋出了 1.0 穩定版本,目前正在開發 1.1 Alpha 版本並且很快將會進入 Beta 階段。並且我們一如既往地不斷為新增裝置推出相容性修復程式,例如 1.0.1 和 1.0.2。

在 CameraX 1.1 版本中我們新增了開發者呼聲很高的功能,具體而言,在本文中我們將重點介紹:

  • 影片拍攝
  • YUV 到 RGB 的轉換
  • Beta 版 Extensions API
  • 一些需要了解的其它功能

影片拍攝

在 CameraX 1.1 版本中我們加入了影片拍攝功能,影片拍攝 API (尚處於 Alpha 階段,細節可能會發生變化,但整體結構基本會保持不變) 提供了錄製到檔案等基本功能、可自動適配每臺裝置的 Quality Setting API,以及 Lifecycle Management API。接下來我們先來了解如何設定影片拍攝功能,程式碼示例如下:

// 建立 Recorder
val recorder = Recorder.Builder()
                       // 我們可以在此處使用 setQualitySelector 設定影片質量
                       .setQualitySelector(...)
                       .build()

// 使用新建立的 Recorder 建立 VideoCapture
val videoCapture = VideoCapture.withOutput(recorder)

// 將其與生命週期繫結
cameraProvider.bindToLifecycle(
    this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)

// 設定 VideoRecordEvent 監聽器
val videoRecordEventListener = Consumer<VideoRecordEvent>{
    when (it) {
        is VideoRecordEvent.Start -> {}
        is VideoRecordEvent.Finalize -> {}
        is VideoRecordEvent.Status -> {
            // status 事件將會在錄製時持續更新
            val stats: RecordingStats = it.recordingStats
            // RecordingStats 中包含錄製檔案的尺寸和時長
        }
        is VideoRecordEvent.Pause -> {}
        is VideoRecordEvent.Resume -> {}

// 指定輸出
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
    .setContentValues(contentValues)
    .build()

// 準備錄製
val activeRecording = videoCapture.output.prepareRecording(this, mediaStoreOutput)
    // 關聯事件監聽器
    .withEventListener(ContextCompat.getMainExecutor(this), videoRecordEventListener)
    // 啟用音訊 (前提是此應用已獲得音訊許可權)
    .withAudioEnabled()
    // 開始錄製
    .start()

△ 影片拍攝示例

videoCapture 會在應用啟動時就緒,應用可以使用 videoRecordEventListener 響應開始、結束、暫停和恢復等拍攝事件,其中 Status 事件會提供包括檔案大小和持續時間的 RecordingStats。影片拍攝可以輸出到 File、FileDescriptorMediaStore,在本例中我們選擇 MediaStore。如果選擇啟用音訊,則需要此應用已經獲得音訊許可權。呼叫 start() 開始錄製為我們提供了 activeRecording 控制代碼,它可以用來暫停、恢復或停止錄製。您可以在 1.1 版本中試用這些 API。

YUV 至 RGB 的轉換

另一個呼聲很高的功能是 YUV 到 RGB 的轉換,我們來了解一下此功能。

△ YUV 格式 (圖左) 轉換至 RGB 格式 (圖右)

相機通常以 YUV420 格式生成資料,其中包括明亮度 (Luminance, Y)、色度 (Chroma, U, V) 和一些填充位元組以將各行與有效的記憶體步幅對齊。但是這種格式的影象處理起來可能很麻煩,而現在 CameraX 可以將 ImageAnalysis 的輸出轉換為大家更熟悉的 RGBA 以方便處理。接下來我們看一個示例:

val imageAnalysis = ImageAnalysis.Builder()
        .setTargetResolution(Size(1280, 720))
        .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build()

△ 從 ImageAnalysis 獲取 RGB 輸出

在示例程式碼中,我們建立了 ImageAnalysis 例項,為影象緩衝區指定了所需的解析度和背壓策略,並呼叫新的 setOutputImageFormat 方法以請求 RGBA 8888 格式的輸出。現在,ImageAnalysis輸出的幀為 RGBA 8888 資料而不再是 YUV 格式。

CameraX 中 YUV 到 RGB 的轉換基於 libyuv。此外,在 CameraX 1.1 版本中資料本身可以轉換到目標解析度。在中端裝置上對影象大小為 640x480 至 1080p 的資料進行轉換大約需要 5~10 毫秒,具體效能因裝置而異。此外 APK 會略微增加 50KB 左右。

修復單畫素漂移

YUV 轉換還修復了部分裝置上存在的單畫素漂移問題。在這些裝置上,YUV 輸出經過桶形移位一個畫素,導致最右邊的一列資料出現在影象的左邊緣。在已知會發生這種情況的裝置上,進行 YUV 到 RGB 的轉換及輸出 YUV 或 RGB 都會被修復,並且 CameraX 將會持續對更多有需要的裝置進行修復。

△ 修復單畫素漂移

△ 修復單畫素漂移

如需瞭解更多,請參閱我們之前的推文《為 CameraX ImageAnalysis 進行 YUV 到 RGB 的轉換》。

CameraX Extensions API

相機特效

在 CameraX 1.1 中的 CameraX Extensions API 可以更為充分地發揮裝置強大的功能。

CameraX Extensions 包括一些最常見的內建相機特效:

  • BOKEH (焦外虛化) : 在人像模式下拍攝照片時,讓前景人物更清晰。
  • HDR (高動態範圍) : 拍照時使用不同的自動曝光 (AE) 配置,以獲得最佳效果。
  • NIGHT (夜間) : 在低照度環境下 (通常是在夜間) 捕獲最佳靜態影象。
  • FACE RETOUCH (臉部照片修復) : 拍攝靜態影象時,修飾臉部膚色、輪廓等。
  • AUTO (自動) : 根據周圍的景色自動調整最終影象。

我們來看看如何使用 CameraX Extensions API:

// 獲取後置相機列表
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

// 檢查所有的後置相機中是否有支援焦外虛化
if (extensionsManager.isExtensionAvailable(
    cameraProvider,
    cameraSelector,
    ExtensionMode.BOKEH
)) {
    // 建立擴充套件 cameraSelector,我們提供了相機並指定焦外虛化模式
    // 它將開始在後臺搜尋支援焦外虛化的後置相機
    val bokehCameraSelector = extensionsManager.getExtensionCameraSelector(
        cameraProvider,
        cameraSelector,
        ExtensionMode.BOKEH
    )

    // 建立 imageCapture 和 preview
    val imageCapture = ImageCapture.Builder().builder()
    val preview = Preview.Builder().build()

    // 使用 bokehCameraSelector 將它們繫結到生命週期
    cameraProvider.bindToLifecycle(lifecycleOwner,
        bokehCameraSelector,
        imageCapture,
        preview
    )
}

△ 以 BOKEH 效果捕捉並預覽影象

在上面的例子中,imageCapture 輸出的影象將會具有焦外虛化效果,如果裝置支援的話,preview 也將預覽焦外虛化效果。

如需詳細瞭解,請參閱我們之前的推文《使用 CameraX Extensions API 將特效應用到照片上》。

曝光補償

CarmeraX 1.1 中還添加了曝光補償 API,此功能可以幫助使用者更好地捕捉過度曝光或者曝光不足的區域。

如圖所示我們所處的場景窗外很明亮而室內很昏暗,此時則可以調整曝光補償來更好地捕捉明亮的室外或昏暗的室內場景。我們來看一個例子:

// 建立變數來跟蹤 exposureIndex 值
var exposureIndex = 0
// 使用 cameraSelector 將 imageCapture 和 preview 繫結到生命週期
val camera = cameraProvider.bindToLifecycle(
    lifecycleOwner,
    getCameraSelector(),
    preview,
    imageCapture
)
 
// 為檢視中的按鈕新增點選事件
evButton.setOnclickListener {
 
    // 檢查有效的範圍以防止可能的異常
    val range = camera.cameraInfo.exposureState.exposureCompensationRange
 
    if (range.contains(exposureIndex + 1)) {
        // 呼叫 camera.cameraControl 的 setExposureCompenstation() 方法來設定曝光補償
        camera.cameraControl.setExposureCompenstation(++exposureIndex)
        // 使用 exposureCompensationStep 來實現從 index 到 EV 到轉換
        val ev = camera.cameraInfo.exposureState.exposureCompensationStep.toFloat() * exposureIndex
        Log.i("CameraXLog", "EV: $ev")
    }
}

△ 通過按鈕調整曝光

其中 exposureIndex 是一個與裝置無關的數字,它將以硬體允許的最小步長遞增或遞減曝光值,因此可以在不同的裝置上以類似的方式運作。如果您想向用戶展示 EV 值,可以獲取 exposureCompensationStep 來實現轉換。

如需瞭解 CameraX 中曝光補償 API 的應用背景和呼叫方法,請參閱我們之前的推文《CameraX 曝光補償 API 入門指南》。

平滑縮放

在 CameraX 1.1 中,我們還增加了平滑縮放功能。有一些裝置有包括廣角和長焦在內的多個鏡頭,CameraX 可以檢測這些裝置是否支援 SMOOTH_ZOOM 框架,在受支援的裝置上使用 CameraX 的縮放控制元件時,會自動使用所有的相機來實現更大的縮放範圍。如果您已經在使用這個縮放控制元件,那當您使用 1.1 版本進行編譯時,您的應用應該就可以訪問這些裝置上的所有相機。

CameraX 1.1 的更多功能

接下來介紹我們在 1.1 中新增的更多功能。

CameraState API 現在可以提供諸如另一個應用正在使用相機或者正處於勿擾模式等更多有關相機狀態的資訊,使得應用能夠圍繞不同的相機時間來設計更好的介面和使用者體驗流程。Image Analysis 現在可以提供超過 1080p 的影象。Logging API 可以更詳細的除錯日誌並改善了錯誤報告。Coordinate Transformation API 可以將不同用例間的座標關聯起來,如果您在 imageAnalysis緩衝區中定位了興趣點,便可以方便地在影象捕捉的輸出或預覽中輕鬆找到它。您可以使用 CameraFilter API 來指定詳細的規則以選擇合適的相機。如果應用只需要前置或者後置相機,可以使用 AvailableCamerasLimiter 來加快啟動時間。CameraControllerInfo 中可提供相機功能的更多詳細資訊。

裝置相容性

CameraX 會持續關注裝置相容性,以便應用在眾多裝置上都能夠良好執行。我們修復了很多諸如影象拉伸、縮放不正確、影象顛倒及關閉相機時意外輸出了綠色圖形等問題。每個 CameraX 的釋出版本或補丁版本中都會新增此類修復,最新的穩定版為 1.0.2。

您可以在 版本記錄 中看到每個版本中的詳細變更,還可以在 問題跟蹤器 中看已經修復的問題。

更多資訊

希望對 CameraX 1.1 版本的簡要介紹對大家有所幫助,非常期待看到大家使用 CameraX 構建的功能!

歡迎您 點選這裡 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支援!