Birden fazla kamera akışını aynı anda kullanma

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.

Bir kamera uygulaması aynı anda birden fazla kare akışı kullanabilir. İçinde Hatta bazı durumlarda farklı akışlar için farklı bir kare çözünürlüğü veya biçimindedir. Bazı tipik kullanım alanları şunlardır:

  • Video kaydı: Önizleme için bir akış, diğeri kodlanıp kaydedilir bir dosyaya koyabilirsiniz.
  • Barkod tarama: Önizleme için bir akış, barkod algılama için başka bir akış.
  • Bilişimsel fotoğrafçılık: Önizleme için bir akış, yüz/sahne için başka bir akış tespit edebilir.

Çerçeveleri işlerken önemsiz olmayan bir performans maliyeti vardır ve bunun maliyeti artar.

CPU, GPU ve TTP gibi kaynaklar çerçevenin yeniden işlemesi ancak bellek gibi kaynaklar doğrusal olarak büyüyecektir.

İstek başına birden fazla hedef

Birden fazla kamera akışı tek bir cihazda birleştirilebilir CameraCaptureRequest. Aşağıdaki kod snippet'i, tek bir hesapla kamera oturumu kamera önizlemesi için akış ve resim işleme için başka bir akış:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// You will use the preview capture template for the combined streams
// because it is optimized for low latency; for high-quality images, use
// TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD
val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
val combinedRequest = session.device.createCaptureRequest(requestTemplate)

// Link the Surface targets with the combined request
combinedRequest.addTarget(previewSurface)
combinedRequest.addTarget(imReaderSurface)

// In this simple case, the SurfaceView gets updated automatically. ImageReader
// has its own callback that you have to listen to in order to retrieve the
// frames so there is no need to set up a callback for the capture request
session.setRepeatingRequest(combinedRequest.build(), null, null)

Java

CameraCaptureSession session = …;  // from CameraCaptureSession.StateCallback

// You will use the preview capture template for the combined streams
// because it is optimized for low latency; for high-quality images, use
// TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD
        CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

// Link the Surface targets with the combined request
        combinedRequest.addTarget(previewSurface);
        combinedRequest.addTarget(imReaderSurface);

// In this simple case, the SurfaceView gets updated automatically. ImageReader
// has its own callback that you have to listen to in order to retrieve the
// frames so there is no need to set up a callback for the capture request
        session.setRepeatingRequest(combinedRequest.build(), null, null);

Hedef yüzeyleri doğru şekilde yapılandırırsanız bu kod yalnızca tarafından belirlenen minimum FPS'yi karşılayan akışlar StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) ve StreamComfigurationMap.GetOutputStallDuration(int, Size). Gerçek performans cihazdan cihaza değişebilir, ancak Android bazı Üç değişkene bağlı olarak belirli kombinasyonları destekleme garantileri: çıktı türünü, çıktı boyutunu ve donanım düzeyini kontrol edin.

Desteklenmeyen değişken kombinasyonu kullanmak düşük kare hızında işe yarayabilir. eğer çalışmazsa başarısız geri çağırmalardan birini tetikler. createCaptureSession ile ilgili dokümanlar neyin garanti edildiğini açıklar.

Çıkış türü

Çıkış türü, karelerin kodlandığı biçimi belirtir. İlgili içeriği oluşturmak için kullanılan olası değerler PRIV, YUV, JPEG ve RAW'dur. Dokümanlar createCaptureSession tanımladığını gösterir.

Uygulamanızın çıkış türünü seçerken amacınız kullanıyorsanız ImageFormat.YUV_420_888 çerçeve analizi ve Hareketsiz için ImageFormat.JPEG resim. Önizleme ve kayıt senaryoları için muhtemelen bir SurfaceView TextureView, MediaRecorder MediaCodec veya RenderScript.Allocation. İçinde resim biçimi belirtmeyin. Uyumluluk açısından, ImageFormat.PRIVATE dahili olarak kullanılan gerçek biçimden bağımsız olarak. Desteklenen biçimleri sorgulamak için cihaz türüne göre CameraCharacteristics şu kodu kullanın:

Kotlin

val characteristics: CameraCharacteristics = ...
val supportedFormats = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats

Java

CameraCharacteristics characteristics = …;
        int[] supportedFormats = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();

Çıkış boyutu

Kullanılabilen tüm çıktı boyutları, aşağıdaki ölçütlere göre listelenmiştir: StreamConfigurationMap.getOutputSizes(), Ancak bunlardan yalnızca ikisi uyumlulukla ilgilidir: PREVIEW ve MAXIMUM. Boyutlar üst sınırlar işlevi görür. PREVIEW boyutundaki bir cihaz çalışıyorsa PREVIEW altındaki boyutlar da işe yarar. Aynı durum MAXIMUM için de geçerlidir. İlgili içeriği oluşturmak için kullanılan belgeleri CameraDevice açıklayacağım.

Kullanılabilir çıktı boyutları, biçim seçimine bağlıdır. Raporda CameraCharacteristics ve bir biçim kullanıyorsanız, kullanılabilir çıkış boyutlarını şu şekilde sorgulayabilirsiniz:

Kotlin

val characteristics: CameraCharacteristics = ...
val outputFormat: Int = ...  // such as ImageFormat.JPEG
val sizes = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
    .getOutputSizes(outputFormat)

Java

CameraCharacteristics characteristics = …;
        int outputFormat = …;  // such as ImageFormat.JPEG
Size[] sizes = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(outputFormat);

Kamera önizlemesi ve kaydı kullanım alanlarında, hedef sınıfı kullanarak desteklenen boyutlar. Biçim, kamera çerçevesinin kendisi tarafından işlenir:

Kotlin

val characteristics: CameraCharacteristics = ...
val targetClass: Class <T> = ...  // such as SurfaceView::class.java
val sizes = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
    .getOutputSizes(targetClass)

Java

CameraCharacteristics characteristics = …;
   int outputFormat = …;  // such as ImageFormat.JPEG
   Size[] sizes = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(outputFormat);

MAXIMUM boyutunu elde etmek için çıktı boyutlarını alana göre sıralayın ve en büyük olanı döndürün bir:

Kotlin

fun <T>getMaximumOutputSize(
    characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null):
    Size {
  val config = characteristics.get(
      CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)

  // If image format is provided, use it to determine supported sizes; or else use target class
  val allSizes = if (format == null)
    config.getOutputSizes(targetClass) else config.getOutputSizes(format)
  return allSizes.maxBy { it.height * it.width }
}

Java

 @RequiresApi(api = Build.VERSION_CODES.N)
    <T> Size getMaximumOutputSize(CameraCharacteristics characteristics,
                                            Class <T> targetClass,
                                            Integer format) {
        StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        // If image format is provided, use it to determine supported sizes; else use target class
        Size[] allSizes;
        if (format == null) {
            allSizes = config.getOutputSizes(targetClass);
        } else {
            allSizes = config.getOutputSizes(format);
        }
        return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get();
    }

PREVIEW, cihazın ekran çözünürlüğüyle veya cihazın ekran çözünürlüğüyle en iyi 1080p (1920x1080) (hangisi daha küçükse). En boy oranı en boy oranına uygun hale getirmek için sinemaskop veya akışı kırparak tam ekran modunda görüntüleyebilirsiniz. Doğru önizleme boyutunu oluşturabilirsiniz. Aynı zamanda, kullanılabilir çıktı boyutlarını görüntü boyutuyla Ekranın döndürülebileceği de hesaba katılır.

Aşağıdaki kod, boyutu yapacak SmartSize yardımcı sınıfını tanımlar biraz daha kolay hale getiriyor:

Kotlin

/** Helper class used to pre-compute shortest and longest sides of a [Size] */
class SmartSize(width: Int, height: Int) {
    var size = Size(width, height)
    var long = max(size.width, size.height)
    var short = min(size.width, size.height)
    override fun toString() = "SmartSize(${long}x${short})"
}

/** Standard High Definition size for pictures and video */
val SIZE_1080P: SmartSize = SmartSize(1920, 1080)

/** Returns a [SmartSize] object for the given [Display] */
fun getDisplaySmartSize(display: Display): SmartSize {
    val outPoint = Point()
    display.getRealSize(outPoint)
    return SmartSize(outPoint.x, outPoint.y)
}

/**
 * Returns the largest available PREVIEW size. For more information, see:
 * https://d.android.com/reference/android/hardware/camera2/CameraDevice
 */
fun <T>getPreviewOutputSize(
        display: Display,
        characteristics: CameraCharacteristics,
        targetClass: Class <T>,
        format: Int? = null
): Size {

    // Find which is smaller: screen or 1080p
    val screenSize = getDisplaySmartSize(display)
    val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short
    val maxSize = if (hdScreen) SIZE_1080P else screenSize

    // If image format is provided, use it to determine supported sizes; else use target class
    val config = characteristics.get(
            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
    if (format == null)
        assert(StreamConfigurationMap.isOutputSupportedFor(targetClass))
    else
        assert(config.isOutputSupportedFor(format))
    val allSizes = if (format == null)
        config.getOutputSizes(targetClass) else config.getOutputSizes(format)

    // Get available sizes and sort them by area from largest to smallest
    val validSizes = allSizes
            .sortedWith(compareBy { it.height * it.width })
            .map { SmartSize(it.width, it.height) }.reversed()

    // Then, get the largest output size that is smaller or equal than our max size
    return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size
}

Java

/** Helper class used to pre-compute shortest and longest sides of a [Size] */
    class SmartSize {
        Size size;
        double longSize;
        double shortSize;

        public SmartSize(Integer width, Integer height) {
            size = new Size(width, height);
            longSize = max(size.getWidth(), size.getHeight());
            shortSize = min(size.getWidth(), size.getHeight());
        }

        @Override
        public String toString() {
            return String.format("SmartSize(%sx%s)", longSize, shortSize);
        }
    }

    /** Standard High Definition size for pictures and video */
    SmartSize SIZE_1080P = new SmartSize(1920, 1080);

    /** Returns a [SmartSize] object for the given [Display] */
    SmartSize getDisplaySmartSize(Display display) {
        Point outPoint = new Point();
        display.getRealSize(outPoint);
        return new SmartSize(outPoint.x, outPoint.y);
    }

    /**
     * Returns the largest available PREVIEW size. For more information, see:
     * https://d.android.com/reference/android/hardware/camera2/CameraDevice
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    <T> Size getPreviewOutputSize(
            Display display,
            CameraCharacteristics characteristics,
            Class <T> targetClass,
            Integer format
    ){

        // Find which is smaller: screen or 1080p
        SmartSize screenSize = getDisplaySmartSize(display);
        boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize;
        SmartSize maxSize;
        if (hdScreen) {
            maxSize = SIZE_1080P;
        } else {
            maxSize = screenSize;
        }

        // If image format is provided, use it to determine supported sizes; else use target class
        StreamConfigurationMap config = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (format == null)
            assert(StreamConfigurationMap.isOutputSupportedFor(targetClass));
        else
            assert(config.isOutputSupportedFor(format));
        Size[] allSizes;
        if (format == null) {
            allSizes = config.getOutputSizes(targetClass);
        } else {
            allSizes = config.getOutputSizes(format);
        }

        // Get available sizes and sort them by area from largest to smallest
        List <Size> sortedSizes = Arrays.asList(allSizes);
        List <SmartSize> validSizes =
                sortedSizes.stream()
                        .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth()))
                        .map(s -> new SmartSize(s.getWidth(), s.getHeight()))
                        .sorted(Collections.reverseOrder()).collect(Collectors.toList());

        // Then, get the largest output size that is smaller or equal than our max size
        return validSizes.stream()
                .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize)
                .findFirst().get().size;
    }

Desteklenen donanım seviyesini kontrol edin

Çalışma zamanında kullanılabilir özellikleri belirlemek için desteklenen donanımı kontrol edin düzeyi kullanın CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.

Şununla CameraCharacteristics nesnesini tanımlarsanız donanım düzeyini tek bir ifadeyle alabilirsiniz:

Kotlin

val characteristics: CameraCharacteristics = ...

// Hardware level will be one of:
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
val hardwareLevel = characteristics.get(
        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)

Java

CameraCharacteristics characteristics = ...;

// Hardware level will be one of:
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
Integer hardwareLevel = characteristics.get(
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);

Tüm parçaları bir araya getirmek

Çıkış türü, çıkış boyutu ve donanım düzeyi ile hangi ölçütlerin karşılanacağını belirlemek için geçerli olduğundan emin olun. Aşağıdaki grafikte desteklenen yapılandırmalara sahip CameraDevice LEGACY kullanabilirsiniz.

Hedef 1 Hedef 2 Hedef 3 Örnek kullanım alanları
Tür Maksimum boyut Tür Maksimum boyut Tür Maksimum boyut
PRIV MAXIMUM Basit önizleme, GPU video işleme veya önizleme olmayan video kaydı.
JPEG MAXIMUM Vizörsüz sabit görüntü yakalama.
YUV MAXIMUM Uygulama içi video/resim işleme.
PRIV PREVIEW JPEG MAXIMUM Standart görüntüleme işlemi devam ediyor.
YUV PREVIEW JPEG MAXIMUM Uygulama içi işleme artı görüntü yakalama.
PRIV PREVIEW PRIV PREVIEW Standart kayıt.
PRIV PREVIEW YUV PREVIEW Önizleme ve uygulama içi işleme.
PRIV PREVIEW YUV PREVIEW Önizleme ve uygulama içi işleme.
PRIV PREVIEW YUV PREVIEW JPEG MAXIMUM Yakalamaya devam etme ve uygulama içi işleme.

LEGACY, mümkün olan en düşük donanım düzeyidir. Bu tablo, her bir Kamera2'yi destekleyen bir cihaz (API düzeyi 21 ve üstü) en fazla üç çıkış yapabilir doğru yapılandırmayı kullanarak aynı anda birden fazla video oynatın. ek yük sınırlayıcı performans (örneğin, bellek, CPU veya termal kısıtlamalar).

Uygulamanızın, hedefleme çıktı tamponlarını da yapılandırması gerekir. Örneğin, LEGACY donanım düzeyinde bir cihazı hedeflerseniz iki hedef çıkış ayarlayabilirsiniz biri ImageFormat.PRIVATE, diğeri ise ImageFormat.YUV_420_888. Bu kombinasyon, PREVIEW boyut. Bu konunun önceki bölümlerinde tanımlanan işlevi kullanarak, kamera kimliği için gerekli önizleme boyutları için şu kod gereklidir:

Kotlin

val characteristics: CameraCharacteristics = ...
val context = this as Context  // assuming you are inside of an activity

val surfaceViewSize = getPreviewOutputSize(
    context, characteristics, SurfaceView::class.java)
val imageReaderSize = getPreviewOutputSize(
    context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)

Java

CameraCharacteristics characteristics = ...;
        Context context = this; // assuming you are inside of an activity

        Size surfaceViewSize = getPreviewOutputSize(
                context, characteristics, SurfaceView.class);
        Size imageReaderSize = getPreviewOutputSize(
                context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);

SurfaceView sağlanan geri çağırmaları kullanarak hazır olana kadar beklemeniz gerekir:

Kotlin

val surfaceView = findViewById <SurfaceView>(...)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
  override fun surfaceCreated(holder: SurfaceHolder) {
    // You do not need to specify image format, and it will be considered of type PRIV
    // Surface is now ready and you could use it as an output target for CameraSession
  }
  ...
})

Java

SurfaceView surfaceView = findViewById <SurfaceView>(...);

surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                // You do not need to specify image format, and it will be considered of type PRIV
                // Surface is now ready and you could use it as an output target for CameraSession
            }
            ...
        });

Şu ifadeyi çağırarak SurfaceView cihazını kamera çıkışı boyutuyla eşleşmeye zorlayabilirsiniz: SurfaceHolder.setFixedSize() Şuna benzer bir yaklaşım benimseyebilirsiniz: Common'tan AutoFitSurfaceView modül mutlak bir boyut belirleyen, GitHub'daki kamera örneklerinin sayısını hem en boy oranını hem de kullanılabilir alanı hesaba katar etkinlik değişikliklerinin ne zaman tetikleneceğini ayarlar.

Diğer yüzeyi İstediğiniz biçime sahip ImageReader daha kolay olur, çünkü beklenebilecek geri çağırmalar olmaz:

Kotlin

val frameBufferCount = 3  // just an example, depends on your usage of ImageReader
val imageReader = ImageReader.newInstance(
    imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888,
    frameBufferCount)

Java

int frameBufferCount = 3;  // just an example, depends on your usage of ImageReader
ImageReader imageReader = ImageReader.newInstance(
                imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888,
                frameBufferCount);

ImageReader gibi bir engelleme hedefi arabelleği kullanırken şundan sonra kareleri silin: nasıl kullanacağımızı konuştuk.

Kotlin

imageReader.setOnImageAvailableListener({
  val frame =  it.acquireNextImage()
  // Do something with "frame" here
  it.close()
}, null)

Java

imageReader.setOnImageAvailableListener(listener -> {
            Image frame = listener.acquireNextImage();
            // Do something with "frame" here
            listener.close();
        }, null);

LEGACY donanım düzeyi, en düşük ortak paydaya sahip cihazları hedefler. Şunları yapabilirsiniz: koşullu dallandırma ekleme ve çıkış hedeflerinin biri için RECORD boyutu kullanma LIMITED donanım seviyesine sahip cihazlarda yüzeyleri keşfedebilir veya Donanım seviyesinde FULL olan cihazlar için MAXIMUM boyut.