注意:本頁面是指 Camera2 套件。除非應用程式需要 Camera2 的特定低階功能,否則建議使用 CameraX。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。
一部 Android 裝置可以搭載多部相機。每個攝影機都是 CameraDevice
,而 CameraDevice
可以同時輸出多個串流。
這麼做的其中一個原因是,從 CameraDevice
產生的一個串流依序鏡頭畫面已最佳化處理特定工作 (例如顯示觀景窗),其他則可用於拍照或錄製影片。串流可做為平行管道,處理相機傳入的原始影格 (一次一個影格):
平行處理功能表示效能上限取決於 CPU、GPU 或其他處理器的可用處理能力。如果管道無法跟上收到的影格,便會開始捨棄這些影格。
每個管道都有自己的輸出格式。傳入的原始資料會透過與每個管道相關聯的隱含邏輯,自動轉換為適當的輸出格式。本頁程式碼範例中使用的 CameraDevice
並非特定類型,因此請先列舉所有可用相機,再繼續操作。
您可以使用 CameraDevice
建立該 CameraDevice
專屬的 CameraCaptureSession
。CameraDevice
必須使用 CameraCaptureSession
接收每個原始影格的影格設定。這個設定會指定相機屬性,例如自動對焦、光圈、效果和曝光。由於硬體限制,鏡頭感應器中每次只會啟用一項設定,這稱為「主動」設定。
不過,串流用途可以強化並擴充先前使用 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
串流用途新增至 previewSurface
,並將 STILL_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
編碼,然後再傳送至相機。如要建立擷取要求,您可以使用其中一個預先定義的範本,也可以使用 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
)。這時,您可以從指定的緩衝區中擷取圖片資料。
重複 CaptureRequests
單一相機要求非常簡單,但如要顯示即時預覽或影片,則不實用。在這種情況下,您必須接收連續的影格串流,而非只有單一串流。下列程式碼片段說明如何為工作階段新增重複要求:
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
,藉此從相機擷取影片,如這個 GitHub 上的 Camera2 範例存放區所示。此外,這個程式庫也能使用重複播放爆發的 CaptureRequests
拍攝高速 (慢動作) 影片,如 GitHub 上的 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 是單一影格擷取要求,這就是工作階段處理要求佇列的方式:
系統無法保證從 A 發出的最後一個重複要求 (要求 B 啟用之前和下次再次使用 A) 之間的延遲時間,因此可能會發生某些略過的影格。如要避免這個問題,您可以採取下列做法:
從要求 A 新增輸出目標,以要求 B。這樣一來,當 B 的影格準備就緒時,系統就會將該內容複製到 A 的輸出目標中。舉例來說,如果要建立影片快照來維持穩定的影格速率,就必須這麼做。在上述程式碼中,您必須在建構要求之前新增
singleRequest.addTarget(previewSurface)
。請搭配使用專為這個特定情境設計的範本組合,例如零延遲延遲。