Arsitektur CameraX

Halaman ini mencakup arsitektur CameraX, termasuk strukturnya, cara menggunakan API ini, cara menggunakan siklus proses, dan cara menggabungkan kasus penggunaan.

Struktur CameraX

Anda dapat menggunakan CameraX untuk berinteraksi dengan kamera perangkat melalui abstraksi yang disebut kasus penggunaan. Kasus penggunaan berikut tersedia:

  • Pratinjau: menerima platform untuk menampilkan pratinjau, seperti PreviewView.
  • Analisis gambar: menyediakan buffering yang dapat diakses CPU untuk analisis, misalnya untuk machine learning.
  • Pengambilan gambar: mengambil dan menyimpan foto.
  • Perekaman video: merekam video dan audio dengan VideoCapture.

Kasus penggunaan dapat digabungkan dan aktif serentak. Misalnya, sebuah aplikasi dapat mengizinkan pengguna untuk menampilkan gambar yang dilihat kamera menggunakan kasus penggunaan pratinjau, memiliki kasus penggunaan analisis gambar yang menentukan apakah orang-orang dalam foto tersenyum, dan mencakup kasus penggunaan pengambilan gambar untuk mengambil gambar saat orang-orang tersebut tersenyum.

Model API

Untuk menggunakan library ini, Anda harus menentukan hal berikut:

  • Kasus penggunaan yang diinginkan dengan opsi konfigurasi.
  • Tindakan yang dilakukan terhadap data output dengan menambahkan pemroses.
  • Alur yang diinginkan, seperti kapan mengaktifkan kamera dan kapan menghasilkan data, dengan mengikatkan kasus penggunaan ke Siklus Proses Arsitektur Android.

Ada 2 cara untuk menulis aplikasi CameraX: CameraController (cocok jika Anda menginginkan cara paling sederhana untuk menggunakan CameraX) atau CameraProvider (cocok jika Anda memerlukan fleksibilitas lebih).

CameraController

CameraController menyediakan sebagian besar fungsi inti CameraX dalam satu class. Cara ini memerlukan sedikit kode penyiapan, dan secara otomatis menangani inisialisasi kamera, pengelolaan kasus penggunaan, rotasi target, ketuk untuk fokus, cubit untuk zoom, dan lainnya. Class konkret yang memperluas CameraController adalah LifecycleCameraController.

Kotlin

val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController

Java

PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);

UseCase default untuk CameraController adalah Preview, ImageCapture, dan ImageAnalysis. Untuk menonaktifkan ImageCapture atau ImageAnalysis, atau mengaktifkan VideoCapture, gunakan metode setEnabledUseCases().

Untuk penggunaan CameraController lainnya, lihat contoh pemindai Kode QR atau video dasar-dasar CameraController.

CameraProvider

CameraProvider masih mudah digunakan, tetapi karena developer aplikasi menangani lebih banyak penyiapan, maka ada lebih banyak peluang untuk menyesuaikan konfigurasi, seperti mengaktifkan rotasi gambar output atau menyetel format gambar output di ImageAnalysis. Anda juga dapat menggunakan Surface kustom untuk pratinjau kamera agar lebih fleksibel, sedangkan dengan CameraController, Anda harus menggunakan PreviewView. Menggunakan kode Surface yang ada dapat bermanfaat jika sudah menjadi input untuk bagian lain aplikasi Anda.

Anda mengonfigurasi kasus penggunaan menggunakan metode set() dan menyelesaikannya dengan metode build(). Setiap objek kasus penggunaan menyediakan sekumpulan API khusus kasus penggunaan. Misalnya, kasus penggunaan pengambilan gambar menyediakan panggilan metode takePicture().

Sebagai ganti melakukan panggilan metode mulai dan berhenti spesifik dalam onResume() dan onPause(), aplikasi menentukan siklus proses untuk dikaitkan dengan kamera, menggunakan cameraProvider.bindToLifecycle(). Siklus proses tersebut selanjutnya memberi tahu CameraX kapan harus mengonfigurasi sesi pengambilan gambar kamera dan memastikan kamera berubah ke status yang tepat untuk menyesuaikan dengan peralihan siklus proses.

Untuk mengetahui langkah-langkah implementasi setiap kasus penggunaan, lihat Mengimplementasikan pratinjau, Menganalisis gambar, Pengambilan gambar, dan Perekaman video

Kasus penggunaan pratinjau berinteraksi dengan Surface untuk tampilan. Aplikasi membuat kasus penggunaan ini dengan opsi konfigurasi menggunakan kode berikut:

Kotlin

val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)

// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// PreviewView creates a surface provider and is the recommended provider
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())

Java

Preview preview = new Preview.Builder().build();
PreviewView viewFinder = findViewById(R.id.view_finder);

// The use case is bound to an Android Lifecycle with the following code
Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview);

// PreviewView creates a surface provider, using a Surface from a different
// kind of view will require you to implement your own surface provider.
preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();

Untuk kode contoh lainnya, lihat aplikasi contoh CameraX resmi.

Siklus proses CameraX

CameraX mengamati siklus proses untuk menentukan kapan harus membuka kamera, memulai sesi pengambilan gambar, serta berhenti dan menutup kamera. API kasus penggunaan menyediakan panggilan metode dan callback untuk memantau progres.

Sebagaimana dijelaskan dalam Menggabungkan kasus penggunaan, Anda dapat mengikat beberapa campuran kasus penggunaan menjadi satu siklus proses. Jika aplikasi Anda perlu mendukung kasus penggunaan yang tidak dapat digabungkan, Anda dapat melakukan salah satu langkah berikut:

  • Mengelompokkan kasus penggunaan yang kompatibel menjadi beberapa fragmen, lalu beralih antarfragmen
  • Membuat komponen siklus proses kustom dan menggunakannya untuk mengontrol siklus proses kamera secara manual

Jika Anda memisahkan tampilan dan pemilik Siklus Proses kasus penggunaan kamera (misalnya, jika menggunakan siklus proses kustom atau mempertahankan fragmen), maka Anda harus memastikan bahwa semua kasus penggunaan tidak terikat dengan CameraX menggunakan ProcessCameraProvider.unbindAll() atau dengan memisahkan setiap kasus penggunaan satu per satu. Atau, saat Anda mengikatkan kasus penggunaan ke Siklus Proses, Anda dapat mengizinkan CameraX mengelola pembukaan dan penutupan sesi pengambilan gambar, serta pemisahan kasus penggunaan.

Jika semua fungsi kamera sesuai dengan siklus proses komponen tunggal berbasis siklus proses seperti AppCompatActivity atau fragmen AppCompat, penggunaan siklus proses komponen tersebut saat mengikat semua kasus penggunaan yang diinginkan akan memastikan bahwa fungsi kamera siap saat komponen berbasis siklus proses aktif dan dibuang dengan aman sehingga tidak memakai resource apa pun.

LifecycleOwner kustom

Untuk kasus lanjutan, Anda dapat membuat LifecycleOwner kustom untuk memungkinkan aplikasi mengontrol siklus proses sesi CameraX secara eksplisit, bukan mengikatnya ke LifecycleOwner Android standar.

Contoh kode berikut menunjukkan cara membuat LifecycleOwner kustom sederhana:

Kotlin

class CustomLifecycle : LifecycleOwner {
    private val lifecycleRegistry: LifecycleRegistry

    init {
        lifecycleRegistry = LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }
    ...
    fun doOnResume() {
        lifecycleRegistry.markState(State.RESUMED)
    }
    ...
    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class CustomLifecycle implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;
    public CustomLifecycle() {
        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }
   ...
   public void doOnResume() {
        lifecycleRegistry.markState(State.RESUMED);
    }
   ...
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

Dengan menggunakan LifecycleOwner ini, aplikasi Anda dapat melakukan transisi status pada poin yang diinginkan dalam kodenya. Untuk informasi selengkapnya tentang cara mengimplementasikan fungsi ini pada aplikasi, lihat Mengimplementasikan LifecycleOwner kustom.

Kasus penggunaan serentak

Kasus penggunaan dapat berjalan secara serentak. Meskipun kasus penggunaan dapat secara berurutan terikat ke siklus proses, sebaiknya ikatkan semua kasus penggunaan dengan satu panggilan ke CameraProcessProvider.bindToLifecycle(). Untuk informasi selengkapnya tentang praktik terbaik perubahan konfigurasi, lihat Menangani perubahan konfigurasi.

Dalam contoh kode berikut, aplikasi menentukan dua kasus penggunaan yang akan dibuat dan dijalankan secara bersamaan. Aplikasi juga menentukan siklus proses yang digunakan untuk kedua kasus penggunaan, sehingga keduanya dimulai dan dihentikan sesuai dengan siklus prosesnya.

Kotlin

private lateinit var imageCapture: ImageCapture

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        // Camera provider is now guaranteed to be available
        val cameraProvider = cameraProviderFuture.get()

        // Set up the preview use case to display camera preview.
        val preview = Preview.Builder().build()

        // Set up the capture use case to allow users to take photos.
        imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

        // Choose the camera by requiring a lens facing
        val cameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                .build()

        // Attach use cases to the camera with the same lifecycle owner
        val camera = cameraProvider.bindToLifecycle(
                this as LifecycleOwner, cameraSelector, preview, imageCapture)

        // Connect the preview use case to the previewView
        preview.setSurfaceProvider(
                previewView.getSurfaceProvider())
    }, ContextCompat.getMainExecutor(this))
}

Java

private ImageCapture imageCapture;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PreviewView previewView = findViewById(R.id.previewView);

    ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
            ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            // Camera provider is now guaranteed to be available
            ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

            // Set up the view finder use case to display camera preview
            Preview preview = new Preview.Builder().build();

            // Set up the capture use case to allow users to take photos
            imageCapture = new ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .build();

            // Choose the camera by requiring a lens facing
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(lensFacing)
                    .build();

            // Attach use cases to the camera with the same lifecycle owner
            Camera camera = cameraProvider.bindToLifecycle(
                    ((LifecycleOwner) this),
                    cameraSelector,
                    preview,
                    imageCapture);

            // Connect the preview use case to the previewView
            preview.setSurfaceProvider(
                    previewView.getSurfaceProvider());
        } catch (InterruptedException | ExecutionException e) {
            // Currently no exceptions thrown. cameraProviderFuture.get()
            // shouldn't block since the listener is being called, so no need to
            // handle InterruptedException.
        }
    }, ContextCompat.getMainExecutor(this));
}

Kombinasi konfigurasi berikut dijamin akan didukung (saat salah satu dari Pratinjau dan Perekaman Video diperlukan, tetapi tidak didukung saat keduanya diperlukan secara bersamaan):

Pratinjau atau Perekaman Video Pengambilan gambar Analisis Deskripsi
Menyediakan pratinjau kepada pengguna atau merekam video, mengambil foto, dan menganalisis aliran gambar.
  Mengambil foto dan menganalisis aliran gambar.
  Menyediakan pratinjau kepada pengguna atau merekam video, dan mengambil foto.
  Menyediakan pratinjau kepada pengguna atau merekam video, dan menganalisis aliran gambar.

Jika Pratinjau dan Perekaman Video diperlukan, kombinasi kasus penggunaan berikut dapat didukung secara bersyarat:

Pratinjau Perekaman video Pengambilan gambar Analisis Persyaratan khusus
    Dijamin untuk semua kamera
  Perangkat kamera LIMITED (atau yang lebih baik).
  Perangkat kamera LEVEL_3 (atau yang lebih baik).

Selain itu,

  • Setiap kasus penggunaan dapat berfungsi sendiri. Misalnya, aplikasi dapat merekam video tanpa menggunakan pratinjau.
  • Jika ekstensi diaktifkan, hanya kombinasi ImageCapture dan Preview yang dijamin akan berfungsi. Bergantung pada implementasi OEM, Anda juga mungkin tidak dapat menambahkan ImageAnalysis; ekstensi tidak dapat diaktifkan untuk kasus penggunaan VideoCapture. Lihat Dokumen referensi ekstensi untuk mengetahui detailnya.
  • Bergantung pada kemampuan kamera, beberapa kamera mungkin mendukung kombinasi tersebut dalam mode resolusi rendah, tetapi tidak dapat mendukung kombinasi yang sama pada beberapa resolusi yang lebih tinggi.

Level hardware yang didukung dapat diambil dari Camera2CameraInfo. Misalnya, kode berikut memeriksa apakah kamera belakang default adalah perangkat LEVEL_3:

Kotlin

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return CameraSelector.DEFAULT_BACK_CAMERA
            .filter(cameraProvider.availableCameraInfos)
            .firstOrNull()
            ?.let { Camera2CameraInfo.from(it) }
            ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    }
    return false
}

Java

@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        List\ filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.getAvailableCameraInfos());
        if (!filteredCameraInfos.isEmpty()) {
            return Objects.equals(
                Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
        }
    }
    return false;
}

Izin

Aplikasi Anda membutuhkan izin CAMERA. Untuk menyimpan gambar ke file, tindakan ini juga memerlukan izin WRITE_EXTERNAL_STORAGE, kecuali pada perangkat yang menjalankan Android 10 atau yang lebih baru.

Untuk informasi selengkapnya tentang cara mengonfigurasi izin untuk aplikasi Anda, baca Meminta Izin Aplikasi.

Persyaratan

CameraX memiliki persyaratan versi minimum berikut:

  • Android API level 21
  • Komponen Arsitektur Android 1.1.1

Untuk aktivitas berbasis siklus proses, gunakan FragmentActivity atau AppCompatActivity.

Mendeklarasikan dependensi

Untuk menambahkan dependensi pada CameraX, Anda harus menambahkan repositori Maven Google ke project Anda.

Buka file settings.gradle untuk project Anda dan tambahkan repositori google() seperti yang ditunjukkan berikut:

Groovy

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Tambahkan item berikut ke bagian akhir blok Android:

Groovy

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Tambahkan kode berikut ke file build.gradle setiap modul untuk aplikasi:

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.4.0-alpha04"
  // The following line is optional, as the core library is included indirectly by camera-camera2
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  // If you want to additionally use the CameraX Lifecycle library
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  // If you want to additionally use the CameraX VideoCapture library
  implementation "androidx.camera:camera-video:${camerax_version}"
  // If you want to additionally use the CameraX View class
  implementation "androidx.camera:camera-view:${camerax_version}"
  // If you want to additionally add CameraX ML Kit Vision Integration
  implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.4.0-alpha04"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX VideoCapture library
    implementation("androidx.camera:camera-video:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")
    // If you want to additionally add CameraX ML Kit Vision Integration
    implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

Untuk informasi selengkapnya tentang cara mengonfigurasi aplikasi agar sesuai dengan persyaratan ini, lihat Mendeklarasikan dependensi.

Interoperabilitas CameraX dengan Camera2

CameraX dibuat di Camera2, dan CameraX menampilkan cara untuk membaca dan bahkan menulis properti dalam implementasi Camera2. Untuk mengetahui detail selengkapnya, lihat paket Interop.

Untuk informasi selengkapnya tentang cara CameraX mengonfigurasi properti Camera2, gunakan Camera2CameraInfo untuk membaca CameraCharacteristics yang mendasarinya. Anda juga dapat memilih untuk menulis properti Camera2 yang mendasarinya di salah satu dari dua jalur berikut:

Contoh kode berikut menggunakan kasus penggunaan streaming untuk mengoptimalkan panggilan video. Gunakan Camera2CameraInfo untuk mengetahui ketersediaan kasus penggunaan streaming panggilan video. Kemudian, gunakan Camera2Interop.Extender untuk menetapkan kasus penggunaan streaming yang mendasarinya.

Kotlin

// Set underlying Camera2 stream use case to optimize for video calls.

val videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
            )?.contains(videoCallStreamId)
        val isFrontFacing = (cameraInfo.getLensFacing() == 
                             CameraSelector.LENS_FACING_FRONT)
        (isVideoCallStreamingSupported == true) && isFrontFacing
    }

val cameraSelector = frontCameraInfo.cameraSelector

// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)

// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Java

// Set underlying Camera2 stream use case to optimize for video calls.

Long videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
    Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
        .getCameraCharacteristic(
            CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
        );
    boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
                .contains(videoCallStreamId);
    boolean isFrontFacing = (cameraInfo.getLensFacing() ==
                             CameraSelector.LENS_FACING_FRONT);

    if (isVideoCallStreamingSupported && isFrontFacing) {
        frontCameraInfo = cameraInfo;
    }
}

if (frontCameraInfo == null) {
    // Handle case where video call streaming is not supported.
}

CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();

// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation);

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);

// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Referensi lainnya

Untuk mempelajari CameraX lebih lanjut, lihat referensi tambahan berikut.

Codelab

  • Mulai Menggunakan CameraX
  • Contoh kode

  • Aplikasi contoh CameraX