Sitzungen und Anfragen für Kameraaufnahmen

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:

Abbildung 1. Abbildung aus dem Dokument zum Erstellen einer universellen Kamera-App (Google I/O 2018)

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 CaptureRequests, 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:

Abbildung 2. Abbildung einer Anfragewarteschlange für die laufende Kamerasitzung

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.