Hinweis:Diese Seite bezieht sich auf das Paket Camera2. Sofern Ihre App keine bestimmten Low-Level-Funktionen von Camera2 erfordert, empfehlen wir die Verwendung von CameraX. CameraX und Camera2 unterstützen Android 5.0 (API-Level 21) und höher.
In einer Kamera-App können mehrere Frames gleichzeitig verwendet werden. In einigen Fällen erfordern unterschiedliche Streams sogar eine andere Frame-Auflösung oder ein anderes Pixelformat. Einige typische Anwendungsfälle sind:
- Videoaufzeichnung: Ein Stream wird für die Vorschau codiert und ein anderer wird codiert und in einer Datei gespeichert.
- Barcode-Scan: ein Stream für die Vorschau, ein weiterer für die Barcodeerkennung
- Computergestützte Fotografie: ein Stream für die Vorschau, ein anderer für die Gesichts-/Szeneerkennung
Bei der Verarbeitung von Frames entstehen nicht triviale Leistungskosten, die bei einer parallelen Stream- oder Pipelineverarbeitung multipliziert werden.
Ressourcen wie CPU, GPU und DSP können unter Umständen die Neuverarbeitungsfunktionen 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 anderen 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, erzeugt dieser Code nur Streams, die den durch StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
und StreamComfigurationMap.GetOutputStallDuration(int, Size)
festgelegten Mindest-fps entsprechen.
Die tatsächliche Leistung variiert von Gerät zu Gerät. Android bietet allerdings einige Garantien für die Unterstützung bestimmter Kombinationen, die von drei Variablen abhängen: Ausgabetyp, Ausgabegröße und Hardwareebene.
Die Verwendung einer nicht unterstützten Kombination von Variablen funktioniert möglicherweise bei einer niedrigen Framerate. Andernfalls wird ein Fehler-Callback ausgelöst.
In der Dokumentation zu createCaptureSession
wird beschrieben, was garantiert funktioniert.
Ausgabetyp
Der Ausgabetyp bezieht sich auf das Format, in dem die Frames codiert sind. Mögliche Werte sind PRIV, YUV, JPEG und RAW. Eine Beschreibung dazu finden Sie in der Dokumentation zu createCaptureSession
.
Wenn Sie bei der Auswahl des Ausgabetyps Ihrer Anwendung die Kompatibilität maximieren möchten, 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
. Geben Sie in diesen Fällen kein Bildformat an. Aus Kompatibilitätsgründen zählt er als ImageFormat.PRIVATE
, unabhängig vom tatsächlichen intern verwendeten Format. Verwenden Sie den folgenden Code, um die von einem Gerät anhand seiner CameraCharacteristics
unterstützten Formate 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 sind nach StreamConfigurationMap.getOutputSizes()
aufgelistet, aber nur zwei sind für die Kompatibilität relevant: PREVIEW
und MAXIMUM
. Die Größen dienen als Obergrenzen. Wenn etwas der Größe PREVIEW
funktioniert, funktioniert auch alles mit einer Größe, die kleiner als PREVIEW
ist. Dasselbe gilt für MAXIMUM
. Eine Erklärung zu diesen Größen finden Sie in der Dokumentation zu CameraDevice
.
Die verfügbaren Ausgabegrößen hängen vom Format ab. Mit dem CameraCharacteristics
und einem Format 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 den Anwendungsfällen für die Kameravorschau und -aufzeichnung die Zielklasse, um die unterstützten Größen festzulegen. Das Format wird von der Kamera selbst gehandhabt:
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);
Sortieren Sie die Ausgabegrößen nach Fläche und geben Sie die größte Größe zurück, um die MAXIMUM
-Größe zu erhalten:
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 optimale Größe für die Bildschirmauflösung des Geräts oder auf 1080p (1920 × 1080), je nachdem, was kleiner ist. Das Seitenverhältnis stimmt möglicherweise nicht genau mit dem Seitenverhältnis des Bildschirms überein. Daher müssen Sie den Stream eventuell mit Letterbox-Bild versehen oder zuschneiden, damit er im Vollbildmodus angezeigt werden kann. Um die richtige Vorschaugröße zu erhalten, vergleichen Sie die verfügbaren Ausgabegrößen mit der Anzeigegröße. Berücksichtigen Sie dabei, dass der Bildschirm gedreht werden kann.
Mit dem 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 Hardware prüfen
Prüfen Sie die unterstützte Hardwareebene mit CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
, um die während der Laufzeit verfügbaren Funktionen zu ermitteln.
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);
Indem alle Teile zusammengefügt werden
Mit dem Ausgabetyp, der Ausgabegröße und der Hardwareebene können Sie bestimmen, welche Kombinationen von Streams gültig sind. Das folgende Diagramm zeigt eine Übersicht der Konfigurationen, die von einem CameraDevice
mit der Hardwareebene LEGACY
unterstützt werden.
Ziel 1 | Ziel 2 | Ziel 3 | Beispielanwendungsfälle | |||
---|---|---|---|---|---|---|
Typ | Maximale Größe | Typ | Maximale Größe | Typ | Maximale Größe | |
PRIV |
MAXIMUM |
Einfache Vorschau, GPU-Videoverarbeitung oder Videoaufzeichnung ohne Vorschau. | ||||
JPEG |
MAXIMUM |
Standbild ohne Sucher aufnehmen. | ||||
YUV |
MAXIMUM |
Video-/Bildverarbeitung in der Anwendung | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Standard-Bildverarbeitung. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
In-App-Verarbeitung und Aufnahme | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Standardaufzeichnung. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Vorschau und In-App-Verarbeitung. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Vorschau und In-App-Verarbeitung. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Trotzdem Erfassung plus In-App-Verarbeitung. |
LEGACY
ist die niedrigste Hardwareebene. Die folgende Tabelle zeigt, dass jedes Gerät, das Camera2 (API-Level 21 und höher) unterstützt, bei der richtigen Konfiguration bis zu drei gleichzeitige Streams ausgeben kann, wenn der Aufwand nicht zu stark die Leistung beeinträchtigt, z. B. Speicher-, CPU- oder Überhitzungseinschränkungen.
Außerdem müssen in Ihrer App Ausgabezwischenspeicher für das Targeting konfiguriert werden. Für das Targeting auf ein Gerät mit der Hardwareebene LEGACY
könnten Sie beispielsweise zwei Zielausgabeoberflächen einrichten, eine mit ImageFormat.PRIVATE
und eine mit ImageFormat.YUV_420_888
. Diese Kombination wird bei Verwendung der Größe PREVIEW
unterstützt. Wenn Sie die zuvor in diesem Thema beschriebene Funktion verwenden, ist der folgende Code erforderlich, um die erforderlichen Vorschaugröß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);
Es muss mit den bereitgestellten Callbacks gewartet werden, bis SurfaceView
bereit ist:
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 SurfaceView
an die Ausgabegröße der Kamera angepasst wird, indem Sie SurfaceHolder.setFixedSize()
aufrufen. Sie können auch eine ähnliche Methode wie AutoFitSurfaceView
aus dem Common Module der Kamerabeispiele auf GitHub verwenden, das eine absolute Größe festlegt, bei der sowohl das Seitenverhältnis als auch der verfügbare Platz berücksichtigt und automatisch angepasst wird, wenn Aktivitätsänderungen ausgelöst werden.
Das Einrichten der anderen Oberfläche aus ImageReader
mit dem gewünschten Format ist einfacher, da keine Callbacks warten 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:
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 Hardwareebene „LEGACY
“ ist auf Geräte mit dem kleinsten gemeinsamen Nenner ausgerichtet. Sie können eine bedingte Verzweigung hinzufügen und die Größe RECORD
für eine der Ausgabezieloberflächen auf Geräten mit der Hardwareebene LIMITED
verwenden. Sie können die Größe für Geräte mit der Hardwareebene FULL
sogar auf die Größe MAXIMUM
erhöhen.