相機擷取工作階段和要求

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

一部 Android 裝置可以有多部相機。每部攝影機都是 CameraDeviceCameraDevice 可同時輸出多個串流。

之所以能這麼做,是因為 來自 CameraDevice,最適合特定工作,例如顯示 觀看器則用於拍照或錄影 串流會做為處理原始影格的平行管道 或從鏡頭外流,一次一個影格:

圖 1 插圖:打造通用相機應用程式 (2018 年 Google I/O 大會)

平行處理系統顯示, CPU、GPU 或其他處理器提供的處理效能如果 管道無法跟上傳入的影格,模型會開始捨棄這些影格。

每個管道都有自己的輸出格式。收到的原始資料 自動轉換為適當的 輸出格式 (根據隱含邏輯) 與各個管道相關聯這個網頁的 CameraDevice 中所用的 程式碼範例範圍不同,因此您可以先列舉 所有可用鏡頭,才能繼續操作

您可以使用 CameraDevice 建立 CameraCaptureSession, 專屬於該 CameraDeviceCameraDevice 必須接收 使用 CameraCaptureSession 為每個原始影格設定不同的影格設定 設定可指定自動對焦、光圈、效果、 以及曝光由於硬體限制,只有單一設定 隨時都在相機感應器上啟動,稱為 active 配置。

然而,串流用途案例強化及擴充先前的 CameraDevice 使用方式 串流擷取會議,讓您最佳化攝影機串流, 適合特定用途例如,在執行最佳化作業時可延長電池續航力 視訊通話。

CameraCaptureSession 說明繫結至 CameraDevice。工作階段建立後,就無法新增或移除管道。 CameraCaptureSession 會將佇列中的 CaptureRequest、 這會成為使用中的設定

CaptureRequest 會將設定新增至佇列,並選取一項設定,超過 一個或所有的可用管道 CameraDevice。您可以在擷取的生命週期內傳送許多擷取要求 會很有幫助每項要求都可以變更使用中的設定和一組輸出內容 用於接收原始映像檔的管道

透過串流用途提升效能

串流用途是一種提升 Camera2 擷取效能的方法 工作階段。可讓硬體裝置提供有關調整參數的資訊 這能針對您的特定工作,提供更好的相機體驗。

這個 可讓相機裝置最佳化相機硬體和軟體管道 根據每個串流的使用者情境 建立適當的提示進一步瞭解串流用途 如需記錄,請參閱 setStreamUseCase

「串流用途」可讓您指定特定相機串流在「串流」用途中的使用方式 設定範本外 CameraDevice.createCaptureRequest()。以利相機硬體最佳化 「調整」、「感應器模式」或「相機感應器設定」等參數 並在品質或延遲方面做出取捨

串流用途包括:

  • DEFAULT:涵蓋所有現有的應用程式行為。等同於不要使用 設定任何串流用途

  • PREVIEW:建議用於觀景窗或應用程式內圖片分析。

  • STILL_CAPTURE:已完成最佳化調整,具備優異的高解析度拍攝功能,不會 仍能維持類似預覽的畫面更新率

  • VIDEO_RECORD:最佳化處理高畫質影片,包括高畫質影片 圖片穩定功能 (如果裝置支援這項功能且由應用程式啟用)。 這個選項可能會導致輸出影格的即時延遲很大 以提供最佳的穩定功能或其他處理程序。

  • VIDEO_CALL:建議用於長時間執行的相機,且耗電 疑慮。

  • PREVIEW_VIDEO_STILL:建議用於社群媒體應用程式或單一串流用途 用途這是多用途的串流

  • VENDOR_START:適用於原始設備製造商 (OEM) 定義的用途。

建立 CameraCaptureSession

如要建立相機工作階段,請提供一或多個輸出緩衝區 應用程式可寫入輸出影格每個緩衝區都代表一個管道。您必須 請先操作再使用相機 裝置的內部管道,並分配記憶體緩衝區來傳送影格 所需的輸出目標

下列程式碼片段顯示如何準備相機工作階段 一個屬於 SurfaceView 和另一個 ImageReader。將 PREVIEW 串流用途新增至 previewSurfaceSTILL_CAPTURE 訊息串用途 有了 imReaderSurface 的保護殼,裝置硬體就能針對這些串流內容進行最佳化 進一步

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

在此階段,您尚未定義攝影機的使用中配置。 設定工作階段後,您就能建立及調度擷取內容 來執行這項要求

輸入到輸入緩衝區時,該轉換會套用 取決於每個目標的類型,且必須是 Surface。Android 架構知道 將目前配置中的原始圖片轉換成 每個目標。轉換是由像素格式和 特定 Surface

這個架構會盡可能嘗試最佳做法,但有些Surface 可能無法使用,因此造成 未建立,您在分派要求時擲回執行階段錯誤,或 效能會降低該架構可保證 裝置、介面和請求參數的組合。 createCaptureSession()敬上 提供了更多資訊

單一擷取要求

每個影格使用的設定都編碼在 CaptureRequest 中, 傳送至相機。如要建立擷取要求,您可以使用 預先定義 templates、 或使用 TEMPLATE_MANUAL 取得完整控制。當您選擇 範本,您必須提供一或多個輸出緩衝區 要求。只能使用擷取當下已定義的緩衝區 要使用的工作階段

擷取要求會使用 建構工具模式 讓開發人員有機會 選項,包括 自動曝光 自動對焦, 和 光圈。 設定欄位前,請確定 撥號 CameraCharacteristics.getAvailableCaptureRequestKeys()敬上 檢查適合的鏡頭,確認系統支援您要的值 例如:可用的自動曝光

使用範本為 SurfaceView 建立擷取要求 以便直接預覽,不需修改 CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

定義擷取要求後,您現在可以調度 我剛剛回答:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

當輸出影格放入特定緩衝區時, 回呼 在許多情況下還會進行其他回呼,例如: ImageReader.OnImageAvailableListener、 其中包含的影格處理完畢後就會觸發時間 便可以從指定的緩衝區擷取圖片資料。

重複擷取要求

單一鏡頭要求很簡單,不過如要顯示即時影像, 就沒有什麼幫助在這種情況下,您必須收到 連續串流多個影格,而不只是單一影格。下列程式碼片段 會說明如何 這段課程:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

重複拍攝要求會導致相機裝置持續擷取 使用所提供的設定 CaptureRequest 控制圖像。Camera2 API 使用者也可以將自己傳到攝影機的影片 重複 CaptureRequests,如以上所示 Camera2 範例 託管在 GitHub 上此外,還能擷取 重複播放快速連拍 CaptureRequests 的高速 (慢動作) 影片 Camera2 慢動作影片範例應用程式中所示 。

交錯擷取要求

如要在重複擷取要求處於啟用狀態時,傳送第二個擷取要求, 例如顯示觀景窗及讓使用者拍攝相片,您就不需要這麼做 停止進行中的重複要求而是發出不重複拍攝 持續執行。

您使用的任何輸出緩衝區都必須設為相機工作階段的一部分 請務必建立設定檔重複要求的優先順序低於 可使用單一影格或爆發要求,讓以下範例可運作:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

但這種方法存在缺點: 不會產生單一要求下圖中,如果 A 是重複的 擷取要求,B 則是單一影格擷取要求,這就是 會處理要求佇列:

圖 2. 處理中攝影機工作階段的要求佇列插圖

在上次發出要求之間的延遲時間則無法保證 要求 B 之前 A,並在下次使用 A 時啟用 所以可能會出現一些略過的影格載入 Google 試算表時 如何減少這個問題:

  • 從要求 A 新增輸出目標,以要求 B。如此一來 B 的影格已準備就緒,系統會將它複製到 A 的輸出目標中。 舉例來說,執行影片快照來維持 影格速率穩定在上述程式碼中 singleRequest.addTarget(previewSurface),然後再建立要求。

  • 請針對這種情況,設計合適的範本組合 例如零快門延遲