Mehrere Kamerastreams gleichzeitig verwenden

Hinweis:Diese Seite bezieht sich auf das Camera2-Paket. Sofern für Ihre App keine spezifischen Low-Level-Funktionen von Camera2 erforderlich sind, empfehlen wir die Verwendung von CameraX. Sowohl CameraX als auch Camera2 unterstützen Android 5.0 (API-Level 21) und höher.

Eine Kamera-App kann mehr als einen Frame-Stream gleichzeitig verwenden. In Manche Streams erfordern sogar eine andere Frame-Auflösung oder Pixelzahl. Format. Hier einige typische Anwendungsfälle:

  • Videoaufnahme: ein Stream für die Vorschau, ein anderer wird codiert und gespeichert in einer Datei.
  • Barcode-Scanning: ein Stream für die Vorschau, ein anderer für die Barcodeerkennung.
  • Computergestützte Fotografie: ein Stream für die Vorschau, ein anderer für die Gesichts-/Szene -Erkennung.

Bei der Verarbeitung von Frames entstehen nicht unerhebliche Leistungskosten, bei paralleler Stream- oder Pipelineverarbeitung vervielfacht werden.

Ressourcen wie CPU, GPU und DSP können möglicherweise Wiederverarbeitung des Frameworks aber Ressourcen wie der Arbeitsspeicher wachsen linear zu.

Mehrere Ziele pro Anfrage

Mehrere Kamerastreams können zu einem einzigen CameraCaptureRequest Im folgenden Code-Snippet sehen Sie, wie eine Kamerasitzung Stream für die Kameravorschau und einen weiteren Stream für die Bildverarbeitung:

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 die den in den StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) und StreamComfigurationMap.GetOutputStallDuration(int, Size) Die tatsächliche Leistung variiert von Gerät zu Gerät, auch wenn Android garantiert die Unterstützung bestimmter Kombinationen in Abhängigkeit von drei Variablen: Ausgabetyp, Ausgabegröße und Hardwareebene.

Die Verwendung einer nicht unterstützten Kombination von Variablen funktioniert möglicherweise bei einer niedrigen Framerate. wenn nicht, löst sie einen der fehlgeschlagenen Callbacks aus. In der Dokumentation zu createCaptureSession was garantiert funktioniert.

Ausgabetyp

Der Ausgabetyp bezieht sich auf das Format, in dem die Frames codiert sind. Die Mögliche Werte sind PRIV, YUV, JPEG und RAW. Die Dokumentation für createCaptureSession beschreibt.

Wenn Sie den Ausgabetyp Ihrer Anwendung auswählen, und wählen Sie dann ImageFormat.YUV_420_888 für die Frame-Analyse und ImageFormat.JPEG für still Bilder. Für Vorschau- und Aufzeichnungsszenarien verwenden Sie wahrscheinlich ein SurfaceView, TextureView MediaRecorder MediaCodec oder RenderScript.Allocation. In geben Sie in diesen Fällen kein Bildformat an. Aus Kompatibilitätsgründen wird sie wie folgt gezählt: ImageFormat.PRIVATE, unabhängig vom intern verwendeten Format. So fragen Sie die unterstützten Formate ab: die von einem Gerät stammen, CameraCharacteristics, verwenden Sie den folgenden Code:

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 StreamConfigurationMap.getOutputSizes(), aber nur zwei stehen im Zusammenhang mit der Kompatibilität: PREVIEW und MAXIMUM. Die Größen als Obergrenzen fungieren. Wenn etwas mit der Größe PREVIEW funktioniert, ist alles mit einem kleiner als PREVIEW funktioniert. Dasselbe gilt für MAXIMUM. Die Dokumentation für CameraDevice werden diese Größen erläutert.

Die verfügbaren Ausgabegrößen hängen vom Format ab. In Anbetracht der 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 den Anwendungsfällen für Kameravorschau und -aufzeichnung die Zielklasse, um zu bestimmen, unterstützten Größen. Das Format wird vom Kamera-Framework 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);

Um die Größe von MAXIMUM zu erhalten, sortieren Sie die Ausgabegrößen nach Bereich und geben Sie die größte 1:

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 passt, oder 1080p (1920 x 1080), je nachdem, welcher Wert kleiner ist. Das Seitenverhältnis stimmt möglicherweise nicht mit dem auf das Seitenverhältnis des Bildschirms achten. auf den Stream zuschneiden, um ihn im Vollbildmodus anzuzeigen. Um die richtigen Vorschaugröße angezeigt wird, vergleichen Sie die verfügbaren Ausgabegrößen mit der Anzeigegröße, Dabei ist zu berücksichtigen, dass der Bildschirm gedreht werden kann.

Mit dem folgenden Code wird die Hilfsklasse SmartSize definiert, mit der die Größe Vergleiche etwas einfacher:

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 Hardwarestufe prüfen

Prüfen Sie die unterstützte Hardware, um die zur Laufzeit verfügbaren Funktionen zu ermitteln Level mit CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL

Mit einem CameraCharacteristics -Objekt enthält, 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);

Zusammensetzen aller Teile

Anhand des Ausgabetyps, der Ausgabegröße und der Hardwareebene können Sie festlegen, Kombinationen von Streams gültig sind. Das folgende Diagramm ist eine Momentaufnahme von CameraDevice unterstützte Konfigurationen mit LEGACY Hardwareebene.

Ziel 1 Ziel 2 Ziel 3 Beispiele für Anwendungsfälle
Typ Max. Größe Typ Max. Größe Typ Max. Größe
PRIV MAXIMUM Einfache Vorschau, GPU-Videoverarbeitung oder Videoaufzeichnung ohne Vorschau.
JPEG MAXIMUM Standbilderfassung ohne Sucher.
YUV MAXIMUM App-interne Video-/Bildverarbeitung
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 Aufnahme sowie In-App-Verarbeitung noch möglich.

LEGACY ist die niedrigste mögliche Hardwarestufe. Diese Tabelle zeigt, dass alle Gerät, das Camera2 (API-Level 21 und höher) unterstützt, kann bis zu drei ausgeben gleichzeitige Streams mit der richtigen Konfiguration. den Overhead, der die Leistung einschränkt, z. B. durch Speicher-, CPU- oder thermische Einschränkungen.

Außerdem müssen in Ihrer App Targeting-Ausgabepuffer konfiguriert werden. Zum Beispiel auf ein Gerät mit der Hardwareebene LEGACY ausrichten, könnten Sie zwei Zielausgabe einrichten Oberflächen, eine mit ImageFormat.PRIVATE und eine mit ImageFormat.YUV_420_888 Diese Kombination wird bei Verwendung des PREVIEW Größe. Mit der zuvor in diesem Thema definierten Funktion wird der Für die erforderlichen Vorschaugrößen für eine Kamera-ID ist folgender Code erforderlich:

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 über die bereitgestellten Callbacks 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 der Ausgabegröße der Kamera entspricht, indem Sie Folgendes aufrufen: SurfaceHolder.setFixedSize() oder Sie gehen ähnlich vor wie AutoFitSurfaceView aus der Common Modul Kamerabeispiele auf GitHub. Dabei wird eine absolute Größe festgelegt, das Seitenverhältnis und den verfügbaren Platz berücksichtigen, während und anpassen, wenn Aktivitätsänderungen ausgelöst werden.

Die andere Oberfläche wird über ImageReader mit dem gewünschten Format ist einfacher ist, da Sie keine Rückrufe abwarten 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 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 kleinsten gemeinsamen Nenner ausgerichtet. Sie können Fügen Sie eine bedingte Verzweigung hinzu und verwenden Sie für eines der Ausgabeziele die Größe RECORD in Geräten mit der Hardwarestufe LIMITED an. Größe: MAXIMUM für Geräte mit der Hardwarestufe FULL.