注意:本頁面是指 Camera2 套件。除非應用程式需要 Camera2 的特定低階功能,否則建議使用 CameraX。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。
相機應用程式可以同時使用多個影格。在某些情況下,不同的串流甚至需要不同的影格解析度或像素格式。常見的用途包括:
- 錄影:一個用於預覽的串流,另一個正在編碼並儲存至檔案。
- 條碼掃描:一個用於預覽的串流,另一個用於偵測條碼。
- 計算攝影:一個用於預覽的串流,另一個用於偵測臉部/場景偵測。
處理影格時的效能成本可顯著,而且在執行平行串流或管道處理時,成本會乘以費用。
CPU、GPU 和 DSP 等資源或許可以利用架構的「重新處理」功能,但記憶體等資源將會線性成長。
每項要求多個目標
多個相機串流可合併為單一 CameraCaptureRequest
。下列程式碼片段說明如何設定相機工作階段,其中包含一個用於相機預覽的串流,以及另一個串流以進行圖片處理:
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
如果您正確設定目標途徑,則此程式碼只會產生符合 StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
和 StreamComfigurationMap.GetOutputStallDuration(int, Size)
決定的最低 FPS 串流。實際效能因裝置而異,但 Android 根據以下三個變數,提供一定的支援特定組合:「輸出類型」、「輸出大小」和「硬體等級」。
使用不支援的變數組合可能會以低影格速率運作;如果沒有,則會觸發其中一個失敗回呼。createCaptureSession
的說明文件說明瞭保證運作方式。
輸出類型
「輸出類型」是指影格的編碼格式。可能的值包括 PRIV、YUV、JPEG 和 RAW。如需相關說明,請參閱 createCaptureSession
的說明文件。
選擇應用程式的輸出類型時,如果您的目標是盡可能提高相容性,請使用 ImageFormat.YUV_420_888
進行影格分析,並使用 ImageFormat.JPEG
處理靜態圖片。針對預覽和錄製情境,您可能需要使用 SurfaceView
、TextureView
、MediaRecorder
、MediaCodec
或 RenderScript.Allocation
。在這種情況下,請不要指定圖片格式。但如果是相容性,系統仍會計入 ImageFormat.PRIVATE
,無論內部使用的實際格式為何。如要查詢裝置根據 CameraCharacteristics
而支援的格式,請使用下列程式碼:
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
輸出大小
所有可用的輸出大小都會以 StreamConfigurationMap.getOutputSizes()
列出,但只有兩種與相容性有關:PREVIEW
和 MAXIMUM
。尺寸會以上限表示。如果 PREVIEW
大小的項目適用,那麼尺寸小於 PREVIEW
的項目也適用。MAXIMUM
也是如此。CameraDevice
的說明文件說明瞭這些大小。
可用的輸出大小取決於所選格式。依據 CameraCharacteristics
和格式,您可以查詢可用的輸出大小,如下所示:
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
在相機預覽和錄製用途中,請使用目標類別判斷支援的大小。格式將由相機架構本身處理:
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
如要取得 MAXIMUM
大小,請依區域排序輸出大小並傳回最大的值:
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW
是符合裝置螢幕解析度或 1080p (1920x1080) 的最佳尺寸,以較小者為準。顯示比例可能與螢幕的長寬比可能不符,因此您可能需要對串流套用上下黑邊或裁剪,才能以全螢幕模式顯示。如要取得正確的預覽大小,請比較可用的輸出大小與顯示大小,同時考慮螢幕可能會旋轉。
以下程式碼定義了 SmartSize
輔助類別,可讓您更輕鬆地比較大小:
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
查看支援的硬體級別
如要判斷執行階段的可用功能,請使用 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
查看支援的硬體級別。
使用 CameraCharacteristics
物件時,您可以使用單一陳述式擷取硬體級別:
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
將所有影片整合在一起
您可以使用輸出類型、輸出大小和硬體層級,判斷哪些串流組合是有效的。以下圖表為具有 LEGACY
硬體級別的 CameraDevice
支援的設定數據匯報。
目標 1 | 目標 2 | 目標 3 | 使用範例 | |||
---|---|---|---|---|---|---|
類型 | 大小上限 | 類型 | 大小上限 | 類型 | 大小上限 | |
PRIV |
MAXIMUM |
簡易預覽、GPU 影片處理功能,或是不預覽錄影。 | ||||
JPEG |
MAXIMUM |
無觀景窗靜態影像拍攝。 | ||||
YUV |
MAXIMUM |
應用程式內的影片/圖片處理功能。 | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
標準靜態影像。 | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
應用程式內處理和靜態拍攝功能。 | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
標準錄製。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
提供預先發布版和應用程式內處理功能。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
提供預先發布版和應用程式內處理功能。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
靜態畫面加上應用程式處理功能, |
LEGACY
是可能的最低硬體等級。這個表格顯示,每部支援 Camera2 (API 級別 21 或以上版本) 的裝置都能以正確的設定輸出最多三個同時串流資料;如果沒有過多負載限制效能 (例如記憶體、CPU 或熱力限制),則最多可以輸出三個同時串流。
您的應用程式也必須設定指定輸出緩衝區。舉例來說,如要指定具有 LEGACY
硬體等級的裝置,您可以使用 ImageFormat.PRIVATE
設定兩個目標輸出介面,另一個則使用 ImageFormat.YUV_420_888
。這是使用 PREVIEW
大小時支援的組合。如要使用本主題前面定義的函式,取得相機 ID 所需的預覽大小需要下列程式碼:
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
這需要等到 SurfaceView
可以使用所提供的回呼後才會進行:
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
您可以呼叫 SurfaceHolder.setFixedSize()
來強制 SurfaceView
符合相機輸出內容大小,或採取與 GitHub 上相機範例常見模組類似的 AutoFitSurfaceView
方法,這樣就能設定絕對大小,同時將長寬比和可用空間納入考量,並會在觸發活動變更時自動進行調整。
沒有等待等待的回呼,因此從 ImageReader
以所需格式設定另一個途徑較為簡單:
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
使用 ImageReader
等封鎖目標緩衝區時,請在使用影格後捨棄影格:
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
LEGACY
硬體層級可指定最低的公分母裝置。在採用 LIMITED
硬體等級的裝置中,您可以為其中一個輸出目標介面新增條件式分支,並使用 RECORD
大小,甚至針對具備 FULL
硬體等級的裝置,將其增加到 MAXIMUM
大小。