Çoklu kamera API'sı

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

Çoklu kamera, Android 9 (API düzeyi 28) ile kullanıma sunulmuştur. API'nin kullanıma sunulmasından bu yana, API'yi destekleyen cihazlar piyasaya sürüldü. Çoklu kamera kullanım alanlarının çoğu, belirli bir donanım yapılandırmasıyla sıkı bir şekilde bağlantılıdır. Başka bir deyişle, tüm kullanım alanları her cihazla uyumlu değildir. Bu nedenle,  çoklu kamera özellikleri Play Feature Delivery için iyi bir adaydır.

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

  • Yakınlaştırma: Kırpma bölgesine veya seçilen odak uzaklığına bağlı olarak kameralar arasında geçiş yapma.
  • Derinlik: Derinlik haritası oluşturmak için birden fazla kamera kullanma.
  • Bokeh: DSLR benzeri dar bir odak uzaklığını simüle etmek için tahmin edilen derinlik bilgilerini kullanma.

Mantıksal ve fiziksel kameralar arasındaki fark

Çoklu kamera API'sini anlamak için mantıksal ve fiziksel kameralar arasındaki farkı anlamak gerekir. Örnek olarak, üç arka kamerası olan bir cihazı ele alalım. Bu örnekte, üç arka kameranın her biri fiziksel kamera olarak kabul edilir. Mantıksal kamera ise bu fiziksel kameralardan iki veya daha fazlasının gruplandırılmasıdır. Mantıksal kameranın çıkışı, temel fiziksel kameralardan birinden gelen bir akış veya aynı anda birden fazla temel fiziksel kameradan gelen birleştirilmiş bir akış olabilir. Her iki durumda da akış, kamera donanım soyutlama katmanı (HAL) tarafından işlenir.

Birçok telefon üreticisi, genellikle cihazlarına önceden yüklenmiş olarak gelen birinci taraf kamera uygulamaları geliştirir. Donanımın tüm özelliklerini kullanmak için özel veya gizli API'lerden yararlanabilir ya da sürücü uygulamasından diğer uygulamaların erişemediği özel muamele görebilirler. Bazı cihazlar, farklı fiziksel kameralardan birleştirilmiş kare akışı sağlayarak mantıksal kamera kavramını uygular ancak bu yalnızca belirli ayrıcalıklı uygulamalar için geçerlidir. Genellikle fiziksel kameralardan yalnızca biri çerçeveye maruz kalır. Android 9'dan önce üçüncü taraf geliştiricilerin durumu aşağıdaki şemada gösterilmiştir:

Genellikle yalnızca ayrıcalıklı uygulamalarda kullanılabilen kamera özellikleri
Şekil 1. Genellikle yalnızca ayrıcalıklı uygulamalara sunulan kamera özellikleri

Android 9'dan itibaren Android uygulamalarında özel API'lere artık izin verilmemektedir. Çerçeveye çoklu kamera desteğinin eklenmesiyle birlikte Android en iyi uygulamaları, telefon üreticilerinin aynı yöne bakan tüm fiziksel kameralar için mantıksal bir kamera sunmasını önemle tavsiye eder. Android 9 ve sonraki sürümleri çalıştıran cihazlarda üçüncü taraf geliştiricilerin görmeyi bekleyebileceği durumlar şunlardır:

Android 9'dan itibaren tüm kamera cihazlarına tam geliştirici erişimi
Şekil 2. Android 9'dan itibaren tüm kamera cihazlarına tam geliştirici erişimi

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

Çoklu kamera API'si

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

Android Uyumluluk Tanımlama Belgesi'nde (CDD) yapılan değişiklikler nedeniyle çoklu kamera API'si, geliştiricilerden belirli beklentiler de içerir. Çift kameralı cihazlar Android 9'dan önce de vardı ancak aynı anda birden fazla kamera açmak deneme yanılma gerektiriyordu. 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 sunar.

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

Aynı anda birden fazla yayın

Aynı anda birden fazla kamera görüntü akışı kullanma bölümünde, tek bir kamerada aynı anda birden fazla görüntü akışı kullanmayla ilgili kurallar ele alınmaktadır. Önemli bir eklemeyle, aynı kurallar birden fazla kamera için de geçerlidir. 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 her YUV veya RAW türü akış, aynı tür ve boyutta iki akışla değiştirilebilir. Tek kameralı cihazlar için aşağıdaki garantili yapılandırmaya sahip bir kamera görüntü akışıyla başlayabilirsiniz:

  • Yayın 1: YUV türü, mantıksal kameradan id = 0 MAXIMUM boyutu

Ardından, çoklu kamera desteği sunan bir cihaz, mantıksal YUV akışını iki fiziksel akışla değiştirerek bir oturum oluşturmanıza olanak tanır:

  • 1. akış: YUV türü, fiziksel kamera id = 1 için MAXIMUM boyutu
  • Yayın 2: YUV türü, fiziksel kameradan MAXIMUM boyutu id = 2

YUV veya RAW akışını iki eşdeğer akışla değiştirebilmeniz için bu iki kameranın CameraCharacteristics.getPhysicalCameraIds() altında listelenen mantıksal bir kamera grubunun parçası olması gerekir.

Çerçevenin sağladığı garantiler, aynı anda birden fazla fiziksel kameradan kare almak için gereken minimum düzeydedir. Çoğu cihazda ek akışlar desteklenir. Hatta bazen birden fazla fiziksel kamera cihazının bağımsız olarak açılmasına izin verilir. Bu, çerçeve tarafından kesin olarak garanti edilmediğinden, deneme yanılma yoluyla cihaz bazında test ve ayarlama yapılması gerekir.

Birden fazla fiziksel kamera içeren oturum oluşturma

Çoklu kamera özellikli bir cihazda fiziksel kameraları kullanırken tek bir CameraDevice (mantıksal kamera) açın ve tek bir oturumda bu kamerayla etkileşim kurun. API düzey 28'de eklenen CameraDevice.createCaptureSession(SessionConfiguration config) API'sini kullanarak tek oturumu oluşturun. Oturum yapılandırmasında bir dizi çıkış yapılandırması bulunur. Bunların her birinde bir dizi çıkış hedefi ve isteğe bağlı olarak seçilen bir fiziksel kamera kimliği vardır.

SessionConfiguration ve OutputConfiguration modeli
Şekil 3. SessionConfiguration ve OutputConfiguration modeli

Yakalama isteklerinin kendileriyle ilişkili bir çıkış hedefi vardır. Çerçeve, hangi çıkış hedefinin bağlı olduğuna göre isteklerin hangi fiziksel (veya mantıksal) kameraya gönderileceğini belirler. Çıkış 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ır ve işler.

İki fiziksel kamera kullanma

Çoklu kamera için kamera API'lerine yapılan bir diğer ekleme, mantıksal kameraları tanımlama ve arkalarındaki fiziksel kameraları bulma özelliğidir. Mantıksal kamera görüntü akışlarından birini değiştirmek için kullanabileceğiniz potansiyel fiziksel kamera çiftlerini belirlemenize 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<DualCamera> {
    val dualCameras = MutableList<DualCamera>()

    // 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<DualCamera> findDualCameras(CameraManager manager, Integer facing) {
    List<DualCamera> dualCameras = new ArrayList<>();

    List<String> 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 durum yönetimi, mantıksal kamera tarafından kontrol edilir. "Ç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);
            }
        });
    }

Açılacak kamerayı seçmenin dışında, süreç önceki Android sürümlerinde kamera açma işlemiyle aynıdır. Yeni oturum yapılandırma API'sini kullanarak bir yakalama oturumu oluşturmak, çerçeveye belirli hedefleri belirli fiziksel kamera kimlikleriyle ilişkilendirmesini söyler:

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<Surface>?, MutableList<Surface>?, MutableList<Surface>?>

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 using the callback
        it.createCaptureSession(sessionConfiguration)
    }
}

Java

/**
 * Helper class definition that encapsulates 3 sets of output targets:
 * <p>
 * 1. Logical camera
 * 2. First physical camera
 * 3. Second physical camera
 */
final class DualCameraOutputs {
    private final List<Surface> logicalCamera;
    private final List<Surface> firstPhysicalCamera;
    private final List<Surface> secondPhysicalCamera;

    public DualCameraOutputs(List<Surface> logicalCamera, List<Surface> firstPhysicalCamera, List<Surface> third) {
        this.logicalCamera = logicalCamera;
        this.firstPhysicalCamera = firstPhysicalCamera;
        this.secondPhysicalCamera = third;
    }

    public List<Surface> getLogicalCamera() {
        return logicalCamera;
    }

    public List<Surface> getFirstPhysicalCamera() {
        return firstPhysicalCamera;
    }

    public List<Surface> 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<OutputConfiguration> outputConfigsLogical = targets.getLogicalCamera().stream()
            .map(OutputConfiguration::new)
            .collect(Collectors.toList());
    List<OutputConfiguration> outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream()
            .map(s -> {
                OutputConfiguration outputConfiguration = new OutputConfiguration(s);
                outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1);
                return outputConfiguration;
            })
            .collect(Collectors.toList());
    List<OutputConfiguration> 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<OutputConfiguration> 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 using the callback
            c.createCaptureSession(sessionConfiguration));
}

Hangi akış kombinasyonunun desteklendiği hakkında bilgi için createCaptureSession bölümüne bakın. Akışları birleştirme, tek bir mantıksal kameradaki birden fazla akış için kullanılır. Uyumluluk, aynı yapılandırmanın kullanılmasını ve bu akışlardan birinin aynı mantıksal kameranın parçası olan iki fiziksel kameradan gelen iki akışla değiştirilmesini kapsar.

Kamera oturumu hazır olduğunda seçilen yakalama isteklerini gönderin. Yakalama isteğinin her hedefi, verilerini ilişkili fiziksel kamerasından (kullanımda olan varsa) alır veya mantıksal kameraya geri döner.

Zoom örnek kullanım alanı

Fiziksel kameraları tek bir akışta birleştirerek kullanıcıların farklı fiziksel kameralar arasında geçiş yapmasını ve farklı bir görüş alanı deneyimlemesini sağlayabilirsiniz. Böylece farklı bir "yakınlaştırma seviyesi" etkili bir şekilde yakalanabilir.

Yakınlaştırma seviyesi kullanım alanında kamera değiştirme örneği
Şekil 4. Yakınlaştırma seviyesi kullanım alanında kamera değiştirme örneği (Pixel 3 reklamından)

Kullanıcıların aralarında geçiş yapmasına izin vermek istediğiniz fiziksel kamera çiftini seçerek başlayın. Maksimum etki için, mevcut 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 olmak üzere iki SurfaceViews kullanmaktır. Bu SurfaceViews, kullanıcı etkileşimine göre değiştirilir. Böylece, herhangi bir zamanda yalnızca biri görünür olur.

Aşağıdaki kodda mantıksal kameranın nasıl açılacağı, kamera çıkışlarının nasıl yapılandırılacağı, kamera oturumunun nasıl oluşturulacağı ve iki önizleme akışının nasıl başlatılacağı gösterilmektedir:

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

Geriye yalnızca kullanıcının iki yüzey arasında geçiş yapabileceği bir kullanıcı arayüzü (ör. düğme veya SurfaceView simgesine iki kez dokunma) sağlamak kalır. Hatta bir tür sahne analizi yapabilir ve iki akış arasında otomatik olarak geçiş yapabilirsiniz.

Lens bozulması

Tüm lensler belirli bir miktar bozulmaya neden olur. Android'de, artık kullanımdan kaldırılan CameraCharacteristics.LENS_RADIAL_DISTORTION yerine CameraCharacteristics.LENS_DISTORTION kullanarak lenslerin oluşturduğu bozulmayı sorgulayabilirsiniz. Mantıksal kameralarda bozulma minimum düzeydedir ve uygulamanız, kareleri kameradan geldiği gibi kullanabilir. Fiziksel kameralarda, özellikle geniş lenslerde çok farklı lens yapılandırmaları olabilir.

Bazı cihazlar, CaptureRequest.DISTORTION_CORRECTION_MODE kullanarak otomatik distorsiyon düzeltme uygulayabilir. Distorsiyon düzeltme, çoğu cihazda varsayılan olarak etkindir.

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 yakalama isteği ayarlamak, kameranın üretebileceği kare hızını etkileyebilir. Distorsiyon düzeltmeyi yalnızca hareketsiz resim yakalama için ayarlayabilirsiniz.