相機預覽

注意:本頁面所述是指 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

其中 sign1 (前置鏡頭) 或 -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 在內部使用 TextureViewSurfaceView 來顯示相機動態饋給,並對其套用必要的轉換,以便正確顯示觀景窗。包括修正顯示比例、縮放和旋轉。

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

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

呼叫 requestSurfaceAsync() 會將要求傳送至表面供應器 (即 TextureViewSurfaceView),並取得 SurfaceListenableFuture

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

KotlinJava
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))
}
    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() 原始碼位於下方的「相對旋轉」)。

KotlinJava
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)
}
@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,請參閱「在相機應用程式中支援可調整大小的介面

相對旋轉

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

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

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

KotlinJava
/**
 * 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
}
/**
 * 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() 回呼,以留意優先順序較高的相機存取權 活動。

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

其他資源