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.
Ein einzelnes Android-Gerät kann mehrere Kameras haben. Jede Kamera ist eine CameraDevice
und eine CameraDevice
kann mehr als einen Stream gleichzeitig ausgeben.
Ein Grund dafür ist, dass ein Stream – sequenzielle Kamera-Frames aus einer CameraDevice
– für eine bestimmte Aufgabe optimiert wird, z. B. das Anzeigen eines Suchers, während andere dazu verwendet werden können, ein Foto oder eine Videoaufzeichnung zu erstellen. Die Streams fungieren als parallele Pipelines, die Roh-Frames verarbeiten, die aus der Kamera kommen, Frame für Frame:
Die parallele Verarbeitung deutet darauf hin, dass je nach verfügbarer Prozessorleistung der CPU, GPU oder eines anderen Prozessors Leistungsgrenzen gelten können. Wenn eine Pipeline nicht mit den eingehenden Frames mithalten kann, werden sie gelöscht.
Jede Pipeline hat ein eigenes Ausgabeformat. Die eingehenden Rohdaten werden durch implizite Logik, die jeder Pipeline zugeordnet ist, automatisch in das geeignete Ausgabeformat umgewandelt. Die CameraDevice
, die in den Codebeispielen auf dieser Seite verwendet wird, ist unspezifisch. Daher müssen Sie zuerst alle verfügbaren Kameras aufzählen, bevor Sie fortfahren.
Sie können CameraDevice
verwenden, um eine CameraCaptureSession
zu erstellen, die für diese CameraDevice
spezifisch ist. Ein CameraDevice
muss für jeden Rohframe mithilfe von CameraCaptureSession
eine Frame-Konfiguration erhalten. Die Konfiguration legt Kameraattribute wie Autofokus, Blende, Effekte und Belichtung fest. Aufgrund von Hardwareeinschränkungen ist immer nur eine einzige Konfiguration im Kamerasensor aktiv. Diese wird als aktive Konfiguration bezeichnet.
„Stream Use Cases“ (Anwendungsfälle für Streams) erweitert und erweitert die bisherigen Möglichkeiten zur Verwendung von CameraDevice
zum Streamen von Aufnahmesitzungen, sodass Sie den Kamerastream für Ihren jeweiligen Anwendungsfall optimieren können. Beispielsweise kann die Akkulaufzeit bei der Optimierung von Videoanrufen verbessert werden.
Ein CameraCaptureSession
beschreibt alle möglichen Pipelines, die an die CameraDevice
gebunden sind. Wenn eine Sitzung erstellt wird, können Sie keine Pipelines hinzufügen oder entfernen.
Der CameraCaptureSession
unterhält eine Warteschlange mit CaptureRequest
s, die zur aktiven Konfiguration werden.
Ein CaptureRequest
fügt der Warteschlange eine Konfiguration hinzu und wählt eine, mehrere oder alle verfügbaren Pipelines aus, um einen Frame aus dem CameraDevice
zu empfangen. Sie können während der Lebensdauer einer Erfassungssitzung viele Erfassungsanfragen senden. Jede Anfrage kann die aktive Konfiguration und die Reihe von Ausgabepipelines ändern, die das Roh-Image empfangen.
Stream-Anwendungsfälle für eine bessere Leistung
Mithilfe von Stream-Anwendungsfällen lässt sich die Leistung von Camera2-Aufnahmesitzungen verbessern. Sie liefern dem Hardwaregerät mehr Informationen zur Feinabstimmung von Parametern, wodurch die Kamera für Ihre jeweilige Aufgabe besser geeignet ist.
Dadurch kann das Kameragerät Kamerahardware- und -softwarepipelines basierend auf Nutzerszenarien für jeden Stream optimieren. Weitere Informationen zu Anwendungsfällen für Streams finden Sie unter setStreamUseCase
.
Mit Stream-Anwendungsfällen kannst du zusätzlich zum Festlegen einer Vorlage in CameraDevice.createCaptureRequest()
angeben, wie ein bestimmter Kamerastream verwendet werden soll. So kann die Kamerahardware Parameter wie Feinabstimmung, Sensormodus oder Kamerasensoreinstellungen auf der Grundlage von Qualitäts- oder Latenzabfällen optimieren, die für bestimmte Anwendungsfälle geeignet sind.
Beispiele für Anwendungsfälle für Streams:
DEFAULT
: Deckt das gesamte Anwendungsverhalten ab. Das ist dasselbe, als würden Sie keinen Stream-Anwendungsfall festlegen.PREVIEW
: Empfohlen für den Sucher oder die In-App-Bildanalyse.STILL_CAPTURE
: Optimiert für hochauflösende Aufnahmen in hoher Qualität. Es wird erwartet, dass die Framerates wie in der Vorschau vergleichbar bleiben.VIDEO_RECORD
: Optimiert für Aufnahmen in hoher Qualität, einschließlich Bildstabilisierung in hoher Qualität, sofern dies vom Gerät unterstützt und von der App aktiviert wird. Diese Option kann Ausgabeframes mit einer erheblichen Zeitverzögerung zur Echtzeit erzeugen, um eine Stabilisierung in höchster Qualität oder eine andere Verarbeitung zu ermöglichen.VIDEO_CALL
: Empfohlen für Kameraanwendungen mit langer Laufzeit, bei denen es zu Problemen mit hohem Stromverbrauch kommt.PREVIEW_VIDEO_STILL
: Empfohlen für Apps sozialer Medien oder für Anwendungsfälle mit einem einzelnen Stream. Es ist ein Mehrzweck-Stream.VENDOR_START
: Wird für vom OEM definierte Anwendungsfälle verwendet.
CameraCaptureSession erstellen
Stellen Sie zum Erstellen einer Kamerasitzung einen oder mehrere Ausgabepuffer bereit, in die Ihre App Ausgabeframes schreiben kann. Jeder Zwischenspeicher stellt eine Pipeline dar. Sie müssen dies tun, bevor Sie die Kamera verwenden, damit das Framework die internen Pipelines des Geräts konfigurieren und Arbeitsspeicherpuffer zum Senden von Frames an die erforderlichen Ausgabeziele zuweisen kann.
Das folgende Code-Snippet zeigt, wie Sie eine Kamerasitzung mit zwei Ausgabezwischenspeichern vorbereiten, von denen einer zu einem SurfaceView
und einer zu einem ImageReader
gehört. Wenn Sie den PREVIEW
Stream Use Case zu previewSurface
und den STILL_CAPTURE
Stream Use Case zu imReaderSurface
hinzufügen, kann die Gerätehardware diese Streams noch weiter optimieren.
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) }
Sie haben die aktive Konfiguration der Kamera noch nicht definiert. Wenn die Sitzung konfiguriert ist, können Sie dazu Aufnahmeanfragen erstellen und weiterleiten.
Die auf die Eingaben angewendete Transformation beim Schreiben in den Zwischenspeicher wird durch den Typ der einzelnen Ziele bestimmt. Diese muss ein Surface
sein. Das Android-Framework weiß, wie ein Rohbild in der aktiven Konfiguration in ein Format konvertiert wird, das für das jeweilige Ziel geeignet ist. Die Konvertierung wird durch das Pixelformat und die Größe der jeweiligen Surface
gesteuert.
Das Framework versucht, sein Bestes zu geben. Es kann jedoch vorkommen, dass einige Surface
-Konfigurationskombinationen nicht funktionieren. Dies kann zu Problemen führen, z. B. wenn die Sitzung nicht erstellt wird, beim Senden einer Anfrage ein Laufzeitfehler ausgegeben wird oder die Leistung beeinträchtigt wird. Das Framework bietet Garantien für bestimmte Kombinationen von Gerät-, Oberfläche- und Anfrageparametern. Weitere Informationen finden Sie in der Dokumentation zu createCaptureSession()
.
Einzelne CaptureRequests
Die für jeden Frame verwendete Konfiguration ist in einem CaptureRequest
codiert, das an die Kamera gesendet wird. Zum Erstellen einer Erfassungsanfrage können Sie eine der vordefinierten Vorlagen oder TEMPLATE_MANUAL
für die vollständige Kontrolle verwenden. Wenn Sie eine Vorlage auswählen, müssen Sie einen oder mehrere Ausgabepuffer für die Anfrage angeben. Sie können nur Puffer verwenden, die bereits in der gewünschten Erfassungssitzung definiert wurden.
Erfassungsanfragen nutzen ein Builder-Muster und bieten Entwicklern die Möglichkeit, viele verschiedene Optionen festzulegen, darunter automatische Belichtung, Autofokus und Objektivöffnung.
Bevor Sie ein Feld festlegen, müssen Sie prüfen, ob die entsprechende Option für das Gerät verfügbar ist. Rufen Sie dazu CameraCharacteristics.getAvailableCaptureRequestKeys()
auf und prüfen Sie die entsprechende Kameraeigenschaften, z. B. die verfügbaren Modi für die automatische Belichtung, und prüfen, ob der gewünschte Wert unterstützt wird.
Wenn Sie mit der für die Vorschau entworfenen Vorlage ohne Änderungen eine Aufnahmeanfrage für eine SurfaceView
erstellen möchten, verwenden Sie 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);
Nachdem Sie eine Aufnahmeanfrage definiert haben, können Sie sie jetzt an die Kamerasitzung senden:
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);
Wenn ein Ausgabe-Frame in einen spezifischen Zwischenspeicher gestellt wird, wird ein Erfassungs-Callback ausgelöst. In vielen Fällen werden zusätzliche Callbacks wie ImageReader.OnImageAvailableListener
ausgelöst, wenn der darin enthaltene Frame verarbeitet wird. Jetzt können Sie die Bilddaten aus dem angegebenen Zwischenspeicher abrufen.
CaptureRequests wiederholen
Anfragen für eine einzelne Kamera lassen sich einfach durchführen, aber für die Anzeige einer Livevorschau oder eines Videos sind sie nicht sehr nützlich. In diesem Fall müssen Sie einen kontinuierlichen Stream von Frames empfangen, nicht nur einen einzelnen. Das folgende Code-Snippet zeigt, wie Sie der Sitzung eine wiederkehrende Anfrage hinzufügen:
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);
Eine sich wiederholende Aufnahmeanfrage sorgt dafür, dass die Kamera kontinuierlich Bilder mit den Einstellungen im bereitgestellten CaptureRequest
-Objekt aufnimmt. Mit der Camera2 API können Nutzer auch Videos mit der Kamera aufnehmen, indem sie CaptureRequests
wiederholen, wie in diesem Camera2-Beispiel-Repository auf GitHub gezeigt. Es kann auch Videos in Zeitlupe rendern, indem ein Hochgeschwindigkeitsvideo (Zeitlupe) mit einer sich wiederholenden Bilderserie CaptureRequests
aufgenommen wird, wie in der Beispiel-App für Videos in Zeitlupe von Camera2 auf GitHub gezeigt.
CaptureRequests verschachteln
Wenn Sie eine zweite Aufnahmeanfrage senden möchten, während die wiederkehrende Aufnahmeanfrage aktiv ist, z. B. um einen Sucher einzublenden und Nutzern die Möglichkeit zu geben, ein Foto aufzunehmen, müssen Sie die laufende Anfrage nicht beenden. Stattdessen senden Sie eine sich nicht wiederholende Erfassungsanfrage, während die sich wiederholende Anfrage weiter ausgeführt wird.
Jeder verwendete Ausgabepuffer muss beim Erstellen der Sitzung als Teil der Kamerasitzung konfiguriert werden. Wiederkehrende Anfragen haben eine niedrigere Priorität als Einzelframe- oder Burst-Anfragen. Daher funktioniert das folgende Beispiel:
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);
Dieser Ansatz hat jedoch einen Nachteil: Sie wissen nicht genau, wann die einzelne Anfrage auftritt. Wenn in der folgenden Abbildung A die wiederkehrende Erfassungsanfrage und B die Aufnahmeanfrage für einen einzelnen Frame ist, wird die Anfragewarteschlange in der Sitzung so verarbeitet:
Es gibt keine Garantie für die Latenz zwischen der letzten wiederholten Anfrage von A vor der Aktivierung von Anfrage B und der nächsten Verwendung von A. Es können also einige Frames übersprungen werden. Es gibt einige Dinge, die Sie tun können, um dieses Problem zu verringern:
Fügen Sie die Ausgabeziele von Anfrage A zu Anfrage B hinzu. Wenn der Frame von B bereit ist, wird er auf diese Weise in die Ausgabeziele von A kopiert. Dies ist beispielsweise bei der Erstellung von Video-Snapshots wichtig, um eine gleichmäßige Framerate beizubehalten. Im vorherigen Code fügen Sie
singleRequest.addTarget(previewSurface)
hinzu, bevor Sie die Anfrage erstellen.Verwenden Sie eine Kombination von Vorlagen, die für dieses spezielle Szenario geeignet sind, z. B. ohne Auslöser.