Sesi dan permintaan pengambilan gambar kamera

Catatan: Halaman ini merujuk ke paket Camera2. Kecuali aplikasi Anda memerlukan fitur tingkat rendah khusus dari Camera2, sebaiknya gunakan CameraX. CameraX dan Camera2 mendukung Android 5.0 (level API 21) dan versi yang lebih baru.

Satu perangkat yang didukung Android dapat memiliki beberapa kamera. Setiap kamera adalah CameraDevice, dan CameraDevice dapat menghasilkan lebih dari satu aliran data secara bersamaan.

Salah satu alasan untuk melakukannya adalah agar satu streaming, frame kamera berurutan yang berasal dari CameraDevice, dioptimalkan untuk tugas tertentu, seperti menampilkan jendela bidik, sementara yang lain mungkin digunakan untuk mengambil foto atau membuat rekaman video.Stream berfungsi sebagai pipeline paralel yang memproses frame mentah yang keluar dari kamera,satu frame dalam satu waktu:

Gambar 1. Ilustrasi dari Membuat Aplikasi Kamera Universal (Google I/O ‘18)

Pemrosesan paralel menunjukkan bahwa mungkin ada batas performa yang bergantung pada daya pemrosesan yang tersedia dari CPU, GPU, atau prosesor lainnya. Jika pipeline tidak dapat mengikuti frame yang masuk, pipeline akan mulai menjatuhkannya.

Setiap pipeline memiliki format output-nya sendiri. Data mentah yang masuk secara otomatis diubah menjadi format output yang sesuai dengan logika implisit yang terkait dengan setiap pipeline. CameraDevice yang digunakan di seluruh contoh kode halaman ini tidak spesifik, jadi Anda harus mengenumerasi semua kamera yang tersedia terlebih dahulu sebelum melanjutkan.

Anda dapat menggunakan CameraDevice untuk membuat CameraCaptureSession, yang khusus untuk CameraDevice tersebut. CameraDevice harus menerima konfigurasi frame untuk setiap frame mentah menggunakan CameraCaptureSession. Konfigurasi menentukan atribut kamera seperti fokus otomatis, bukaan, efek, dan eksposur. Karena keterbatasan hardware, hanya satu konfigurasi yang aktif di sensor kamera pada waktu tertentu, yang disebut konfigurasi aktif.

Namun, Kasus Penggunaan Streaming meningkatkan dan memperluas cara sebelumnya menggunakan CameraDevice untuk melakukan streaming sesi pengambilan gambar, yang memungkinkan Anda mengoptimalkan streaming kamera untuk kasus penggunaan tertentu. Misalnya, hal ini dapat meningkatkan masa pakai baterai saat mengoptimalkan panggilan video.

CameraCaptureSession menjelaskan semua kemungkinan pipeline yang terikat ke CameraDevice. Saat sesi dibuat, Anda tidak dapat menambahkan atau menghapus pipeline. CameraCaptureSession mempertahankan antrean CaptureRequest, yang menjadi konfigurasi aktif.

CaptureRequest menambahkan konfigurasi ke antrean dan memilih satu, lebih dari satu, atau semua pipeline yang tersedia untuk menerima frame dari CameraDevice. Anda dapat mengirim banyak permintaan pengambilan selama sesi pengambilan gambar aktif. Setiap permintaan dapat mengubah konfigurasi aktif dan kumpulan pipeline output yang menerima gambar mentah.

Gunakan Kasus Penggunaan Streaming untuk performa yang lebih baik

Kasus Penggunaan Streaming adalah cara untuk meningkatkan performa sesi pemotretan Camera2. Class ini memberi perangkat hardware lebih banyak informasi untuk menyesuaikan parameter, sehingga memberikan pengalaman kamera yang lebih baik untuk tugas spesifik Anda.

Hal ini memungkinkan perangkat kamera mengoptimalkan pipeline hardware dan software kamera berdasarkan skenario pengguna untuk setiap streaming. Untuk mengetahui informasi selengkapnya tentang Kasus Penggunaan Streaming, lihat setStreamUseCase.

Kasus Penggunaan Streaming memungkinkan Anda menentukan cara streaming kamera tertentu digunakan secara lebih detail, selain menetapkan template dalam CameraDevice.createCaptureRequest(). Hal ini memungkinkan hardware kamera mengoptimalkan parameter, seperti tuning, mode sensor, atau setelan sensor kamera, berdasarkan kompromi kualitas atau latensi yang cocok untuk kasus penggunaan tertentu.

Kasus Penggunaan Streaming mencakup:

  • DEFAULT: Mencakup semua perilaku aplikasi yang ada. Ini sama dengan tidak menetapkan Kasus Penggunaan Streaming.

  • PREVIEW: Direkomendasikan untuk Jendela bidik atau analisis gambar dalam aplikasi.

  • STILL_CAPTURE: Dioptimalkan untuk pengambilan gambar beresolusi tinggi berkualitas tinggi, dan tidak diharapkan untuk mempertahankan kecepatan frame seperti pratinjau.

  • VIDEO_RECORD: Dioptimalkan untuk perekaman video berkualitas tinggi, termasuk stabilisasi gambar berkualitas tinggi, jika didukung oleh perangkat dan diaktifkan oleh aplikasi. Opsi ini mungkin menghasilkan frame output dengan jeda yang cukup lama dari real time, untuk memungkinkan stabilisasi berkualitas tertinggi atau pemrosesan lainnya.

  • VIDEO_CALL: Direkomendasikan untuk penggunaan kamera yang berjalan lama di mana konsumsi daya menjadi masalah.

  • PREVIEW_VIDEO_STILL: Direkomendasikan untuk aplikasi media sosial atau kasus penggunaan satu streaming. Ini adalah streaming serbaguna.

  • VENDOR_START: Digunakan untuk kasus penggunaan yang ditentukan OEM.

Membuat CameraCaptureSession

Untuk membuat sesi kamera, sediakan satu atau beberapa buffer output yang dapat digunakan aplikasi Anda untuk menulis frame output. Setiap buffer mewakili pipeline. Anda harus melakukannya sebelum mulai menggunakan kamera agar framework dapat mengonfigurasi pipeline internal perangkat dan mengalokasikan buffering memori untuk mengirim frame ke target output yang diperlukan.

Cuplikan kode berikut menunjukkan cara menyiapkan sesi kamera dengan dua buffer output, satu milik SurfaceView dan satu lagi milik ImageReader. Menambahkan PREVIEW Kasus Penggunaan Streaming ke previewSurface dan STILL_CAPTURE Kasus Penggunaan Streaming ke imReaderSurface memungkinkan hardware perangkat mengoptimalkan streaming ini lebih jauh lagi.

Kotlin

// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List){
    val configs = mutableListOf()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java

// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List targets){
    ArrayList configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

Pada tahap ini, Anda belum menentukan konfigurasi aktif kamera. Saat sesi dikonfigurasi, Anda dapat membuat dan mengirim permintaan pengambilan untuk melakukannya.

Transformasi yang diterapkan ke input saat ditulis ke buffer-nya ditentukan oleh jenis setiap target, yang harus berupa Surface. Framework Android mengetahui cara mengonversi gambar mentah dalam konfigurasi aktif menjadi format yang sesuai untuk setiap target. Konversi dikontrol oleh format piksel dan ukuran Surface tertentu.

Framework ini akan berusaha melakukan yang terbaik, tetapi beberapa kombinasi konfigurasi Surface mungkin tidak berfungsi, sehingga menyebabkan masalah seperti sesi tidak dibuat, sehingga memunculkan error runtime saat Anda mengirim permintaan, atau penurunan performa. Framework ini memberikan jaminan untuk kombinasi parameter perangkat, platform, dan permintaan tertentu. Dokumentasi untuk createCaptureSession() memberikan informasi selengkapnya.

CaptureRequest Tunggal

Konfigurasi yang digunakan untuk setiap frame dienkode dalam CaptureRequest, yang dikirim ke kamera. Untuk membuat permintaan pengambilan gambar, Anda dapat menggunakan salah satu template yang telah ditetapkan, atau Anda dapat menggunakan TEMPLATE_MANUAL untuk kontrol penuh. Saat memilih template, Anda harus menyediakan satu atau beberapa buffer output untuk digunakan dengan permintaan. Anda hanya dapat menggunakan buffer yang sudah ditentukan pada sesi pengambilan yang ingin digunakan.

Permintaan pengambilan menggunakan pola builder dan memberi developer kesempatan untuk menetapkan banyak opsi yang berbeda termasuk eksposur otomatis, fokus otomatis, dan bukaan lensa. Sebelum menetapkan kolom, pastikan opsi spesifik tersedia untuk perangkat dengan memanggil CameraCharacteristics.getAvailableCaptureRequestKeys() dan nilai yang diinginkan didukung dengan memeriksa karakteristik kamera yang sesuai, seperti mode eksposur otomatis yang tersedia.

Guna membuat permintaan pengambilan untuk SurfaceView menggunakan template yang dirancang untuk pratinjau tanpa modifikasi apa pun, gunakan CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

Setelah menentukan permintaan pengambilan gambar, kini Anda dapat mengirimkannya ke sesi kamera:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

Saat frame output dimasukkan ke dalam buffering tertentu, callback capture akan dipicu. Dalam banyak kasus, callback tambahan, seperti ImageReader.OnImageAvailableListener, akan dipicu saat frame yang dimuatnya diproses. Pada tahap ini, Anda dapat mengambil data gambar dari buffer yang ditentukan.

Mengulangi CaptureRequest

Permintaan satu kamera mudah dilakukan, tetapi untuk menampilkan pratinjau langsung, atau video, permintaan ini tidak terlalu berguna. Dalam hal ini, Anda harus menerima aliran frame yang berkelanjutan, bukan hanya satu. Cuplikan kode berikut menunjukkan cara menambahkan permintaan berulang ke sesi:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

Permintaan pengambilan berulang membuat perangkat kamera terus mengambil gambar menggunakan setelan di CaptureRequest yang disediakan. Camera2 API juga memungkinkan pengguna merekam video dari kamera dengan mengirimkan CaptureRequests berulang seperti yang terlihat dalam repositori contoh Camera2 ini di GitHub. Aplikasi ini juga dapat merender video gerak lambat dengan merekam video berkecepatan tinggi (gerakan lambat) menggunakan CaptureRequests burst berulang seperti yang ditampilkan di aplikasi contoh video gerak lambat Camera2 di GitHub.

Interleave CaptureRequests

Untuk mengirim permintaan pengambilan kedua saat permintaan pengambilan berulang sedang aktif, seperti untuk menampilkan jendela bidik dan mengizinkan pengguna mengambil foto, Anda tidak perlu menghentikan permintaan berulang yang sedang berlangsung. Sebagai gantinya, Anda mengeluarkan permintaan pengambilan yang tidak berulang saat permintaan berulang terus berjalan.

Setiap buffering output yang digunakan perlu dikonfigurasi sebagai bagian dari sesi kamera saat sesi pertama kali dibuat. Permintaan berulang memiliki prioritas lebih rendah daripada permintaan burst atau frame tunggal, yang memungkinkan contoh berikut berfungsi:

Kotlin

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

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

Namun, ada kelemahan dari pendekatan ini: Anda tidak tahu persis kapan satu permintaan terjadi. Pada gambar berikut, jika A adalah permintaan pengambilan berulang dan B adalah permintaan pengambilan frame tunggal, beginilah cara sesi memproses antrean permintaan:

Gambar 2. Ilustrasi antrean permintaan untuk sesi kamera yang sedang berlangsung

Tidak ada jaminan latensi antara permintaan berulang terakhir dari A sebelum permintaan B diaktifkan dan saat A digunakan lagi, sehingga Anda mungkin mengalami beberapa frame yang dilewati. Ada beberapa hal yang dapat Anda lakukan untuk memitigasi masalah ini:

  • Tambahkan target output dari permintaan A untuk meminta B. Dengan begitu, jika frame B sudah siap, frame tersebut akan disalin ke target output A. Misalnya, hal ini penting saat melakukan snapshot video untuk mempertahankan kecepatan frame yang stabil. Dalam kode sebelumnya, Anda menambahkan singleRequest.addTarget(previewSurface) sebelum membuat permintaan.

  • Gunakan kombinasi template yang didesain agar berfungsi untuk skenario khusus ini, seperti zero-shutter-lag.