Çoklu kamera API'sı

Not: Bu sayfa Kamera2 paketiyle ilgilidir. Uygulamanız için Kamera2'nin belirli, alt düzey özellikleri gerekmiyorsa, 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, Android 9 (API düzeyi 28) sürümünde kullanıma sunulmuştur. Yayınlandığından beri cihaz piyasasına API'yi destekleyen ek özellikler getirmiştir. Çok kameralı birçok kullanım alanı belirli bir donanım yapılandırmasıyla sıkı sıkıya bağlantılı. Başka bir deyişle Tüm kullanım alanları her cihazla uyumludur,  bu da birden fazla kameranın Oyun Özelliği için iyi bir adaya sahip. Yayınlama.

Bazı tipik kullanım alanları şunlardır:

  • Yakınlaştırma: Kırpma bölgesine veya istenen odak noktasına bağlı olarak kameralar arasında geçiş yapma seçeceğiz.
  • Derinlik: Derinlik haritası oluşturmak için birden fazla kamera kullanma.
  • Bokeh: DSLR benzeri dar bir görünümü simüle etmek için tahmin edilen derinlik bilgilerinin kullanılması odak aralığı.

Mantıksal ve fiziksel kameralar arasındaki fark

Çoklu kamera API'sini anlamak, mantıksal ve fiziksel kameralar. Referans olarak, üç özellik olan bir cihaz düşünün: kameralar. Bu örnekte, arkadaki üç kameranın her biri fiziksel bir kamera olarak değerlendirilir. Mantıksal kamera, iki veya daha fazla kameradan oluşan bir gruptur çok daha kolay oluyor. Mantıksal kamera, alttaki fiziksel kameraların birinden gelen bir akış olabilir. temeldeki birden fazla fiziksel kameradan gelen karma akış olanak tanır. Her iki durumda da akış kamera Donanımı tarafından işlenir. Soyutlama Katmanı (HAL).

Birçok telefon üreticisi, genellikle birinci taraf kamera uygulamaları yüklü olarak gelir. Donanımın tüm özelliklerini kullanmak için gizli API'ler kullanabilir veya gizli API'lerden özel muamele görebilirler. diğer uygulamaların erişemediği sürücü uygulamasıdır. Biraz mantıksal kameralar kavramını uygulamaya koyan cihazlar, farklı fiziksel kameralardan kareler alır, ancak yalnızca belirli ayrıcalıklı kameralara izin verir. Çoğu zaman fiziksel kameralardan yalnızca biri görüntüye maruz kalır. bahsedeceğim. Android 9'dan önceki üçüncü taraf geliştiriciler için durum: aşağıdaki şemada gösterilmektedir:

Şekil 1. Kamera özellikleri genellikle yalnızca gizli uygulamalar

Android 9'dan itibaren Android uygulamalarında gizli API'lere artık izin verilmemektedir. Çerçeveye birden fazla kamera desteğinin dahil edilmesiyle birlikte, Android en iyi telefon üreticilerinin mantığa dayalı bir kamera ya da kameralar oluşturur. Bu dönüşüm üçüncü taraf geliştiriciler, Android 9 ve sonraki sürümleri çalıştıran cihazlarda daha yüksek:

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

Mantıksal kameranın sağladıkları, tamamen OEM uygulamasına bağlıdır bir görüntü kalitesidir. Örneğin, Pixel 3 gibi bir cihaz mantıksal konumdaki fiziksel kameralardan birini seçecek şekilde istenen odak uzaklığı ve kırpma bölgesi.

Ç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'sini kullanmak, geliştiricilerin belirli beklentilerini de beraberinde getiriyor. Cihazlar Android 9'dan önce de çift kamera bulunuyordu ancak birden fazla kamera açılıyor hem de deneme yanılma yöntemini kullanıyordu. Android 9 ve sonraki sürümlerde çok kameralı bir çift fiziksel aktivitenin açılabileceğini belirten bir dizi Bunlar aynı mantıksal kameranın parçası olan kameralar.

Çoğu durumda, Android 9 ve sonraki sürümleri çalıştıran cihazlar tüm fiziksel kameralar (kızılötesi gibi daha seyrek görülen sensör türleri hariç) kullanımı kolay, mantıklı bir kamera. En iyi performans gösteren akış kombinasyonları için çalışacağı garanti edildiğinde, mantıksal bir kameraya ait bir akış, temel fiziksel kameralardan gelen iki akış bulunur.

Aynı anda birden fazla yayın

Birden fazla kamera akışını aynı anda kullanma , tek bir kamerada aynı anda birden çok yayın kullanmaya ilişkin kuralları kapsar. Önemli bir eklemeyle birlikte aynı kurallar birden fazla kamera için geçerli olur. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA Mantıksal bir YUV_420_888 veya ham akışın iki gerçekleşebilir. Yani, YUV veya RAW türündeki her akış aynı tür ve büyüklükte iki akıştan oluşur. Şu videoya ait kamera yayınıyla başlayabilirsiniz: Tek kameralı cihazlar için aşağıdaki garantili yapılandırma:

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

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

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

Bir YUV veya RAW akışını yalnızca aşağıdaki durumlarda iki eşdeğer akışla değiştirebilirsiniz: bu iki kamera mantıksal bir kamera gruplandırmasının parçasıdır. Bu gruplandırma CameraCharacteristics.getPhysicalCameraIds().

Çerçevenin sağladığı garantiler, sözleşmenin uygulanması için gereken aynı anda birden fazla fiziksel kameradan kare alabilir. Ek akışlar çoğu cihazda desteklenir. Hatta bazen, birden fazla fiziksel ayrı ayrı çalışır. Daha kesin bir ifadeyle, bunu yaparken cihaz başına test ve ince ayar yapılmasını gerektirir. deneme yanılma yöntemidir.

Birden çok fiziksel kamerayla oturum oluşturma

Çok kameralı bir cihazda fiziksel kameralar kullanırken tek bir CameraDevice (mantıksal kamera) ve tek bir kamerada bu kamerayla etkileşimde bulunun kabul edilir. API'yi kullanarak tek oturum oluşturma Önceki fiyatı: CameraDevice.createCaptureSession(SessionConfiguration config) API düzeyi 28'de eklenmiştir. Oturum yapılandırmasında bir dizi çıkış vardır. Her biri bir dizi çıkış hedefine sahip yapılandırma ve isteğe bağlı olarak fiziksel kamera kimliğini girin.

Şekil 3. SessionConfiguration ve ExitConfiguration modeli

Yakalama isteklerinin ilişkilendirilmiş bir çıkış hedefi vardır. Çerçeve isteklerin hangi fiziksel (veya mantıksal) kameraya gönderileceğini belirler hedefin ekli olduğunu görebilirsiniz. Çıkış hedefi fiziksel yapılandırmayla birlikte çıkış yapılandırması olarak gönderilen çıkış hedefleri fiziksel kameranın isteği alıp işlediğini gösterir.

Bir çift fiziksel kamera kullanma

Çoklu kamera için kamera API'lerine ek bir özellik de mantıksal kameralar ve bunların arkasındaki fiziksel kameraları bulur. Hedeflerinize göre bir kullanabileceğiniz potansiyel fiziksel kamera çiftlerini tanımlamaya yardımcı olan bir işlev mantıksal kamera akışlarından birini değiştirin:

KotlinJava
/**
     * 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
/**
     * 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

Fiziksel kameraların durumu, mantıksal kamera tarafından kontrol edilir. Alıcı: "çift kamera"yı açın, fiziksel makineye karşılık gelen mantıksal kamerayı kameralar:

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

Hangi kameranın açılacağını seçmenin dışındaki işlemler, kamera kullanıyor olması. Yeni oturum yapılandırma API'sı, çerçeveye belirli hedeflerin Belirli fiziksel kamera kimlikleri:

KotlinJava
/**
 * Helper type definition that encapsulates 3 sets of output targets:
 *
 *   1. Logical camera
 *   2. First physical camera
 *   3. Second physical camera
 */

typealias DualCameraOutputs =
       
Triple
/**
 * 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

Görüntüleyin createCaptureSession başlıklı makaleye göz atın. Akışları birleştirme tek bir mantıksal kamerada birden fazla yayın yapmak için kullanılır. Uyumluluk aynı yapılandırmayı kullanarak ve bu akışlardan birini iki akışla değiştirerek iki fiziksel kameradan çekilmiş görüntülerdir.

Şununla kamera oturumu hazır, istenen öğeyi yakalama istekleri. Her biri yakalama isteğinin hedefi, verilerini ilişkili fiziksel cihazdan alır kameranın yerini değiştirebilir veya mantıksal kameraya geri dönebilirsiniz.

Zoom örneği kullanım alanı

Fiziksel kameraların birleştirilmesiyle tek bir akış kullanılabilir. Farklı fiziksel kameralar arasında geçiş yaparak etkili bir şekilde farklı bir "yakınlaştırma seviyesi" yakalayan farklı bir görüş alanı sağlar.

Şekil 4. Yakınlaştırma düzeyi kullanım alanına göre kamera değiştirme örneği (Pixel 3 reklamından)

Kullanıcıların geçiş yapmasına izin vermek için fiziksel kamera çiftini seçerek başlayın arasında yer alır. En yüksek efekt için, her bir resmi ayrı ayrı oynatan mevcut minimum ve maksimum odak uzaklığı.

KotlinJava
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
}
// 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, iki SurfaceViews: Her akış için bir adet. Bu SurfaceViews, kullanıcı etkileşimine göre değiştirilir. Böylece yalnızca biri görünür olmalıdır.

Aşağıdaki kod, mantıksal kameranın nasıl açılacağını, kameranın nasıl yapılandırılacağını gösterir kamera oturumu oluşturun ve iki önizleme akışı başlatın:

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

Kalan tek şey, kullanıcının bu ikisi arasında geçiş yapması için bir kullanıcı arayüzü sağlamaktır. (ör. düğme veya SurfaceView simgesine iki kez dokunma) Hatta, Bir tür sahne analizi yapma ve iki akış arasında geçiş yapma otomatik olarak oluşturur.

Lens bozulması

Tüm lensler belirli bir miktarda distorsiyon oluşturur. Android'de, kullanarak lenslerin oluşturduğu distorsiyon CameraCharacteristics.LENS_DISTORTION Bu segment, desteği sonlandırılan CameraCharacteristics.LENS_RADIAL_DISTORTION. Mantıksal kameralarda bozulma minimum düzeydedir ve uygulamanız, kameradan gelen kare sayısını artırır. Fiziksel kameralarda özellikle geniş açılı çekimde çok farklı lens konfigürasyonları lensler.

Bazı cihazlar otomatik distorsiyon düzeltmeyi CaptureRequest.DISTORTION_CORRECTION_MODE. Distorsiyon düzeltme özelliği çoğu cihazda varsayılan olarak açıktır.

KotlinJava
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(), ...)
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, üretiliyor. Distorsiyon düzeltmesini yalnızca şuna ayarlamayı seçebilirsiniz: hareketsiz görüntü alınır.