Interfejs API do obsługi wielu kamer

Uwaga: ta strona dotyczy pakietu Aparat2. Jeśli Twoja aplikacja nie wymaga konkretnych, niskopoziomowych funkcji z Aparatu 2, zalecamy używanie AparatuX. Aparaty CameraX i Aparat 2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.

Możliwość obsługi wielu aparatów została wprowadzona w Androidzie 9 (poziom interfejsu API 28). Od momentu premiery właśnie na rynku pojawiły się urządzenia obsługujące ten interfejs. Korzystanie z wielu aparatów są ściśle sprzężone z konkretną konfiguracją sprzętową. Innymi słowy, nie można Wszystkie przypadki użycia są zgodne z każdym urządzeniem, co sprawia, że obsługa wielu aparatów ma dobrego kandydata do funkcji Google Play Dostawa.

Oto kilka typowych zastosowań:

  • Zoom: przełączanie się między aparatami w zależności od regionu przycięcia lub wybranej ogniskowej. długości.
  • Głębokość: tworzenie mapy głębi przy użyciu kilku aparatów.
  • Bokeh: na podstawie uzyskanych informacji o głębi symuluje wąski lustrzankę cyfrową. i zakresu ostrości.

Różnica między aparatem logicznym a fizycznym

Aby zrozumieć interfejs API do obsługi wielu kamer, musisz zrozumieć różnicę między z kamer logicznych i fizycznych. Dla porównania: aparat tylny. W tym przykładzie każdy z 3 tylnych aparatów uważany za aparat fizyczny. Kamera logiczna składa się z co najmniej dwóch tych fizycznych aparatów. Wynik funkcji logicznej może być strumieniem pochodzącym z jednej z podstawowych kamer fizycznych, lub scalony strumień pochodzący z więcej niż jednej kamery fizycznej jednocześnie. W obu przypadkach transmisja jest obsługiwana przez sprzęt kamery Warstwa abstrakcji (HAL).

Wielu producentów telefonów tworzy własne aplikacje aparatu, które zwykle są fabrycznie instalowane na urządzeniach. Aby móc korzystać ze wszystkich możliwości sprzętu, mogą używać prywatnych lub ukrytych interfejsów API albo być traktowani w specjalny sposób implementacji sterownika, do której inne aplikacje nie mają dostępu. Niektóre wdrożono koncepcję kamer logicznych, oferując połączone strumienie kadrów z różnych aparatów fizycznych, ale tylko z określonych uprzywilejowanych aplikacji. Często tylko jedna z fizycznych kamer jest narażona platformy. W przypadku deweloperów zewnętrznych przed Androidem 9 sytuacja na tym schemacie:

Rysunek 1. Funkcje aparatu zwykle są dostępne tylko dla aplikacji z podwyższonymi uprawnieniami

Od Androida 9 prywatne interfejsy API nie są już dozwolone w aplikacjach na Androida. Dzięki włączeniu obsługi wielu aparatów na Androidzie zdecydowanie zalecamy, aby producenci telefonów udostępniali aparat logiczny dla wszystkich aparatów fizycznych, które są skierowane w tę samą stronę. Oto co na urządzeniach z Androidem 9 oraz wyższe:

Rys. 2. Pełny dostęp programisty do wszystkich aparatów. od Androida 9

Opcje oferowane przez aparat logiczny zależy całkowicie od implementacji OEM HAL aparatu. Na przykład urządzenie, takie jak Pixel 3, ma zaimplementowaną funkcję w taki sposób, że wybiera on jedną ze swoich kamer o wybranej ogniskowej i regionie przycięcia.

Interfejs API obsługi wielu kamer

Do nowego interfejsu API dodano te nowe stałe, klasy i metody:

W związku z zmianami w dokumencie z definicją zgodności z Androidem (CDD), interfejs Multi-camera API ma też pewne oczekiwania wobec deweloperów. Urządzenia z 2 aparatami, które istniały przed Androidem 9, ale otwierały więcej niż 1 aparat. jednocześnie uwzględniać metody prób i błędów. W Androidzie 9 lub nowszym: kilka aparatów zawiera zestaw reguł, które określają, kiedy można otworzyć parę fizycznych które są częścią tego samego aparatu logicznego.

W większości przypadków na urządzeniach z Androidem 9 lub nowszym narażone są (z wyjątkiem mniej popularnych typów czujników, takich jak podczerwień) wraz z łatwiejszy w użyciu aparat logiczny. W przypadku każdej kombinacji strumieni, która jeden strumień należący do kamery logicznej może zostać zastąpiony 2 transmisje z podstawowych kamer fizycznych.

Wiele strumieni jednocześnie

Jednoczesne korzystanie z kilku kamer Omówienie zasad korzystania z kilku transmisji jednocześnie w ramach 1 kamery. Oprócz tego te same zasady dotyczą wielu aparatów. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA wyjaśnia, jak zastąpić logiczny strumień YUV_420_888 lub nieprzetworzony strumień dwoma strumienie fizyczne. Oznacza to, że każdy strumień typu YUV lub RAW można zastąpić dwóch strumieni o takim samym typie i rozmiarze. Możesz zacząć od transmisji z kamery tę gwarantowaną konfigurację dla urządzeń z jedną kamerą:

  • Strumień 1: typ YUV, rozmiar MAXIMUM z kamery logicznej id = 0

Urządzenie z obsługą wielu kamer umożliwia utworzenie sesji zastępując logiczny strumień YUV dwoma strumieniami fizycznymi:

  • Strumień 1: typ YUV, rozmiar MAXIMUM z kamery fizycznej id = 1
  • Strumień 2: typ YUV, rozmiar MAXIMUM z kamery fizycznej id = 2

Strumień YUV lub RAW możesz zastąpić dwoma równoważnymi strumieniami tylko wtedy, gdy które należą do logicznej grupy kamer,  ujętej CameraCharacteristics.getPhysicalCameraIds()

Gwarancje zawarte w ramach programu to tylko minimalne minimum wymagane do umożliwia jednoczesne robienie klatek z więcej niż 1 kamery fizycznej. Dodatkowe strumienie są obsługiwane przez większość urządzeń. Czasami umożliwia nawet niezależnie od siebie. Ponieważ nie jest to trudna gwarancja wymaga testowania poszczególnych urządzeń i dostrajania przy użyciu metody prób i błędów.

Tworzenie sesji z kilkoma kamerami fizycznymi

Jeśli korzystasz z fizycznych aparatów na urządzeniu obsługującym wiele aparatów, otwórz jeden CameraDevice (kamera logiczna) i wchodź z nią w interakcję w jednym miejscu. . Utwórz pojedynczą sesję za pomocą interfejsu API CameraDevice.createCaptureSession(SessionConfiguration config), czyli dodano w API poziomu 28. Konfiguracja sesji zawiera szereg danych wyjściowych konfiguracji, z których każda ma zbiór celów wyjściowych, opcjonalnie żądany identyfikator fizycznego aparatu.

Rysunek 3. Modele SessionConfiguration i OutputConfiguration

Z żądaniami przechwytywania jest powiązane miejsce docelowe wyjściowe. Struktura Określa, do której fizycznej (lub logicznej) kamery są wysyłane żądania na podstawie które dane wyjściowe są dołączone. Jeśli miejsce docelowe danych wyjściowych odpowiada jednemu ze cele wyjściowe, które zostały wysłane jako konfiguracja danych wyjściowych wraz z fizycznym do identyfikatora, a następnie odbiera i przetwarza żądanie.

Używanie pary aparatów fizycznych

Kolejnym dodatkiem do interfejsów API dla wielu kamer jest możliwość rozpoznawania aparatów logicznych i znajdować za nimi fizyczne kamery. Możesz zdefiniować ułatwiając identyfikację potencjalnych par aparatów fizycznych, których możesz użyć w celu zastąpienia jednego z logicznych strumieni kamery:

Kotlin

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)

    fun findDualCameras(manager: CameraManager, facing: Int? = null): List {
        val dualCameras = MutableList()

        // Iterate over all the available camera characteristics
        manager.cameraIdList.map {
            Pair(manager.getCameraCharacteristics(it), it)
        }.filter {
            // Filter by cameras facing the requested direction
            facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
        }.filter {
            // Filter by logical cameras
            // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
            it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
        }.forEach {
            // All possible pairs from the list of physical cameras are valid results
            // NOTE: There could be N physical cameras as part of a logical camera grouping
            // getPhysicalCameraIds() requires API >= 28
            val physicalCameras = it.first.physicalCameraIds.toTypedArray()
            for (idx1 in 0 until physicalCameras.size) {
                for (idx2 in (idx1 + 1) until physicalCameras.size) {
                    dualCameras.add(DualCamera(
                        it.second, physicalCameras[idx1], physicalCameras[idx2]))
                }
            }
        }

        return dualCameras
    }

Java

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    final class DualCamera {
        final String logicalId;
        final String physicalId1;
        final String physicalId2;

        DualCamera(String logicalId, String physicalId1, String physicalId2) {
            this.logicalId = logicalId;
            this.physicalId1 = physicalId1;
            this.physicalId2 = physicalId2;
        }
    }
    List findDualCameras(CameraManager manager, Integer facing) {
        List dualCameras = new ArrayList<>();

        List cameraIdList;
        try {
            cameraIdList = Arrays.asList(manager.getCameraIdList());
        } catch (CameraAccessException e) {
            e.printStackTrace();
            cameraIdList = new ArrayList<>();
        }

        // Iterate over all the available camera characteristics
        cameraIdList.stream()
                .map(id -> {
                    try {
                        CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
                        return new Pair<>(characteristics, id);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }
                })
                .filter(pair -> {
                    // Filter by cameras facing the requested direction
                    return (pair != null) &&
                            (facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
                })
                .filter(pair -> {
                    // Filter by logical cameras
                    // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
                    IntPredicate logicalMultiCameraPred =
                            arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
                    return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
                            .anyMatch(logicalMultiCameraPred);
                })
                .forEach(pair -> {
                    // All possible pairs from the list of physical cameras are valid results
                    // NOTE: There could be N physical cameras as part of a logical camera grouping
                    // getPhysicalCameraIds() requires API >= 28
                    String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
                    for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
                        for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
                            dualCameras.add(
                                    new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
                        }
                    }
                });
return dualCameras;
}

Obsługa stanów fizycznych kamer jest określana przez kamerę logiczną. Do otworzyć „podwójny aparat”, należy uruchomić kamerę logiczną odpowiadającą kamery:

Kotlin

fun openDualCamera(cameraManager: CameraManager,
                       dualCamera: DualCamera,
        // AsyncTask is deprecated beginning API 30
                       executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                       callback: (CameraDevice) -> Unit) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(
            dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
                override fun onOpened(device: CameraDevice) = callback(device)
                // Omitting for brevity...
                override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
                override fun onDisconnected(device: CameraDevice) = device.close()
            })
    }

Java

void openDualCamera(CameraManager cameraManager,
                        DualCamera dualCamera,
                        Executor executor,
                        CameraDeviceCallback cameraDeviceCallback
    ) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
               cameraDeviceCallback.callback(cameraDevice);
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int i) {
                onDisconnected(cameraDevice);
            }
        });
    }

Oprócz wyboru aparatu, który chcesz otworzyć, proces jest taki sam jak przy otwieraniu aparat w starszych wersjach Androida. Utwórz sesję przechwytywania za pomocą nowego tagu API konfiguracji sesji informuje platformę, że ma powiązać określone środowiska docelowe z konkretnych identyfikatorów fizycznych aparatów fotograficznych:

Kotlin

/**
 * Helper type definition that encapsulates 3 sets of output targets:
 *
 *   1. Logical camera
 *   2. First physical camera
 *   3. Second physical camera
 */
typealias DualCameraOutputs =
        Triple?, MutableList?, MutableList?>

fun createDualCameraSession(cameraManager: CameraManager,
                            dualCamera: DualCamera,
                            targets: DualCameraOutputs,
                            // AsyncTask is deprecated beginning API 30
                            executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                            callback: (CameraCaptureSession) -> Unit) {

    // Create 3 sets of output configurations: one for the logical camera, and
    // one for each of the physical cameras.
    val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
    val outputConfigsPhysical1 = targets.second?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
    val outputConfigsPhysical2 = targets.third?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }

    // Put all the output configurations into a single flat array
    val outputConfigsAll = arrayOf(
        outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
        .filterNotNull().flatMap { it }

    // Instantiate a session configuration that can be used to create a session
    val sessionConfiguration = SessionConfiguration(
        SessionConfiguration.SESSION_REGULAR,
        outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) = callback(session)
            // Omitting for brevity...
            override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
        })

    // Open the logical camera using the previously defined function
    openDualCamera(cameraManager, dualCamera, executor = executor) {

        // Finally create the session and return via callback
        it.createCaptureSession(sessionConfiguration)
    }
}

Java

/**
 * Helper class definition that encapsulates 3 sets of output targets:
 * 

* 1. Logical camera * 2. First physical camera * 3. Second physical camera */ final class DualCameraOutputs { private final List logicalCamera; private final List firstPhysicalCamera; private final List secondPhysicalCamera; public DualCameraOutputs(List logicalCamera, List firstPhysicalCamera, List third) { this.logicalCamera = logicalCamera; this.firstPhysicalCamera = firstPhysicalCamera; this.secondPhysicalCamera = third; } public List getLogicalCamera() { return logicalCamera; } public List getFirstPhysicalCamera() { return firstPhysicalCamera; } public List getSecondPhysicalCamera() { return secondPhysicalCamera; } } interface CameraCaptureSessionCallback { void callback(CameraCaptureSession cameraCaptureSession); } void createDualCameraSession(CameraManager cameraManager, DualCamera dualCamera, DualCameraOutputs targets, Executor executor, CameraCaptureSessionCallback cameraCaptureSessionCallback) { // Create 3 sets of output configurations: one for the logical camera, and // one for each of the physical cameras. List outputConfigsLogical = targets.getLogicalCamera().stream() .map(OutputConfiguration::new) .collect(Collectors.toList()); List outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1); return outputConfiguration; }) .collect(Collectors.toList()); List outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2); return outputConfiguration; }) .collect(Collectors.toList()); // Put all the output configurations into a single flat array List outputConfigsAll = Stream.of( outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2 ) .filter(Objects::nonNull) .flatMap(Collection::stream) .collect(Collectors.toList()); // Instantiate a session configuration that can be used to create a session SessionConfiguration sessionConfiguration = new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigsAll, executor, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSessionCallback.callback(cameraCaptureSession); } // Omitting for brevity... @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSession.getDevice().close(); } }); // Open the logical camera using the previously defined function openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) -> // Finally create the session and return via callback c.createCaptureSession(sessionConfiguration)); }

Zobacz createCaptureSession . Łączenie strumieni Dotyczy wielu strumieni z jednej kamery logicznej. Zgodność obejmuje za pomocą tej samej konfiguracji i zastąpienie jednego z nich dwoma strumieniami. za pomocą dwóch fizycznych kamer, które są częścią tej samej kamery logicznej.

Za pomocą sesja kamery gotowe, wyślij potrzebne żądania przechwytywania. Każdy miejsce docelowe żądania przechwytywania otrzymuje dane z powiązanego lub użyć kamery logicznej.

Przykład zastosowania Zoom

Można wykorzystać połączenie kamer fizycznych w jeden strumień, użytkownicy mogą przełączać się między różnymi aparatami fizycznymi, aby cieszyć się różne pola widzenia, co pozwala zarejestrować inny „poziom powiększenia”.

Rysunek 4. Przykład zmiany aparatu na potrzeby zastosowania poziomu powiększenia (z reklamy Pixela 3)

Zacznij od wybrania pary aparatów fizycznych, aby umożliwić użytkownikom przełączanie się między kamerami między nimi. Aby osiągnąć maksymalny efekt, wybierz parę kamer, które od minimalnej i maksymalnej dostępnej ogniskowej.

Kotlin

fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {

    return findDualCameras(manager, facing).map {
        val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
        val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)

        // Query the focal lengths advertised by each physical camera
        val focalLengths1 = characteristics1.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
        val focalLengths2 = characteristics2.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)

        // Compute the largest difference between min and max focal lengths between cameras
        val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
        val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!

        // Return the pair of camera IDs and the difference between min and max focal lengths
        if (focalLengthsDiff1 < focalLengthsDiff2) {
            Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
        } else {
            Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
        }

        // Return only the pair with the largest difference, or null if no pairs are found
    }.maxByOrNull { it.second }?.first
}

Java

// Utility functions to find min/max value in float[]
    float findMax(float[] array) {
        float max = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            max = Math.max(max, cur);
        return max;
    }
    float findMin(float[] array) {
        float min = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            min = Math.min(min, cur);
        return min;
    }

DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
        return findDualCameras(manager, facing).stream()
                .map(c -> {
                    CameraCharacteristics characteristics1;
                    CameraCharacteristics characteristics2;
                    try {
                        characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
                        characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }

                    // Query the focal lengths advertised by each physical camera
                    float[] focalLengths1 = characteristics1.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
                    float[] focalLengths2 = characteristics2.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);

                    // Compute the largest difference between min and max focal lengths between cameras
                    Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
                    Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);

                    // Return the pair of camera IDs and the difference between min and max focal lengths
                    if (focalLengthsDiff1 < focalLengthsDiff2) {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
                    } else {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
                    }

                }) // Return only the pair with the largest difference, or null if no pairs are found
                .max(Comparator.comparing(pair -> pair.second)).get().first;
    }

Rozsądną w tym przypadku architekturą byłaby dwie SurfaceViews – po jednym na każdy strumień. Te komponenty typu SurfaceViews są zastępowane na podstawie interakcji użytkownika, tak że tylko 1 z nich jest widoczne w każdej chwili.

Ten kod pokazuje, jak otworzyć kamerę logiczną i skonfigurować ją utworzyć sesję kamery i uruchomić 2 strumienie podglądu:

Kotlin

val cameraManager: CameraManager = ...

// Get the two output targets from the activity / fragment
val surface1 = ...  // from SurfaceView
val surface2 = ...  // from SurfaceView

val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2))

// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->

  // Create a single request which has one target for each physical camera
  // NOTE: Each target receive frames from only its associated physical camera
  val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
  val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
    arrayOf(surface1, surface2).forEach { addTarget(it) }
  }.build()

  // Set the sticky request for the session and you are done
  session.setRepeatingRequest(captureRequest, null, null)
}

Java

CameraManager manager = ...;

        // Get the two output targets from the activity / fragment
        Surface surface1 = ...;  // from SurfaceView
        Surface surface2 = ...;  // from SurfaceView

        DualCamera dualCamera = findShortLongCameraPair(manager, null);
                DualCameraOutputs outputTargets = new DualCameraOutputs(
                null, Collections.singletonList(surface1), Collections.singletonList(surface2));

        // Here you open the logical camera, configure the outputs and create a session
        createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
            // Create a single request which has one target for each physical camera
            // NOTE: Each target receive frames from only its associated physical camera
            CaptureRequest.Builder captureRequestBuilder;
            try {
                captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);

                // Set the sticky request for the session and you are done
                session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        });

Wystarczy, że udostępnisz interfejs, który pozwoli użytkownikowi przełączać się między nimi. np. przycisk lub dwukrotnie SurfaceView. Możesz nawet Zrobić jakąś analizę sceny i przełączać się między 2 strumieniami automatycznie.

Zniekształcenie obiektywu

Wszystkie obiektywy generują pewną ilość zniekształceń. Na Androidzie można wysyłać zapytania zniekształcenia spowodowane przez soczewki CameraCharacteristics.LENS_DISTORTION, , która zastępuje wycofane CameraCharacteristics.LENS_RADIAL_DISTORTION W przypadku kamer logicznych zniekształcenia są minimalne, a aplikacja może korzystać z liczba klatek na sekundę z kamery. W przypadku aparatów fizycznych mogą istnieć potencjalnie bardzo różne konfiguracje obiektywów, zwłaszcza obiektywów.

Niektóre urządzenia mogą korzystać z automatycznej korekcji zniekształceń przez: CaptureRequest.DISTORTION_CORRECTION_MODE Korekcja zniekształceń jest domyślnie włączona na większości urządzeń.

Kotlin

val cameraSession: CameraCaptureSession = ...

        // Use still capture template to build the capture request
        val captureRequest = cameraSession.device.createCaptureRequest(
            CameraDevice.TEMPLATE_STILL_CAPTURE
        )

        // Determine if this device supports distortion correction
        val characteristics: CameraCharacteristics = ...
        val supportsDistortionCorrection = characteristics.get(
            CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
        )?.contains(
            CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
        ) ?: false

        if (supportsDistortionCorrection) {
            captureRequest.set(
                CaptureRequest.DISTORTION_CORRECTION_MODE,
                CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            )
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequest.build(), ...)

Java

CameraCaptureSession cameraSession = ...;

        // Use still capture template to build the capture request
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
                    CameraDevice.TEMPLATE_STILL_CAPTURE
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Determine if this device supports distortion correction
        CameraCharacteristics characteristics = ...;
        boolean supportsDistortionCorrection = Arrays.stream(
                        characteristics.get(
                                CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
                        ))
                .anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
        if (supportsDistortionCorrection) {
            captureRequestBuilder.set(
                    CaptureRequest.DISTORTION_CORRECTION_MODE,
                    CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            );
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequestBuilder.build(), ...);

Ustawienie żądania przechwytywania w tym trybie może wpłynąć na liczbę klatek, jaką nagrywany przez kamerę. Możesz ustawić korekcję zniekształcenia tylko na i wykonania nieruchomych obrazów.