Hinweis:Diese Seite bezieht sich auf das Camera2-Paket. Sofern Ihre App keine bestimmten Low-Level-Funktionen von Camera2 erfordert, empfehlen wir die Verwendung von CameraX. Sowohl CameraX als auch Camera2 unterstützen Android 5.0 (API-Level 21) und höher.
Eine Kameraanwendung kann mehrere Streams von Frames gleichzeitig verwenden. In einigen Fällen ist für verschiedene Streams sogar eine andere Frame-Auflösung oder ein anderes Pixelformat erforderlich. Hier einige typische Anwendungsfälle:
- Videoaufzeichnung: Ein Stream für die Vorschau, ein anderer wird codiert und in einer Datei gespeichert.
- Barcode-Scanning: ein Stream für die Vorschau, ein weiterer für die Barcode-Erkennung.
- Computergestützte Fotografie: Ein Stream für die Vorschau, ein weiterer für die Gesichts-/Szenenerkennung.
Die Verarbeitung von Frames ist mit einem nicht unerheblichen Leistungsaufwand verbunden. Dieser Aufwand wird bei der parallelen Stream- oder Pipelineverarbeitung noch erhöht.
Ressourcen wie CPU, GPU und DSP können die Wiederverarbeitungsfunktionen des Frameworks nutzen, Ressourcen wie Arbeitsspeicher wachsen jedoch linear.
Mehrere Ziele pro Anfrage
Mehrere Kamerastreams können in einem einzigen CameraCaptureRequest kombiniert werden.
Das folgende Code-Snippet zeigt, wie Sie eine Kamerasitzung mit einem Stream für die Kameravorschau und einem weiteren Stream für die Bildverarbeitung einrichten:
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);
Wenn Sie die Zieloberflächen richtig konfigurieren, werden mit diesem Code nur Streams erstellt, die die von StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) und StreamComfigurationMap.GetOutputStallDuration(int, Size) festgelegte Mindest-FPS erfüllen.
Die tatsächliche Leistung variiert von Gerät zu Gerät. Android bietet jedoch einige Garantien für die Unterstützung bestimmter Kombinationen, die von drei Variablen abhängen: Ausgabetyp, Ausgabegröße und Hardware-Level.
Die Verwendung einer nicht unterstützten Kombination von Variablen kann bei einer niedrigen Framerate funktionieren. Wenn dies nicht der Fall ist, wird einer der Fehler-Callbacks ausgelöst.
In der Dokumentation zu createCaptureSession wird beschrieben, was garantiert funktioniert.
Ausgabetyp
Ausgabetyp bezieht sich auf das Format, in dem die Frames codiert werden. Mögliche Werte sind PRIV, YUV, JPEG und RAW. Die Dokumentation für createCaptureSession enthält eine Beschreibung.
Wenn Sie den Ausgabetyp Ihrer Anwendung auswählen und das Ziel darin besteht, die Kompatibilität zu maximieren, verwenden Sie ImageFormat.YUV_420_888 für die Frame-Analyse und ImageFormat.JPEG für Standbilder. Für Vorschau- und Aufzeichnungsszenarien verwenden Sie wahrscheinlich SurfaceView, TextureView, MediaRecorder, MediaCodec oder RenderScript.Allocation. In diesen Fällen geben Sie kein Bildformat an. Aus Gründen der Kompatibilität wird es als ImageFormat.PRIVATE gezählt, unabhängig vom tatsächlich intern verwendeten Format. Verwenden Sie den folgenden Code, um die von einem Gerät unterstützten Formate anhand seiner CameraCharacteristics abzufragen:
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();
Ausgabegröße
Alle verfügbaren Ausgabegrößen werden von StreamConfigurationMap.getOutputSizes() aufgeführt. Nur zwei davon beziehen sich jedoch auf die Kompatibilität: PREVIEW und MAXIMUM. Die Größen fungieren als Obergrenzen. Wenn etwas mit der Größe PREVIEW funktioniert, funktioniert auch alles mit einer Größe, die kleiner als PREVIEW ist. Dasselbe gilt für MAXIMUM. In der Dokumentation zu CameraDevice werden diese Größen erläutert.
Die verfügbaren Ausgabegrößen hängen vom ausgewählten Format ab. Wenn Sie die CameraCharacteristics und ein Format haben, können Sie die verfügbaren Ausgabegrößen so abfragen:
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);
Verwenden Sie in der Kameravorschau und bei Aufzeichnungen die Zielklasse, um die unterstützten Größen zu ermitteln. Das Format wird vom Kamera-Framework selbst verarbeitet:
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);
Um die Größe von MAXIMUM zu ermitteln, sortieren Sie die Ausgabegrößen nach Fläche und geben Sie die größte zurück:
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 bezieht sich auf die Größe, die am besten zur Bildschirmauflösung des Geräts oder zu 1080p (1920 × 1080) passt, je nachdem, welche Auflösung kleiner ist. Das Seitenverhältnis stimmt möglicherweise nicht genau mit dem Seitenverhältnis des Bildschirms überein. Daher müssen Sie möglicherweise Letterboxing oder Zuschneiden auf den Stream anwenden, um ihn im Vollbildmodus anzuzeigen. Um die richtige Vorschaugröße zu erhalten, vergleichen Sie die verfügbaren Ausgabegrößen mit der Displaygröße. Berücksichtigen Sie dabei, dass das Display möglicherweise gedreht wird.
Im folgenden Code wird die Hilfsklasse SmartSize definiert, die Größenvergleiche etwas einfacher macht:
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; }
Unterstützte Hardwareebene prüfen
Um die verfügbaren Funktionen zur Laufzeit zu ermitteln, prüfen Sie die unterstützte Hardwareebene mit CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.
Mit einem CameraCharacteristics-Objekt können Sie die Hardwareebene mit einer einzigen Anweisung abrufen:
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);
Das Gesamtbild
Mit Ausgabetyp, Ausgabegröße und Hardwareebene können Sie festlegen, welche Streamkombinationen gültig sind. Das folgende Diagramm zeigt eine Übersicht der Konfigurationen, die von einem CameraDevice mit dem Hardware-Level LEGACY unterstützt werden.
| Ziel 1 | Ziel 2 | Ziel 3 | Beispielanwendungsfälle | |||
|---|---|---|---|---|---|---|
| Eingeben | Maximale Größe | Eingeben | Maximale Größe | Eingeben | Maximale Größe | |
PRIV |
MAXIMUM |
Einfache Vorschau, GPU-Videoverarbeitung oder Videoaufzeichnung ohne Vorschau. | ||||
JPEG |
MAXIMUM |
Standbildaufnahme ohne Sucher. | ||||
YUV |
MAXIMUM |
Videoverarbeitung in der App | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Standard-Standbilder | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
In-App-Verarbeitung und Aufnahme von Standbildern | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Standardaufzeichnung | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Vorschau und In-App-Verarbeitung | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Standbildaufnahme und In-App-Verarbeitung |
LEGACY ist die niedrigste mögliche Hardwareebene. Diese Tabelle zeigt, dass jedes Gerät, das Camera2 unterstützt (API-Level 21 und höher), mit der richtigen Konfiguration bis zu drei gleichzeitige Streams ausgeben kann, sofern nicht zu viel Overhead die Leistung einschränkt, z. B. durch Arbeitsspeicher-, CPU- oder thermische Einschränkungen.
Ihre App muss auch Targeting-Ausgabepuffer konfigurieren. Wenn Sie beispielsweise ein Gerät mit der Hardwareebene LEGACY als Ziel festlegen möchten, können Sie zwei Zielausgabeoberflächen einrichten, eine mit ImageFormat.PRIVATE und eine mit ImageFormat.YUV_420_888. Dies ist eine unterstützte Kombination bei Verwendung der Größe PREVIEW. Wenn Sie die zuvor in diesem Thema definierte Funktion verwenden, benötigen Sie den folgenden Code, um die erforderlichen Vorschaubildgrößen für eine Kamera-ID abzurufen:
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);
Sie müssen warten, bis SurfaceView bereit ist. Verwenden Sie dazu die bereitgestellten Callbacks:
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 } ... });
Sie können erzwingen, dass die SurfaceView der Größe der Kameraausgabe entspricht, indem Sie SurfaceHolder.setFixedSize() aufrufen. Alternativ können Sie einen Ansatz ähnlich dem AutoFitSurfaceView aus dem Common-Modul der Kamerabeispiele auf GitHub verwenden. Dabei wird eine absolute Größe festgelegt, die sowohl das Seitenverhältnis als auch den verfügbaren Speicherplatz berücksichtigt und automatisch angepasst wird, wenn sich die Aktivität ändert.
Die Einrichtung der anderen Oberfläche über ImageReader mit dem gewünschten Format ist einfacher, da keine Callbacks abgewartet werden müssen:
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);
Wenn Sie einen blockierenden Zielpuffer wie ImageReader verwenden, verwerfen Sie die Frames nach der Verwendung:
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);
Die LEGACY-Hardwareebene ist auf Geräte mit dem niedrigsten gemeinsamen Nenner ausgerichtet. Sie können bedingte Verzweigungen hinzufügen und die RECORD-Größe für eine der Ausgabezieloberflächen auf Geräten mit der Hardwareebene LIMITED verwenden oder sie sogar auf die MAXIMUM-Größe für Geräte mit der Hardwareebene FULL erhöhen.