Çoklu kamera API'sı

Not: Bu sayfa, Camera2 paketiyle ilgilidir. Uygulamanız Camera2'den belirli, alt düzey özellikler 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 gösterilir. 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. Android 9'dan önce çift kameralı cihazlar 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:

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

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

  • Yayın 1: YUV türü, fiziksel kamera id = 1 için MAXIMUM boyutu
  • Akış 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 yöntemiyle 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 API CameraDevice.createCaptureSession(SessionConfiguration config) 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 istekleriyle 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:

/**
 * 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 = mutableListOf<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
        ) ?: false
    }.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
}

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:

fun openDualCamera(
    cameraManager: CameraManager,
    dualCamera: DualCamera,
    executor: 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()
        }
    )
}

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:

/**
 * 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,
    executor: 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)
    }
}

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 yakalanır.

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.

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() ?: 0f) - (focalLengths1.minOrNull() ?: 0f)
        val focalLengthsDiff2 = (focalLengths1.maxOrNull() ?: 0f) - (focalLengths2.minOrNull() ?: 0f)

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

Bunun için her akışa bir tane olmak üzere iki SurfaceViews içeren bir mimari mantıklı olacaktı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:

val cameraManager: CameraManager = ...
val cameraExecutor: Executor = ...  // owned by your activity / fragment

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

val dualCamera = findShortLongCameraPair(cameraManager) ?: return
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2)
)

// Here you open the logical camera, configure the outputs and create a session.
createDualCameraSession(
    cameraManager, dualCamera, targets = outputTargets, executor = cameraExecutor
) { 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)
}

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 yapıp 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.

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

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.