相機預覽

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

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

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

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

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

切換前後鏡頭

Android 相容性定義 指定相機影像感應器的方向「必須經過調整, 相機的尺寸會與螢幕的長邊對齊。也就是說 將裝置保持橫向,相機必須「必須」 橫向模式無論裝置本身是否自然 方向;適用於橫向和橫向裝置 設為直向及橫向的裝置)

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

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

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

如要向應用程式公開感應器旋轉資訊, camera2 API 包含 SENSOR_ORIENTATION敬上 常數。對於大多數手機和平板電腦,裝置會回報感應器方向 前置鏡頭為 270 度 (從 裝置背面),適用於後置鏡頭 (對齊裝置背面的長邊) 長邊感應器。一般而言,筆記型電腦的攝影機會 感應器方向為 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 的直向模式皆適當。
            寬版預覽圖片經過縮放及裁剪,以符合肖像模式
            方向。
圖 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 程式庫提供一系列工具,可簡化相機預覽畫面的建立程序。 這款工具並非依附於 CameraX Core,因此你可以順暢將其整合至自家的 現有 Camera2 程式碼集

您不需要使用 Surface敬上 您可以直接使用 CameraViewfinder 小工具,顯示 Camera2 的相機畫面。

CameraViewfinder 內部使用 TextureViewSurfaceView 即可顯示相機畫面,並對其套用必要的轉換 正確顯示觀景窗 包括修正長寬比、縮放和旋轉問題。

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

這項要求包含介面解析度和相機裝置的需求 資訊,來源:CameraCharacteristics

正在撥打 requestSurfaceAsync() 將要求傳送至介面提供者,可能是 TextureViewSurfaceView,並獲得 SurfaceListenableFuture

正在撥打 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 的比率,您可以縮放內容達到這個比例 更新元件中的 SurfaceView onMeasure() 方法:

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

相對輪播

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

相對旋轉功能由 SurfaceViewTextureView 等元件使用 來決定預覽圖片的 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敬上 並查看裝置旋轉狀態, Display#getRotation()onDisplayChanged() 回呼。

專屬資源

在 Android 10 以下版本中,只有多視窗模式下最頂端的活動 環境處於 RESUMED 狀態這會讓使用者感到困惑 系統並不會顯示重新啟用的活動。

Android 10 (API 級別 29) 導入了多視窗運作,其中所有可見活動 處於 RESUMED 狀態可見的活動仍可進入PAUSED 例如「透明活動」位在活動上方 該活動不成為焦點,例如子母畫面模式 (請參閱 支援子母畫面)。

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

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

其他資源