Çoklu kamera API'sı

Not: Bu sayfa, Kamera2 paketiyle ilgilidir. Uygulamanız Camera2'nin belirli, alt düzey özelliklerini gerektirmiyorsa KameraX'i kullanmanızı öneririz. Hem CameraX hem de Camera2, Android 5.0 (API düzeyi 21) ve sonraki sürümleri destekler.

Çoklu kamera özelliği Android 9'da (API düzeyi 28) kullanıma sunuldu. Kullanıma sunulduğundan beri, API'yi destekleyen cihazlar pazara girmiştir. Çok kameralı birçok kullanım alanı, belirli bir donanım yapılandırmasıyla yakından bağlantılıdır. Diğer bir deyişle, tüm kullanım alanları her cihazla uyumlu değildir. Bu nedenle, birden fazla kamera özelliği, Play Özellik Sunma için iyi bir adaydır.

Tipik kullanım alanlarından bazıları şunlardır:

  • Yakınlaştırma: Kırpma bölgesine veya istenen odak uzaklığına bağlı olarak kameralar arasında geçiş yapın.
  • Derinlik: Derinlik haritası oluşturmak için birden fazla kameranın kullanılması.
  • Bokeh: DSLR benzeri dar bir odak aralığını simüle etmek için, tahmin edilen derinlik bilgileri kullanma.

Mantıksal ve fiziksel kameralar arasındaki fark

Çok kameralı API'yi anlamak için mantıksal ve fiziksel kameralar arasındaki farkı anlamanız gerekir. Referans olarak, üç arka kameraya sahip bir cihaz ele alalım. Bu örnekte, üç arka kameranın her biri fiziksel kamera olarak kabul ediliyor. Mantıksal kamera, bu fiziksel kameralardan iki veya daha fazlasının gruplanmasıdır. Mantıksal kameranın çıkışı, alttaki fiziksel kameraların birinden gelen bir akış veya birden fazla fiziksel kameradan aynı anda gelen çok kaynaklı bir akış olabilir. Her iki durumda da akış, kamera Donanım Soyutlama Katmanı (HAL) tarafından yönetilir.

Birçok telefon üreticisi, genellikle cihazlarında önceden yüklenmiş olarak gelen birinci taraf kamera uygulamaları geliştirmektedir. Donanımın tüm özelliklerini kullanabilmek için özel veya gizli API'ler kullanabilir ya da sürücü uygulamasından başka uygulamaların erişemediği özel işlemler alabilirler. Bazı cihazlar yalnızca belirli ayrıcalıklı uygulamalara yönelik olarak farklı fiziksel kameralardan kaynaşmış kare akışı sağlayarak mantıksal kamera kavramını uygular. Genellikle çerçeveye fiziksel kameralardan yalnızca biri maruz kalır. Üçüncü taraf geliştiricilerin Android 9'dan önceki durumu aşağıdaki şemada gösterilmektedir:

Şekil 1. Kamera özellikleri genellikle yalnızca ayrıcalıklı uygulamalarda kullanılabilir

Android 9'dan itibaren Android uygulamalarında özel API'lere izin verilmeyecektir. Çerçeveye çoklu kamera desteğinin dahil edilmesiyle birlikte, Android en iyi uygulamaları telefon üreticilerinin aynı yöne bakan tüm fiziksel kameralar için mantıksal bir kamera göstermelerini önemle tavsiye eder. Üçüncü taraf geliştiricilerin, Android 9 ve sonraki sürümleri çalıştıran cihazlarda şunları görmeyi beklemesi gerekir:

Şekil 2. Android 9'dan itibaren tüm kameralı cihazlara tam geliştirici erişimi

Mantıksal kameranın sağladığı, tamamen Kamera HAL'sinin OEM uygulamasına bağlıdır. Örneğin, Pixel 3 gibi bir cihaz, mantıksal kamerasını istenen odak uzaklığı ve kırpma bölgesine göre fiziksel kameralarından birini seçecek şekilde uygular.

Çok kameralı API

Yeni API aşağıdaki yeni sabit değerleri, sınıfları ve yöntemleri ekler:

Android Uyumluluk Tanımlama Belgesi'nde (CDD) yapılan değişiklikler nedeniyle, çoklu kamera API'si için geliştiricilerin bazı beklentileri de vardır. Android 9'dan önce çift kameralı cihazlar vardı, ancak bu cihazlar aynı anda birden fazla kameranın açılması için deneme yanılma içeriyordu. Android 9 ve sonraki sürümlerde çoklu kamera, aynı mantıksal kameranın parçası olan bir çift fiziksel kameranın ne zaman açılabileceğini belirten bir dizi kural içerir.

Çoğu durumda, Android 9 ve sonraki sürümleri çalıştıran cihazlarda tüm fiziksel kameralar (kızılötesi gibi daha az yaygın olan sensör türleri hariç) ve kullanımı daha kolay bir mantıksal kamera gösterilir. Çalışması garanti edilen her akış kombinasyonu için mantıksal bir kameraya ait olan bir akışı, arka plandaki fiziksel kameralardan gelen iki akışla değiştirebilirsiniz.

Aynı anda birden fazla yayın

Birden çok kamera akışını aynı anda kullanma, tek bir kamerada aynı anda birden fazla yayın kullanma kurallarını kapsar. Önemli bir eklemeyle aynı kurallar birden fazla kamera için de geçerli. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA, mantıksal bir YUV_420_888 veya ham akışın iki fiziksel akışla nasıl değiştirileceğini açıklar. Yani YUV veya RAW türündeki her bir akış, aynı türde ve boyutta iki akışla değiştirilebilir. Tek kameralı cihazlar için aşağıdaki garantili yapılandırmanın kamera akışıyla başlayabilirsiniz:

  • 1. akış: YUV türü, id = 0 mantıksal kameradan MAXIMUM boyut

Daha sonra, çoklu kamera desteğine sahip bir cihaz bu mantıksal YUV akışını iki fiziksel akışla değiştirerek oturum oluşturmanıza olanak tanır:

  • 1. akış: YUV türü, id = 1 fiziksel kameradan MAXIMUM boyut
  • 2. akış: YUV türü, id = 2 fiziksel kameradan MAXIMUM boyut

Bir YUV veya RAW akışını,  yalnızca bu iki kamera CameraCharacteristics.getPhysicalCameraIds() altında listelenen mantıksal bir kamera gruplamasının parçasıysa ve iki eşdeğer akışla değiştirebilirsiniz.

Çerçevenin sağladığı garantiler, aynı anda birden fazla fiziksel kameradan kare almak için gereken minimum düzeydedir. Çoğu cihazda ek yayınlar desteklenir. Hatta bazen birden fazla fiziksel kamera cihazının bağımsız olarak açılmasına izin verilir. Çerçeveden emin olmak için kesin bir garanti vermeyeceğinizden, bunu yapmak için her cihazda test ve ayarlamalar yapmak için deneme ve yanılma yöntemini kullanmanız gerekir.

Birden çok fiziksel kamerayla oturum oluşturma

Çok kameralı bir cihazda fiziksel kamera kullanırken tek bir CameraDevice (mantıksal kamera) açın ve bu kamerayla tek bir oturumda etkileşimde bulunun. API düzeyi 28'de eklenen API CameraDevice.createCaptureSession(SessionConfiguration config)'yi kullanarak tek oturumu oluşturun. Oturum yapılandırmasında çeşitli çıkış yapılandırmaları bulunur. Bunların her birinin bir dizi çıkış hedefi ve isteğe bağlı olarak istenen bir fiziksel kamera kimliği vardır.

Şekil 3. SessionConfiguration ve ExitConfiguration modeli

Yakalama isteklerinin kendileriyle ilişkilendirilmiş bir çıkış hedefi vardır. Çerçeve, hangi çıkış hedefinin eklendiğine göre isteklerin hangi fiziksel (veya mantıksal) kameraya gönderileceğini belirler. Çıktı hedefi, fiziksel kamera kimliğiyle birlikte çıkış yapılandırması olarak gönderilen çıkış hedeflerinden birine karşılık geliyorsa bu fiziksel kamera, isteği alıp işler.

Bir çift fiziksel kamera kullanırken

Çok kameralı kamera API'lerine bir diğer ek özellik de mantıksal kameraları tanımlayıp bunların arkasındaki fiziksel kameraları bulabilme olanağıdır. Mantıksal kamera yayınlarından birini değiştirmek için kullanabileceğiniz potansiyel fiziksel kamera çiftlerini tespit etmenize yardımcı olacak bir işlev tanımlayabilirsiniz:

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;
}

Fiziksel kameraların durumu, mantıksal kamera tarafından kontrol edilir. Bir "çift kamera" açmak için fiziksel kameralara karşılık gelen mantıksal kamerayı açın:

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);
            }
        });
    }

Bu işlem, hangi kameranın açılacağını seçmenin yanı sıra, eski Android sürümlerinde kameranın açılmasıyla aynıdır. Yeni oturum yapılandırma API'sini kullanarak yakalama oturumu oluşturmak, çerçeveye belirli hedefleri belirli fiziksel kamera kimlikleriyle ilişkilendirmesini bildirir:

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)); }

Desteklenen akış kombinasyonları hakkında bilgi edinmek için createCaptureSession bölümünü inceleyin. Akışları birleştirmek, tek bir mantıksal kamerayla birden fazla yayın yapmaktır. Uyumluluk, aynı yapılandırmanın kullanılmasını ve bu yayınlardan birinin aynı mantıksal kameranın parçası olan iki fiziksel kameradan gelen iki akışla değiştirilmesini de kapsar.

Kamera oturumu hazır olduğunda, istediğiniz yakalama isteklerini gönderin. Yakalama isteğinin her hedefi, varsa verilerini ilişkili fiziksel kameradan alır veya mantıksal kamera kullanmaya başlar.

Zoom örnek kullanım alanı

Fiziksel kameraların tek bir akışta birleştirilmesi, kullanıcıların farklı bir görüş alanı deneyimi için farklı fiziksel kameralar arasında geçiş yapabilmesini ve etkili bir şekilde farklı bir "zum düzeyi" yakalayabilmesini sağlar.

Şekil 4. Yakınlaştırma düzeyinde kullanım alanı için kameraları değiştirme örneği (Pixel 3 reklamından)

Kullanıcıların geçiş yapabilmesi için fiziksel kamera çiftini seçerek başlayın. Maksimum etki için, mümkün olan minimum ve maksimum odak uzaklığını sağlayan kamera çiftini seçebilirsiniz.

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;
    }

Bunun için mantıklı bir mimari, her akış için bir tane olacak şekilde iki SurfaceViews kullanmak olabilir. Bu SurfaceViews, kullanıcı etkileşimine bağlı olarak değiştirilir. Böylece, her seferinde yalnızca bir tane görünür.

Aşağıdaki kod, mantıksal kamerayı açma, kamera çıkışlarını yapılandırma, kamera oturumu oluşturma ve iki önizleme akışı başlatma işlemlerini nasıl yapacağınızı gösterir:

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();
            }
        });

Tek yapmanız gereken, kullanıcının iki yüzey arasında geçiş yapması için bir düğme sağlamak veya SurfaceView simgesine iki kez dokunmak gibi bir kullanıcı arayüzü sağlamaktır. Hatta bir tür sahne analizi gerçekleştirip iki akış arasında otomatik olarak geçiş yapabilirsiniz.

Mercek bozulması

Tüm lenslerin belirli bir miktarda bozulması vardır. Android'de, lenslerin oluşturduğu distorsiyonu sorgulayabilirsiniz. Bunun için CameraCharacteristics.LENS_DISTORTION, kullanımdan kaldırılmış durumdaki CameraCharacteristics.LENS_RADIAL_DISTORTION'in yerini alır. Mantıksal kameralarda distorsiyon minimum düzeydedir ve uygulamanız, kameradan gelen kareleri daha fazla veya daha az kullanabilir. Fiziksel kameralarda, özellikle geniş objektiflerde potansiyel olarak çok farklı lens konfigürasyonları vardır.

Bazı cihazlarda CaptureRequest.DISTORTION_CORRECTION_MODE aracılığıyla otomatik distorsiyon düzeltme özelliği kullanılabilir. Distorsiyon düzeltme özelliği, çoğu cihazda varsayılan olarak açıktır.

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(), ...);

Bu modda bir çekim isteği ayarlamak, kameranın üretebileceği kare hızını etkileyebilir. Bozulma düzeltmesini yalnızca sabit resim çekimlerinde ayarlamayı seçebilirsiniz.