相機預覽

注意:本頁面所述是指 Camera2 套件。除非應用程式需要 Camera2 的特定低階功能,否則建議使用 CameraX。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。

在 Android 上,相機和相機預覽畫面不一定相同 裝置。

無論裝置為何,攝影機都位於裝置上的固定位置 代表手機、平板電腦或電腦。裝置方向變更時,攝影機方向也會變更。

因此,相機應用程式通常會假設 裝置方向和相機預覽畫面的顯示比例。如果 手機為直向,系統會假設相機預覽畫面的高度較高 而不是寬度當手機 (和相機) 旋轉為橫向時,相機預覽畫面的寬度應大於高度。

不過,這些假設受到了新的板型規格 (例如折疊式裝置) 和顯示模式 (例如多視窗和多螢幕) 的挑戰。折疊式裝置可在不變更螢幕方向的情況下,變更顯示大小和顯示比例。多視窗模式會將相機應用程式限制在 螢幕,無論裝置螢幕方向為何,系統都會縮放相機預覽畫面。 多螢幕模式可讓您使用次要螢幕,但次要螢幕的方向可能與主要螢幕不同。

切換前後鏡頭

Android 相容性定義 指定相機影像感應器的方向「必須經過調整, 相機的尺寸會與螢幕的長邊對齊。也就是說,當裝置以橫向方向握持時,相機就必須以橫向方向拍攝影像。無論裝置的自然方向為何,這項規則都適用於橫向為主的裝置和直向為主的裝置。」

相機依螢幕的排列方式可最大化相機的顯示區域 相機應用程式的觀景窗。此外,影像感應器一般會將資料輸出為 橫向顯示比例最多 4:3

手機和相機感應器皆為直向。
圖 1:手機和相機感應器方向的典型關係。

相機感應器的自然方向為橫向。在圖 1 中,感應器 前置鏡頭 (相機指向與 螢幕) 將配合手機旋轉 270 度,符合 Android 相容性定義。

如要向應用程式公開感應器旋轉資訊, camera2 API 包含 SENSOR_ORIENTATION 常數。對於大多數手機和平板電腦,裝置會回報前置鏡頭的傳感器方向為 270 度,後置鏡頭的傳感器方向為 90 度 (從裝置背面觀看的角度),這會讓傳感器的長邊與裝置的長邊對齊。筆記型電腦相機通常會回報 0 或 180 度的感應器方向。

因為相機的影像感應器會將其資料 (影像緩衝區) 在 感應器的自然方向 (橫向),圖片緩衝區就必須旋轉圖像緩衝區 SENSOR_ORIENTATION 為相機預覽畫面指定的度數 會以自然方向直立的方式呈現對於前置鏡頭 旋轉是逆時針方向以順時針方向使用後置鏡頭

舉例來說,針對圖 1 中的前置鏡頭,相機感應器產生的影像緩衝區如下所示:

相機感應器已根據圖片旋轉為橫向
            左上方。

圖片必須逆時針旋轉 270 度,才能讓預覽畫面與裝置方向一致:

相機感應器的直向模式,以及圖片朝上。

後置鏡頭會產生方向相同的影像緩衝區 做為上述緩衝區,但 SENSOR_ORIENTATION 是 90 度。因此,緩衝區會順時針旋轉 90 度。

裝置旋轉

裝置旋轉是指裝置從自然旋轉的角度 方向。舉例來說,橫向擺放的手機會旋轉 90 度或 270 度,具體取決於旋轉方向。

相機感應器圖像緩衝區必須旋轉的度數必須與裝置旋轉度數相同 (除了感應器方向的度數),相機預覽畫面才能顯示為直立。

方向計算

相機預覽畫面的正確方向會考量感應器方向和裝置旋轉。

可使用 以下公式:

rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360

其中 sign 是 1 (前置鏡頭) 或 -1 (後置鏡頭)。

對於前置鏡頭,圖片緩衝區會逆時針旋轉 (從 感應器的自然方向)。對於後置鏡頭,感應器圖片緩衝區會順時針旋轉。

運算式 deviceOrientationDegrees * sign + 360 會將後置鏡頭的裝置旋轉方向從逆時針轉為順時針 (例如將逆時針 270 度轉為順時針 90 度)。模運算會將結果縮放至小於 360 度的值 (例如將 540 度旋轉縮放至 180 度)。

不同 API 回報的裝置旋轉資訊會有不同:

前置鏡頭

包括橫向螢幕方向、感應器的相機預覽畫面和感應器
            就是右側向上的
圖 2 手機將相機預覽畫面和感應器旋轉 90 度, 橫向。

以下是相機感應器產生的圖片緩衝區 (如圖 2 所示):

相機感應器的橫向模式,以及圖片朝上。

緩衝區必須逆時針旋轉 270 度,以便調整感應器方向 (請參閱上方的「相機方向」):

相機感應器已旋轉成直向,並使圖片方向朝向移動方向,

接著,緩衝區會逆時針旋轉 90 度 進而使裝置螢幕的正確方向 相機預覽畫面 (如圖 2 所示):

相機感應器已旋轉為橫向,圖片呈現直向。

以下是將相機轉向右側,以橫向模式拍攝的畫面:

相機預覽畫面和感應器皆處於橫向模式,但
            也就是感應器上下顛倒
圖 3. 手機設為 270 度的相機預覽畫面和感應器 (或 -90 度) 切換為橫向模式。

以下是圖片緩衝區:

相機感應器旋轉至橫向方向,且圖像上下顛倒。

緩衝區必須逆時針旋轉 270 度,以便調整感應器方向:

相機感應器經裁定為直向,且圖片方向為側面

接著將緩衝區逆時針旋轉 270 度 如何控制裝置旋轉方向

相機感應器已根據圖片旋轉為橫向
            直立的。

後置鏡頭

後置鏡頭的感應器方向通常為 90 度 (從裝置背面觀看)。指定相機預覽畫面方向時, 感應器圖片緩衝區會依據感應器旋轉程度順時針旋轉 (而不是以逆時針方向這類前置鏡頭),然後拍攝圖像 緩衝區會依照裝置旋轉程度逆時針旋轉。

相機預覽畫面和感應器皆為橫向,但感應器是倒置的。
圖 4. 手機,配備橫向的後置鏡頭 (變成 270 或 -90 度)。

以下是相機感應器的圖片緩衝區 (如圖 4 所示):

相機感應器旋轉至橫向方向,且圖像上下顛倒。

緩衝區必須順時針旋轉 90 度,以便調整感應器方向:

相機感應器以直向方向評分,圖片橫向顯示,左上方。

接著,緩衝區會逆時針旋轉 270 度,以因應裝置旋轉:

相機感應器已旋轉為橫向,圖片呈現直向。

顯示比例

螢幕顯示比例會在裝置螢幕方向變更時變動,但當裝置螢幕方向改變時, 在多視窗模式下調整視窗大小時,折疊式裝置可折疊及展開 以及應用程式在次要顯示器上開啟的時間。

相機感應器圖片緩衝區必須方向及縮放,以符合 觀景窗 UI 元素的方向和顯示比例 無論是否變更裝置,系統都會動態變更螢幕方向 方向。

在新的板型規格或多視窗/多螢幕環境中,如果應用程式假設相機預覽畫面的方向與裝置 (直向或橫向) 相同,預覽畫面可能會顯示錯誤的方向或縮放比例,甚至兩者皆有錯誤。

展開的摺疊式裝置,直向相機預覽畫面旋轉為橫向。
圖 5. 折疊式裝置從直向轉換為橫向,但相機感應器仍維持直向方向。

在圖 5 中,應用程式誤以為裝置逆時針旋轉 90 度,因此將預覽畫面旋轉相同角度。

展開的摺疊式裝置,相機預覽畫面為直向,但因縮放比例不正確而變形。
圖 6. 摺疊式裝置從直向轉為橫向 但相機感應器仍為直向。

在圖 6 中,應用程式未將圖片緩衝區的長寬比調整為 使其能夠調整大小,以符合相機預覽 UI 的新尺寸 元素。

固定方向的相機應用程式通常會在摺疊式裝置和其他大螢幕裝置 (例如筆記型電腦) 上發生問題:

筆記型電腦上的相機預覽畫面為直向,但應用程式 UI 為橫向。
圖 7. 筆電上的固定方向直向應用程式。

在圖 7 中,由於應用程式的方向,相機應用程式的 UI 會傾斜 僅限於直向。觀景窗圖片相對於相機感應器的方向正確。

插邊直向模式

不支援多視窗模式的相機應用程式 (resizeableActivity="false") 並限制螢幕方向 (screenOrientation="portrait")。 或 screenOrientation="landscape") 若是大螢幕裝置,該視窗會處於嵌入式直向模式,以便適當調整方向 相機預覽畫面。

即使顯示比例為橫向,也能在直向模式下以邊框 (內嵌) 方式顯示僅限直向的應用程式。即使螢幕顯示比例為直向,橫向模式的應用程式仍會加上黑邊。相機圖片會旋轉至與應用程式 UI 對齊,並裁剪為相機預覽畫面的顯示比例,然後縮放至填滿預覽畫面。

相機圖片的顯示比例時,會觸發插邊模式 感應器與應用程式主要活動的顯示比例不相符。

在筆記型電腦上,相機預覽畫面和應用程式 UI 的直向模式皆適當。
            寬版預覽圖片經過縮放及裁剪,以符合肖像模式
            方向。
圖 8. 開啟插邊直向模式的固定方向直向應用程式 。

在圖 8 中,僅限直向的相機應用程式已旋轉,以便在筆電螢幕上顯示直立的 UI。應用程式有上下黑邊, 顯示比例。相機 系統已旋轉預覽圖片,以填補應用程式的使用者介面旋轉 (因為 插邊模式),圖片也經過裁剪並縮放,以符合 減少螢幕方向。

旋轉、裁剪、縮放

螢幕上僅限直向的相機應用程式叫用插邊模式 長寬比為橫向的影片:

筆記型電腦上的相機預覽畫面為直向,但應用程式 UI 為橫向。
圖 9。 在筆記型電腦上執行固定方向的直向應用程式。

在直向模式下,應用程式會加上黑邊:

應用程式旋轉為直向並加上黑邊。圖片為
            由右至右移動

相機圖像會旋轉 90 度,以便調整應用程式的方向:

感應器圖像旋轉 90 度,以便直立。

系統會將圖片裁剪為相機預覽畫面的顯示比例,然後縮放至填滿預覽畫面 (視野範圍會縮小):

裁剪的相機影像經過縮放,以填滿相機預覽畫面。

在摺疊式裝置上,相機感應器的方向可以是直向 螢幕顯示比例為橫向時:

相機預覽畫面和應用程式 UI 會導致寬幅展開的螢幕側面。
圖 10. 展開的裝置,裝置上有僅限直向的相機應用程式,相機感應器和螢幕的顯示比例也不同。

因為相機預覽畫面會旋轉以配合感應器方向 圖片的方向正確,可在觀景窗中正確顯示,但僅限直向應用程式 有些不同凡響

在內嵌直向模式中,您只需要在直向模式下為應用程式加上黑邊,即可正確調整應用程式和相機預覽畫面的方向:

應用程式有上下黑邊的相機預覽畫面
            直接在摺疊式裝置上開啟。

API

自 Android 12 (API 級別 31) 起,應用程式也可以明確控制插邊直向 管理模式 SCALER_ROTATE_AND_CROP CaptureRequest 的屬性 類別

預設值為 SCALER_ROTATE_AND_CROP_AUTO,可讓系統叫用內嵌直向模式。SCALER_ROTATE_AND_CROP_90 是上述所述內嵌直向模式的行為。

並非所有裝置都支援所有 SCALER_ROTATE_AND_CROP 值。如需支援的值清單,請參閱 CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES。

CameraX

Jetpack CameraX 程式庫 打造出可以配合感應器方向和感應器的相機觀景窗 旋轉一項簡單的工作

PreviewView 版面配置元素 建立相機預覽畫面,並根據感應器方向自動調整。 裝置旋轉和縮放PreviewView 會維持 方法是套用 FILL_CENTER 縮放類型,這會將圖片置中,但可能會配合尺寸裁剪 「PreviewView」。如要將相機影像套用上下黑邊,請將縮放類型設為 FIT_CENTER。

如要瞭解使用 PreviewView 建立相機預覽的基本概念,請參閱 導入預覽。

如需完整導入範例,請參閱 CameraXBasic 託管在 GitHub 上

CameraViewfinder

與預覽用途類似,CameraViewfinder 程式庫提供一組工具,可簡化相機預覽畫面的建立作業。這項功能不依賴 CameraX Core,因此您可以將其完美整合至現有的 Camera2 程式碼庫。

您可以使用 CameraViewfinder 小工具,而非直接使用 Surface,顯示 Camera2 的相機畫面。

CameraViewfinder 在內部使用 TextureView 或 SurfaceView 來顯示相機動態饋給,並對其套用必要的轉換,以便正確顯示觀景窗。包括修正顯示比例、縮放和旋轉。

如要從 CameraViewfinder 物件要求介面,您需要 建立 ViewfinderSurfaceRequest。

這項要求包含 CameraCharacteristics 的表面解析度和相機裝置資訊相關規定。

呼叫 requestSurfaceAsync() 會將要求傳送至表面供應器 (即 TextureView 或 SurfaceView),並取得 Surface 的 ListenableFuture。

正在撥打 markSurfaceSafeToRelease() 通知表面供應商表示不需要該途徑,和相關 保持在所需狀態

Kotlin

fun startCamera(){
    val previewResolution = Size(width, height)
    val viewfinderSurfaceRequest =
        ViewfinderSurfaceRequest(previewResolution, characteristics)
    val surfaceListenableFuture =
        cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)

    Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
        override fun onSuccess(surface: Surface) {
            /* create a CaptureSession using this surface as usual */
        }
        override fun onFailure(t: Throwable) { /* something went wrong */}
    }, ContextCompat.getMainExecutor(context))
}

Java

    void startCamera(){
        Size previewResolution = new Size(width, height);
        ViewfinderSurfaceRequest viewfinderSurfaceRequest =
                new ViewfinderSurfaceRequest(previewResolution, characteristics);
        ListenableFuture<Surface> surfaceListenableFuture =
                cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest);

        Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() {
            @Override
            public void onSuccess(Surface result) {
                /* create a CaptureSession using this surface as usual */
            }
            @Override public void onFailure(Throwable t) { /* something went wrong */}
        },  ContextCompat.getMainExecutor(context));
    }

SurfaceView

SurfaceView是 在沒有預覽的情況下 不需要以動畫呈現

SurfaceView 會自動旋轉相機感應器圖片緩衝區,確保符合圖像 包括感應器方向和裝置 並輪替金鑰不過,圖片緩衝區會縮放以符合 SurfaceView 不考慮顯示比例。

您必須確保圖片緩衝區的顯示比例與 SurfaceView 的顯示比例相符,方法是在元件 onMeasure() 方法中縮放 SurfaceView 的內容:

(computeRelativeRotation() 原始碼位於下方的「相對旋轉」)。

Kotlin

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = MeasureSpec.getSize(widthMeasureSpec)
    val height = MeasureSpec.getSize(heightMeasureSpec)

    val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees)

    if (previewWidth > 0f && previewHeight > 0f) {
        /* Scale factor required to scale the preview to its original size on the x-axis. */
        val scaleX =
            if (relativeRotation % 180 == 0) {
                width.toFloat() / previewWidth
            } else {
                width.toFloat() / previewHeight
            }
        /* Scale factor required to scale the preview to its original size on the y-axis. */
        val scaleY =
            if (relativeRotation % 180 == 0) {
                height.toFloat() / previewHeight
            } else {
                height.toFloat() / previewWidth
            }

        /* Scale factor required to fit the preview to the SurfaceView size. */
        val finalScale = min(scaleX, scaleY)

        setScaleX(1 / scaleX * finalScale)
        setScaleY(1 / scaleY * finalScale)
    }
    setMeasuredDimension(width, height)
}

Java

@Override
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees);

    if (previewWidth > 0f && previewHeight > 0f) {

        /* Scale factor required to scale the preview to its original size on the x-axis. */
        float scaleX = (relativeRotation % 180 == 0)
                       ? (float) width / previewWidth
                       : (float) width / previewHeight;

        /* Scale factor required to scale the preview to its original size on the y-axis. */
        float scaleY = (relativeRotation % 180 == 0)
                       ? (float) height / previewHeight
                       : (float) height / previewWidth;

        /* Scale factor required to fit the preview to the SurfaceView size. */
        float finalScale = Math.min(scaleX, scaleY);

        setScaleX(1 / scaleX * finalScale);
        setScaleY(1 / scaleY * finalScale);
    }
    setMeasuredDimension(width, height);
}

如要進一步瞭解如何將 SurfaceView 實作為相機預覽,請參閱 相機方向。

紋理檢視

TextureView 的效能不如 SurfaceView,且需要更多工作,但 TextureView 可讓您全面控管相機預覽畫面。

TextureView 會根據感應器方向旋轉感應器圖片緩衝區,但不會處理裝置旋轉或預覽縮放。

您可以在 Matrix 轉換中編碼縮放和旋轉功能。如要瞭解如何正確縮放及旋轉 TextureView,請參閱「在相機應用程式中支援可調整大小的介面」

相對旋轉

相機感應器的相對旋轉角度,是指相機感應器輸出內容與裝置方向對齊時,所需的旋轉角度。

相對旋轉功能由 SurfaceView 和 TextureView 等元件使用 來決定預覽圖片的 x 和 y 縮放比例係數。作用 指定感應器圖片緩衝區的旋轉角度。

CameraCharacteristics 和 Surface 類別會計算 相機感應器的相對旋轉角度:

Kotlin

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public fun computeRelativeRotation(
    characteristics: CameraCharacteristics,
    surfaceRotationDegrees: Int
): Int {
    val sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    // Reverse device orientation for back-facing cameras.
    val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT
    ) 1 else -1

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
}

Java

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public int computeRelativeRotation(
    CameraCharacteristics characteristics,
    int surfaceRotationDegrees
){
    Integer sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // Reverse device orientation for back-facing cameras.
    int sign = characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1;

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360;
}

視窗指標

請勿使用螢幕大小來判斷相機觀景窗的尺寸;相機應用程式可能會在螢幕的某個部分執行,例如行動裝置的多視窗模式或 ChromeOS 的自由模式。

WindowManager#getCurrentWindowMetrics() (在 API 級別 30 中新增) 會傳回應用程式視窗的大小, 螢幕大小。Jetpack WindowManager 程式庫方法 WindowMetricsCalculator#computeCurrentWindowMetrics() 和 WindowInfoTracker#currentWindowMetrics() 提供類似的支援,可回溯相容至 API 級別 14。

旋轉 180 度

裝置旋轉 180 度 (例如從自然方向旋轉到倒置的自然方向) 不會觸發 onConfigurationChanged() 回呼。因此,相機預覽畫面可能會上下顛倒。

如要偵測 180 度旋轉,請實作 DisplayListener,並在 onDisplayChanged() 回呼中呼叫 Display#getRotation(),藉此檢查裝置旋轉。

專屬資源

在 Android 10 以下版本中,只有多視窗模式下最頂端的活動 環境處於 RESUMED 狀態這會讓使用者感到困惑,因為系統並未指出要恢復哪個活動。

Android 10 (API 級別 29) 推出了多重繼續功能,其中所有顯示的活動都處於 RESUMED 狀態。舉例來說,如果活動上有公開活動,或是活動無法聚焦 (例如在子母畫面模式下),可見的活動仍可進入 PAUSED 狀態 (請參閱「子母畫面支援」)。

在 API 級別 29 以上版本中,使用相機、麥克風或任何專屬或單例資源的應用程式都必須支援多工處理。適用對象 例如,如果三個重新啟用的活動想要使用相機,則只有其中一項可以 取得這項專屬資源每個活動都必須執行 onDisconnected() 回呼,以留意優先順序較高的相機存取權 活動。

若需更多資訊,請參閲 多視窗運作:

其他資源