[譯]Android的多攝像頭支援

luoqiuyu發表於2019-03-04

Android 的多攝像頭支援

從 Android P 開始,新增了對邏輯多攝像頭和 USB 攝像頭的支援。這對 Android 開發者來說意味著什麼?

多攝像頭

一臺裝置有多個攝像頭沒什麼新鮮的,但是直到現在,Android 裝置仍然最多隻有前後兩個攝像頭。如果你想要開啟第一個攝像頭,需要進行以下操作:

val cameraDevice = Camera.open(0)
複製程式碼

但是這些是比較簡單的操作。如今多攝像頭意味著前置或者後置有兩個及兩個以上的攝像頭。有很多鏡頭可供選擇!

Camera2 API

由於相容性問題,儘管舊的 Camera API 已經被廢棄很長時間,上述的程式碼仍然有效。但是隨著生態系統的發展,需要更先進的相機功能。因此,Android 5.0(Lollipop)引進了 Camera2,適用於 API 21 及以上。用 Camera2 API 來開啟第一個存在的攝像頭程式碼如下所示:

val cameraManager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraId = cameraManager.cameraIdList[0]
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
    override fun onOpened(device: CameraDevice) {
        // Do something with `device`
    }
    override fun onDisconnected(device: CameraDevice) {
        device.close()
    }
    override fun onError(device: CameraDevice, error: Int) {
        onDisconnected(device)
    }
}, null)
複製程式碼

第一個並不是最好的選擇

上述程式碼目前看起來沒什麼問題。如果我們所需要的只是一個能夠開啟第一個存在的攝像頭的應用程式,那麼它在大部分的 Android 手機上都有效。但是考慮到以下場景:

  • 如果裝置沒有攝像頭,那麼應用程式會崩潰。這看起來似乎不太可能,但是要知道 Android 運用在各種裝置上,包括 Android Things、Android Wear 和 Android TV 等這些有數百萬使用者的裝置。
  • 如果裝置至少有一個後置攝像頭,它將會對映到列表中的第一個攝像頭。但是當應用程式執行在沒有後置攝像頭的裝置上,比如 PixelBooks 或者其他一些 ChromeOS 的膝上型電腦,將會開啟唯一一個前置攝像頭。

那麼我們應該怎麼做?檢查攝像頭列表和攝像頭特性:


val cameraIdList = cameraManager.cameraIdList // may be empty
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
複製程式碼

變數 cameraLensFacing 有以下取值:

更多有關攝像頭配置的資訊,請檢視文件.

合理的預設設定

根據應用程式的使用情況,我們希望預設開啟特定的相機鏡頭配置(如果可以提供這樣的功能)。比如,自拍應用程式很可能想要開啟前置攝像頭,而一款擴增實境類的應用程式應該希望開啟後置攝像頭。我們可以將這樣的一個邏輯包裝成一個函式,它可以正確地處理上面提到的情況:

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    val cameraIds = cameraManager.cameraIdList
    // Iterate over the list of cameras and return the first one matching desired
    // lens-facing configuration
    cameraIds.forEach {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
            return it
        }
    }
    // If no camera matched desired orientation, return the first one from the list
    return cameraIds.firstOrNull()
}
複製程式碼

切換攝像頭

目前為止,我們討論瞭如何基於應用程式的用途選擇預設攝像頭。很多相機應用程式還為使用者提供切換攝像頭的功能:

[譯]Android的多攝像頭支援

Google 相機應用中切換攝像頭按鈕

要實現這個功能,嘗試從CameraManager.getCameraIdList()提供的列表中選擇下一個攝像頭,但是這並不是個好的方式。因為從 Android P 開始,我們將會看到在同樣的情況下更多的裝置有多個攝像頭,甚至有通過 USB 連線的外部攝像頭。如果我們想要提供給使用者切換不同攝像頭的 UI,建議(按照文件)是為每個可能的鏡頭配置選擇第一個可用的攝像頭。

儘管沒有一個通用的邏輯可以用來選擇下一個攝像頭,但是下述程式碼適用於大部分情況:

fun filterCameraIdsFacing(cameraIds: Array<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = cameraManager.cameraIdList
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be on the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}
複製程式碼

這看起來可能有點複雜,但是我們需要考慮到大量的有不同配置的裝置。

相容性行為

對於那些仍然在使用已經廢棄的 Camera API 的應用程式,通過 Camera.getNumberOfCameras() 得到的攝像頭的數量取決於 OEM 的實現。文件上是這樣描述的:

如果系統中有邏輯多攝像頭,為了保持應用程式的向後相容性,這個方法僅為每個邏輯攝像頭和底層的物理攝像頭組公開一個攝像頭。使用 camera2 API 去檢視所有攝像頭。

請仔細閱讀 其餘文件 獲得更多資訊。通常來說,類似的建議適用於:使用 Camera.getCameraInfo() API 查詢所有的攝像頭方向, 在使用者切換攝像頭時,僅僅只為每個可用的方向提供一個攝像頭。

最佳實踐

Android 執行在許多不同的裝置上。你不應該假設你的應用程式總是在有一兩個攝像頭的傳統的手持裝置上執行,而是應該為你的應用程式選擇最適合的攝像頭。如果你不需要特定的攝像頭,選擇有所需預設配置的第一個攝像頭。如果裝置連線了外部攝像頭,則可以合理的假設使用者希望首先看到這些外部攝像頭中的第一個。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章