Jika aplikasi Anda menggunakan class Camera
lama ("Camera1"), yang sudah tidak digunakan lagi sejak
Android 5.0 (level API 21),
sebaiknya update ke API kamera Android modern. Android menawarkan
CameraX (API kamera Jetpack
yang standar dan kuat) dan Camera2 (API framework level rendah). Untuk
sebagian besar kasus, sebaiknya migrasikan aplikasi Anda ke CameraX. Berikut alasannya:
- Kemudahan penggunaan: CameraX menangani detail level rendah, sehingga Anda dapat lebih santai dalam mem-build pengalaman kamera dari awal dan lebih memperhatikan cara menonjolkan aplikasi Anda.
- CameraX menangani fragmentasi untuk Anda: CameraX mengurangi biaya pemeliharaan jangka panjang dan kode khusus perangkat, sehingga menghadirkan pengalaman berkualitas lebih tinggi kepada pengguna. Untuk mengetahui informasi selengkapnya, lihat postingan blog Kompatibilitas Perangkat yang Lebih Baik dengan CameraX.
- Kemampuan lanjutan: CameraX dirancang dengan cermat guna memudahkan fungsi lanjutan untuk diintegrasikan ke dalam aplikasi Anda. Misalnya, Anda dapat dengan mudah menerapkan Bokeh, Retouch Wajah, HDR (Rentang Dinamis Tinggi), dan mode Night capture low-light-brightening ke foto Anda dengan Ekstensi CameraX.
- Kapasitas update: Android merilis kemampuan baru dan perbaikan bug untuk CameraX sepanjang tahun. Dengan bermigrasi ke CameraX, aplikasi Anda akan mendapatkan teknologi kamera Android terbaru dengan setiap rilis CameraX, bukan hanya pada rilis versi Android tahunan.
Dalam panduan ini, Anda akan menemukan skenario umum untuk aplikasi kamera. Setiap skenario mencakup implementasi Camera1 dan implementasi CameraX untuk perbandingan.
Dalam hal migrasi, terkadang Anda memerlukan fleksibilitas ekstra untuk berintegrasi
dengan codebase yang ada. Semua kode CameraX dalam panduan ini memiliki implementasi
CameraController
—ini bagus jika Anda menginginkan cara paling sederhana untuk menggunakan CameraX—dan juga implementasi
CameraProvider
—ini bagus jika Anda membutuhkan lebih banyak fleksibilitas. Untuk membantu Anda memutuskan mana yang
tepat untuk Anda, berikut ini manfaat masing-masing:
CameraController |
CameraProvider |
Memerlukan sedikit kode penyiapan | Memberikan kontrol yang lebih besar |
Mengizinkan CameraX menangani lebih banyak proses penyiapan berarti fungsi seperti ketuk untuk memfokuskan dan cubit untuk zoom dilakukan secara otomatis |
Karena developer aplikasi menangani penyiapan, ada lebih banyak peluang
untuk menyesuaikan konfigurasi, seperti mengaktifkan rotasi gambar output
atau menyetel format gambar output di ImageAnalysis
|
Mewajibkan PreviewView untuk pratinjau kamera memungkinkan
CameraX menawarkan integrasi menyeluruh yang lancar, seperti dalam integrasi
ML Kit kami yang dapat memetakan koordinat hasil model ML (seperti
kotak pembatas wajah) langsung ke koordinat pratinjau
|
Kemampuan untuk menggunakan `Surface` kustom untuk pratinjau kamera memungkinkan lebih banyak fleksibilitas, seperti menggunakan kode `Surface` yang ada dan dapat menjadi input untuk bagian lain aplikasi Anda |
Jika Anda mengalami kesulitan saat mencoba melakukan migrasi, hubungi kami di Grup Diskusi CameraX.
Sebelum memigrasi
Membandingkan penggunaan CameraX dengan Camera1
Meskipun kodenya mungkin terlihat berbeda, konsep yang mendasari di Camera1 dan
CameraX sangat mirip. CameraX
mengabstraksi fungsi kamera umum ke dalam kasus penggunaan,
sehingga banyak tugas yang diserahkan kepada developer di Camera1
ditangani secara otomatis oleh CameraX. Ada empat
UseCase
di CameraX yang dapat Anda
gunakan untuk berbagai tugas kamera: Preview
,
ImageCapture
,
VideoCapture
, dan
ImageAnalysis
.
Salah satu contoh CameraX yang menangani detail level rendah untuk developer adalah
ViewPort
yang dibagikan di antara
UseCase
yang aktif. Tindakan ini memastikan semua UseCase
melihat piksel yang sama persis.
Di Camera1, Anda harus mengelola detail ini sendiri, dan mengingat variabilitas
dalam rasio aspek di seluruh sensor kamera dan layar perangkat, akan sulit untuk
memastikan pratinjau Anda cocok dengan foto dan video yang diambil.
Sebagai contoh lainnya, CameraX menangani callback Lifecycle
secara otomatis pada
instance Lifecycle
yang Anda teruskan. Ini berarti CameraX menangani koneksi
aplikasi Anda ke kamera sepanjang seluruh
siklus proses aktivitas Android,
termasuk kasus berikut: menutup kamera saat aplikasi Anda masuk ke
latar belakang; menghapus pratinjau kamera saat layar tidak lagi perlu
menampilkannya; dan menjeda pratinjau kamera saat aktivitas lain lebih
diutamakan di latar depan, seperti panggilan video masuk.
Terakhir, CameraX menangani rotasi dan penskalaan tanpa memerlukan kode tambahan
di pihak Anda. Dalam kasus Activity
dengan orientasi tidak terkunci,
penyiapan UseCase
dilakukan setiap kali perangkat diputar, karena sistem menghancurkan
dan membuat ulang Activity
setiap kali orientasi berubah. Hal ini mengakibatkan
UseCases
menetapkan rotasi targetnya agar sesuai dengan orientasi tampilan secara
default setiap waktu.
Baca selengkapnya tentang rotasi di CameraX.
Sebelum membahas detailnya, berikut ini tampilan tingkat tinggi pada UseCase
CameraX dan bagaimana aplikasi Camera1 akan dikaitkan. (Konsep CameraX berwarna
biru dan konsep Camera1
berwarna
hijau.)
CameraX |
|||
Konfigurasi CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Pratinjau | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
Mengelola Surface pratinjau dan menetapkannya di Camera | Menetapkan PictureCallback dan memanggil takePicture() di Kamera | Mengelola konfigurasi Camera dan MediaRecorder dalam urutan tertentu | Kode analisis kustom yang di-build di atas Surface pratinjau |
↑ | ↑ | ↑ | ↑ |
Kode Khusus Perangkat | |||
↑ | |||
Pengelolaan Penskalaan dan Rotasi Perangkat | |||
↑ | |||
Pengelolaan Sesi Kamera (Pemilihan Kamera, Pengelolaan Siklus Proses) | |||
Camera1 |
Kompatibilitas dan performa di CameraX
CameraX mendukung perangkat yang menjalankan Android 5.0 (API level 21) dan yang lebih tinggi. Ini mewakili lebih dari 98% perangkat Android yang ada. CameraX di-build untuk menangani perbedaan antar-perangkat secara otomatis, sehingga mengurangi kebutuhan kode khusus perangkat di aplikasi Anda. Selain itu, kami menguji lebih dari 150 perangkat fisik di semua versi Android sejak versi 5.0 di CameraX Test Lab kami. Anda dapat meninjau daftar lengkap perangkat yang saat ini berada di Test Lab.
CameraX menggunakan Executor
untuk
mendorong stack kamera. Anda dapat
menetapkan eksekutor sendiri di CameraX
jika aplikasi Anda memiliki persyaratan threading tertentu. Jika tidak disetel, CameraX akan membuat
dan menggunakan Executor
internal default yang dioptimalkan. Banyak API platform tempat
CameraX di-build memerlukan pemblokiran komunikasi antar-proses (IPC) dengan
hardware yang terkadang memerlukan waktu ratusan milidetik untuk merespons. Karena alasan
ini, CameraX hanya memanggil API ini dari thread latar belakang, yang memastikan
thread utama tidak diblokir dan UI tetap sesuai.
Baca selengkapnya tentang thread.
Jika target pasar untuk aplikasi Anda meliputi perangkat kelas bawah, CameraX akan menyediakan
cara untuk mengurangi waktu penyiapan dengan
pembatas kamera. Karena
proses menghubungkan ke komponen hardware dapat memakan waktu yang tidak
singkat, terutama pada perangkat kelas bawah, Anda dapat menentukan kumpulan kamera yang dibutuhkan
aplikasi Anda. CameraX hanya terhubung ke kamera ini selama penyiapan. Misalnya, jika
aplikasi hanya menggunakan kamera belakang, aplikasi dapat menetapkan konfigurasi ini
dengan DEFAULT_BACK_CAMERA
, lalu CameraX menghindari menginisialisasi kamera
depan untuk mengurangi latensi.
Konsep pengembangan Android
Panduan ini mengasumsikan Anda telah memiliki pengetahuan umum tentang pengembangan Android. Selain dasar-dasarnya, berikut ini beberapa konsep yang membantu pemahaman Anda sebelum masuk ke kode di bawah ini:
- View Binding menghasilkan class binding untuk
file tata letak XML, sehingga Anda dapat dengan mudah
mereferensikan tampilan di Activities,
seperti yang dilakukan dalam beberapa cuplikan kode di bawah. Ada beberapa
perbedaan antara binding tampilan dan
findViewById()
(cara sebelumnya untuk mereferensikan tampilan), tetapi dalam kode di bawah ini, Anda akan dapat mengganti baris binding tampilan dengan panggilanfindViewById()
yang serupa. - Coroutine Asinkron adalah pola desain serentak
yang ditambahkan di Kotlin 1.3 dan dapat digunakan untuk menangani metode CameraX yang
menampilkan
ListenableFuture
. Hal ini menjadi lebih mudah dengan library Jetpack Concurrent sejak versi 1.1.0. Untuk menambahkan coroutine asinkron ke aplikasi:- Tambahkan
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
ke file Gradle Anda. - Masukkan kode CameraX yang menampilkan
ListenableFuture
dalam bloklaunch
atau fungsi penangguhan. - Tambahkan
panggilan
await()
ke panggilan fungsi yang menampilkanListenableFuture
. - Untuk mendapatkan pemahaman yang lebih mendalam tentang cara kerja coroutine, lihat panduan Memulai coroutine.
- Tambahkan
Memigrasikan skenario umum
Bagian ini menjelaskan cara memigrasikan skenario umum dari Camera1 ke CameraX.
Setiap skenario mencakup implementasi Camera1, implementasi CameraProvider
CameraX,
dan implementasi CameraController
CameraX.
Memilih kamera
Dalam aplikasi kamera Anda, salah satu hal pertama yang mungkin ingin Anda tawarkan adalah cara untuk memilih kamera yang berbeda.
Camera1
Di Camera1, Anda dapat memanggil
Camera.open()
tanpa parameter
untuk membuka kamera belakang pertama, atau Anda dapat meneruskan ID bilangan bulat untuk
kamera yang ingin dibuka. Berikut ini contoh tampilannya:
// Camera1: select a camera from id. // Note: opening the camera is a non-trivial task, and it shouldn't be // called from the main thread, unlike CameraX calls, which can be // on the main thread since CameraX kicks off background threads // internally as needed. private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() camera = Camera.open(id) true } catch (e: Exception) { Log.e(TAG, "failed to open camera", e) false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) camera?.release() camera = null }
CameraX: CameraController
Di CameraX, pemilihan kamera ditangani oleh class CameraSelector
. CameraX
memudahkan kasus umum penggunaan kamera default. Anda dapat menentukan apakah
akan menggunakan kamera depan default atau kamera belakang default. Selain itu,
objek CameraControl
CameraX memungkinkan Anda
menetapkan level zoom untuk aplikasi dengan mudah, sehingga jika
aplikasi Anda berjalan pada perangkat yang mendukung
kamera logis, aplikasi akan beralih
ke lensa yang tepat.
Berikut ini kode CameraX untuk menggunakan kamera belakang default dengan
CameraController
:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
CameraX: CameraProvider
Berikut ini contoh pemilihan kamera depan default dengan CameraProvider
(kamera depan atau belakang dapat digunakan dengan CameraController
atau
CameraProvider
):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector to use the default front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Jika Anda ingin mengontrol kamera mana yang dipilih, hal ini juga dapat dilakukan di
CameraX jika Anda menggunakan CameraProvider
dengan memanggil
getAvailableCameraInfos()
,
yang memberi Anda objek CameraInfo
untuk memeriksa properti kamera tertentu seperti
isFocusMeteringSupported()
.
Kemudian, Anda dapat mengonversinya menjadi CameraSelector
untuk digunakan seperti dalam contoh di atas
dengan metode CameraInfo.getCameraSelector()
.
Anda dapat memperoleh detail selengkapnya tentang setiap kamera dengan menggunakan class
Camera2CameraInfo
. Panggil
getCameraCharacteristic()
dengan kunci untuk data kamera yang Anda inginkan. Periksa
class
CameraCharacteristics
untuk melihat daftar semua kunci yang dapat Anda buat kuerinya.
Berikut ini contoh penggunaan fungsi checkFocalLength()
kustom yang dapat
Anda tentukan sendiri:
// CameraX: get a cameraSelector for first camera that matches the criteria // defined in checkFocalLength(). val cameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val focalLengths = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) return checkFocalLength(focalLengths) } val cameraSelector = cameraInfo.getCameraSelector()
Menampilkan pratinjau
Sebagian besar aplikasi kamera perlu menampilkan feed kamera di layar pada waktu tertentu. Dengan Camera1, Anda perlu mengelola callback siklus proses dengan benar, dan perlu menentukan rotasi dan penskalaan untuk pratinjau.
Selain itu, di Camera1, Anda perlu memutuskan apakah akan menggunakan
TextureView
atau
SurfaceView
sebagai platform pratinjau.
Kedua opsi tersebut memiliki konsekuensi, dan dalam kedua kasus tersebut, Camera1 mengharuskan Anda
menangani rotasi dan penskalaan dengan benar. Di sisi lain, PreviewView
CameraX memiliki implementasi yang mendasarinya untuk TextureView
dan SurfaceView
.
CameraX menentukan implementasi terbaik berdasarkan berbagai faktor seperti
jenis perangkat dan versi Android yang digunakan untuk menjalankan aplikasi Anda. Jika salah satu
implementasi tersebut kompatibel, Anda dapat mendeklarasikan preferensi dengan
PreviewView.ImplementationMode
.
Opsi COMPATIBLE
menggunakan TextureView
untuk pratinjau, dan
nilai PERFORMANCE
menggunakan SurfaceView
(jika memungkinkan).
Camera1
Untuk menampilkan pratinjau, Anda harus menulis class Preview
Anda sendiri dengan
implementasi antarmuka
android.view.SurfaceHolder.Callback
,
yang digunakan untuk meneruskan data gambar dari hardware kamera ke
aplikasi. Kemudian, sebelum Anda dapat memulai pratinjau gambar live, class
Preview
harus diteruskan ke objek Camera
.
// Camera1: set up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } override fun surfaceCreated(holder: SurfaceHolder) { // The Surface has been created, now tell the camera // where to draw the preview. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // Take care of releasing the Camera preview in your activity. } override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { // If your preview can change or rotate, take care of those // events here. Make sure to stop the preview before resizing // or reformatting it. if (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried to stop a non-existent preview; nothing to do. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the content of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
CameraX: CameraController
Di CameraX, hal-hal yang harus Anda kelola sebagai developer semakin berkurang. Jika menggunakan
CameraController
, Anda juga harus menggunakan PreviewView
. Ini berarti
Preview
UseCase
sudah tercakup, sehingga penyiapan menjadi jauh lebih mudah:
// CameraX: set up a camera preview with a CameraController. class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create the CameraController and set it on the previewView. var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) val previewView: PreviewView = viewBinding.cameraPreview previewView.controller = cameraController } }
CameraX: CameraProvider
Dengan CameraProvider
CameraX, Anda tidak perlu menggunakan PreviewView
, tetapi
cara ini tetap sangat menyederhanakan penyiapan pratinjau melalui Camera1. Untuk tujuan
demonstrasi, contoh ini menggunakan PreviewView
, tetapi Anda dapat menulis SurfaceProvider
kustom
untuk diteruskan ke setSurfaceProvider()
jika Anda memiliki kebutuhan yang lebih
kompleks.
Di sini, Preview
UseCase
tidak tercakup seperti pada CameraController
,
sehingga Anda perlu menyiapkannya:
// CameraX: set up a camera preview with a CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Create Preview UseCase. val preview = Preview.Builder() .build() .also { it.setSurfaceProvider( viewBinding.viewFinder.surfaceProvider ) } // Select default back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera() in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Ketuk untuk memfokuskan
Saat pratinjau kamera Anda berada di layar, kontrol yang umum adalah menetapkan titik fokus saat pengguna mengetuk pratinjau.
Camera1
Untuk menerapkan "ketuk untuk memfokuskan" di Camera1, Anda harus menghitung fokus optimal
Area
untuk menunjukkan tempat Camera
harus berupaya untuk fokus. Area
ini
diteruskan ke setFocusAreas()
. Selain itu, Anda harus menetapkan mode fokus yang kompatibel pada
Camera
. Area fokus hanya berpengaruh jika mode fokus saat ini adalah
FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
, atau
FOCUS_MODE_CONTINUOUS_PICTURE
.
Setiap Area
adalah persegi panjang dengan bobot yang ditentukan. Bobot tersebut memiliki nilai antara
1 hingga 1000, dan digunakan untuk memprioritaskan fokus Areas
jika beberapa nilai ditetapkan. Contoh
ini hanya menggunakan satu Area
, sehingga nilai bobot tidak menjadi masalah. Koordinat
persegi panjang berkisar dari -1000 hingga 1000. Titik kiri atas adalah (-1000, -1000).
Titik kanan bawah adalah (1000, 1000). Arahnya relatif terhadap orientasi
sensor, yaitu apa yang dilihat sensor. Arah tidak terpengaruh oleh
rotasi atau pencerminan Camera.setDisplayOrientation()
, sehingga Anda perlu
mengonversi koordinat peristiwa sentuh menjadi koordinat sensor.
// Camera1: implement tap-to-focus. class TapToFocusHandler : Camera.AutoFocusCallback { private fun handleFocus(event: MotionEvent) { val camera = camera ?: return val parameters = try { camera.getParameters() } catch (e: RuntimeException) { return } // Cancel previous auto-focus function, if one was in progress. camera.cancelAutoFocus() // Create focus Area. val rect = calculateFocusAreaCoordinates(event.x, event.y) val weight = 1 // This value's not important since there's only 1 Area. val focusArea = Camera.Area(rect, weight) // Set the focus parameters. parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO) parameters.setFocusAreas(listOf(focusArea)) // Set the parameters back on the camera and initiate auto-focus. camera.setParameters(parameters) camera.autoFocus(this) } private fun calculateFocusAreaCoordinates(x: Int, y: Int) { // Define the size of the Area to be returned. This value // should be optimized for your app. val focusAreaSize = 100 // You must define functions to rotate and scale the x and y values to // be values between 0 and 1, where (0, 0) is the upper left-hand side // of the preview, and (1, 1) is the lower right-hand side. val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000 val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000 // Calculate the values for left, top, right, and bottom of the Rect to // be returned. If the Rect would extend beyond the allowed values of // (-1000, -1000, 1000, 1000), then crop the values to fit inside of // that boundary. val left = max(normalizedX - (focusAreaSize / 2), -1000) val top = max(normalizedY - (focusAreaSize / 2), -1000) val right = min(left + focusAreaSize, 1000) val bottom = min(top + focusAreaSize, 1000) return Rect(left, top, left + focusAreaSize, top + focusAreaSize) } override fun onAutoFocus(focused: Boolean, camera: Camera) { if (!focused) { Log.d(TAG, "tap-to-focus failed") } } }
CameraX: CameraController
CameraController
akan memproses peristiwa sentuh PreviewView
untuk menangani
"ketuk untuk memfokuskan" secara otomatis. Anda dapat mengaktifkan dan menonaktifkan "ketuk untuk memfokuskan" dengan
setTapToFocusEnabled()
,
dan memeriksa nilai dengan
isTapToFocusEnabled()
pengambil yang sesuai.
Metode
getTapToFocusState()
menampilkan objek LiveData
untuk melacak perubahan pada status fokus di CameraController
.
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
CameraX: CameraProvider
Saat menggunakan CameraProvider
, ada beberapa penyiapan yang diperlukan agar fungsi "ketuk untuk memfokuskan"
berfungsi. Contoh ini mengasumsikan bahwa Anda menggunakan PreviewView
. Jika tidak, Anda harus
menyesuaikan logika untuk diterapkan ke Surface
kustom.
Berikut ini langkah-langkah saat menggunakan PreviewView
:
- Siapkan detektor gestur untuk menangani peristiwa ketuk.
- Dengan peristiwa ketuk, buat
MeteringPoint
menggunakanMeteringPointFactory.createPoint()
. - Dengan
MeteringPoint
, buatFocusMeteringAction
. - Dengan objek
CameraControl
diCamera
Anda (yang ditampilkan daribindToLifecycle()
), panggilstartFocusAndMetering()
dengan meneruskanFocusMeteringAction
. - (Opsional) Tanggapi
FocusMeteringResult
. - Setel detektor gestur untuk merespons peristiwa sentuh di
PreviewView.setOnTouchListener()
.
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector to respond to tap events and call // startFocusAndMetering on CameraControl. If you want to use a // coroutine with await() to check the result of focusing, see the // "Android development concepts" section above. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> // See pinch-to-zooom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Cubit untuk zoom
Memperbesar dan memperkecil pratinjau adalah manipulasi langsung lainnya yang umum ditemui pada pratinjau kamera. Dengan bertambahnya jumlah kamera pada perangkat, pengguna juga mengharapkan lensa dengan panjang fokal terbaik untuk dipilih secara otomatis sebagai hasil zoom.
Camera1
Ada dua cara untuk melakukan zoom menggunakan Camera1. Metode Camera.startSmoothZoom()
dianimasikan dari tingkat zoom saat ini ke tingkat zoom yang Anda teruskan. Metode
Camera.Parameters.setZoom()
akan langsung melompat ke tingkat zoom yang Anda
teruskan. Sebelum menggunakan salah satunya, panggil isSmoothZoomSupported()
atau
isZoomSupported()
untuk memastikan metode zoom terkait yang Anda perlukan
tersedia di Kamera Anda.
Untuk menerapkan "cubit untuk zoom", contoh ini menggunakan setZoom()
karena pemroses
sentuh di platform pratinjau terus mengaktifkan peristiwa saat gestur cubit
terjadi, sehingga langsung mengupdate tingkat zoom setiap waktu. Class
ZoomTouchListener
ditentukan di bawah ini, dan harus ditetapkan sebagai callback
untuk pemroses sentuh platform pratinjau Anda.
// Camera1: implement pinch-to-zoom. // Define a scale gesture detector to respond to pinch events and call // setZoom on Camera.Parameters. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return false val parameters = try { camera.parameters } catch (e: RuntimeException) { return false } // In case there is any focus happening, stop it. camera.cancelAutoFocus() // Set the zoom level on the Camera.Parameters, and set // the Parameters back onto the Camera. val currentZoom = parameters.zoom parameters.setZoom(detector.scaleFactor * currentZoom) camera.setParameters(parameters) return true } } ) // Define a View.OnTouchListener to attach to your preview view. class ZoomTouchListener : View.OnTouchListener { override fun onTouch(v: View, event: MotionEvent): Boolean = scaleGestureDetector.onTouchEvent(event) } // Set a ZoomTouchListener to handle touch events on your preview view // if zoom is supported by the current camera. if (camera.getParameters().isZoomSupported()) { view.setOnTouchListener(ZoomTouchListener()) }
CameraX: CameraController
Serupa dengan "ketuk untuk memfokuskan", CameraController
memproses peristiwa
sentuh PreviewView untuk menangani "cubit untuk zoom" secara otomatis. Anda dapat mengaktifkan dan menonaktifkan
"cubit untuk zoom" dengan
setPinchToZoomEnabled()
,
dan memeriksa nilainya dengan
isPinchToZoomEnabled()
pengambil yang sesuai.
Metode
getZoomState()
menampilkan objek LiveData
untuk melacak perubahan pada
ZoomState
di
CameraController
.
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
CameraX: CameraProvider
Agar dapat melakukan "cubit untuk zoom" dengan CameraProvider
, beberapa penyiapan diperlukan. Jika
tidak menggunakan PreviewView
, Anda perlu menyesuaikan logika untuk diterapkan ke
Surface
kustom.
Berikut ini langkah-langkah saat menggunakan PreviewView
:
- Siapkan detektor gestur skala untuk menangani peristiwa cubit.
- Dapatkan
ZoomState
dari objekCamera.CameraInfo
, tempat instanceCamera
ditampilkan saat Anda memanggilbindToLifecycle()
. - Jika
ZoomState
memiliki nilaizoomRatio
, simpan nilai tersebut sebagai rasio zoom saat ini. Jika tidak adazoomRatio
diZoomState
, gunakan tingkat zoom default kamera (1.0). - Ambil produk dari rasio zoom saat ini dengan
scaleFactor
untuk menentukan rasio zoom baru, dan teruskan keCameraControl.setZoomRatio()
. - Setel detektor gestur untuk merespons peristiwa sentuh di
PreviewView.setOnTouchListener()
.
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector to respond to pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zooom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Mengambil foto
Bagian ini menunjukkan cara memicu pengambilan foto, baik saat Anda melakukannya dengan menekan tombol shutter, setelah timer berlalu, atau pada peristiwa lain yang Anda pilih.
Camera1
Di Camera1, Anda harus terlebih dahulu menentukan
Camera.PictureCallback
untuk mengelola data gambar saat diminta. Berikut ini contoh sederhana
PictureCallback
untuk menangani data gambar JPEG:
// Camera1: define a Camera.PictureCallback to handle JPEG data. private val picture = Camera.PictureCallback { data, _ -> val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run { Log.d(TAG, "error creating media file, check storage permissions") return@PictureCallback } try { val fos = FileOutputStream(pictureFile) fos.write(data) fos.close() } catch (e: FileNotFoundException) { Log.d(TAG, "file not found", e) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
Kemudian, setiap kali Anda ingin mengambil gambar, panggil metode takePicture()
pada instance Camera
Anda. Metode takePicture()
ini memiliki tiga parameter
yang berbeda untuk jenis data yang berbeda. Parameter pertama adalah untuk
ShutterCallback
(yang tidak ditentukan dalam contoh ini). Parameter kedua adalah
untuk PictureCallback
guna menangani data kamera raw (tidak dikompresi). Parameter
ketiga adalah parameter yang digunakan contoh ini, karena merupakan PictureCallback
untuk menangani
data gambar JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
CameraController
CameraX mempertahankan kemudahan Camera1 untuk pengambilan
gambar dengan mengimplementasikan metode takePicture()
-nya sendiri. Di sini, tentukan
fungsi untuk mengonfigurasi entri MediaStore
dan mengambil foto untuk disimpan di sana.
// CameraX: define a function that uses CameraController to take a photo. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata. val outputOptions = ImageCapture.OutputFileOptions .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken. cameraController.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(e: ImageCaptureException) { Log.e(TAG, "photo capture failed", e) } override fun onImageSaved( output: ImageCapture.OutputFileResults ) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } } ) }
CameraX: CameraProvider
Mengambil foto dengan CameraProvider
memiliki cara kerja yang hampir sama dengan
CameraController
, tetapi Anda harus membuat dan mengikat ImageCapture
UseCase
terlebih dahulu agar objek dapat memanggil takePicture()
:
// CameraX: create and bind an ImageCapture UseCase. // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
Kemudian, setiap kali ingin mengambil foto, Anda dapat memanggil
ImageCapture.takePicture()
. Lihat kode CameraController
di bagian ini
untuk mengetahui contoh lengkap fungsi takePhoto()
.
// CameraX: define a function that uses CameraController to take a photo. private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return ... // Call takePicture on imageCapture instance. imageCapture.takePicture( ... ) }
Merekam video
Perekaman video jauh lebih rumit daripada skenario yang dibahas sejauh ini. Setiap bagian proses harus disiapkan dengan benar, biasanya dalam urutan tertentu. Selain itu, Anda mungkin perlu memastikan bahwa video dan audio sinkron, atau perlu menangani inkonsistensi perangkat tambahan.
Seperti yang akan Anda lihat, sekali lagi CameraX menangani banyak kerumitan ini untuk Anda.
Camera1
Perekaman video menggunakan Camera1 memerlukan pengelolaan Camera
dan
MediaRecorder
dengan cermat, dan metode tersebut harus
dipanggil dalam urutan tertentu. Anda harus mengikuti urutan ini agar
aplikasi Anda berfungsi dengan baik:
- Nyalakan kamera.
- Siapkan dan mulai pratinjau (jika aplikasi Anda menunjukkan video yang sedang direkam, dan biasanya begitu).
- Buka kunci kamera untuk digunakan oleh
MediaRecorder
dengan memanggilCamera.unlock()
. - Konfigurasikan perekaman dengan memanggil metode ini di
MediaRecorder
:- Hubungkan instance
Camera
Anda dengansetCamera(camera)
. - Panggil
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Panggil
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Panggil
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
untuk menetapkan kualitas. LihatCamcorderProfile
untuk semua opsi kualitas. - Panggil
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - Jika aplikasi Anda memiliki pratinjau video, panggil
setPreviewDisplay(preview?.holder?.surface)
. - Panggil
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - Panggil
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - Panggil
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - Panggil
prepare()
untuk menyelesaikan konfigurasiMediaRecorder
Anda.
- Hubungkan instance
- Untuk mulai merekam, panggil
MediaRecorder.start()
. - Untuk berhenti merekam, panggil metode ini. Sekali lagi, ikuti urutan berikut:
- Panggil
MediaRecorder.stop()
. - Secara opsional, hapus konfigurasi
MediaRecorder
saat ini dengan memanggilMediaRecorder.reset()
. - Panggil
MediaRecorder.release()
. - Kunci kamera agar sesi
MediaRecorder
mendatang dapat menggunakannya dengan memanggilCamera.lock()
.
- Panggil
- Untuk menghentikan pratinjau, panggil
Camera.stopPreview()
. - Terakhir, untuk merilis
Camera
agar proses lain dapat menggunakannya, panggilCamera.release()
.
Berikut adalah semua langkah tersebut jika digabungkan:
// Camera1: set up a MediaRecorder and a function to start and stop video // recording. // Make a reference to the MediaRecorder at a scope that can be accessed // throughout the camera logic in your app. private var mediaRecorder: MediaRecorder? = null private var isRecording = false ... private fun prepareMediaRecorder(): Boolean { mediaRecorder = MediaRecorder() // Unlock and set camera to MediaRecorder. camera?.unlock() mediaRecorder?.run { setCamera(camera) // Set the audio and video sources. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) setVideoSource(MediaRecorder.VideoSource.CAMERA) // Set a CamcorderProfile (requires API Level 8 or higher). setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) // Set the output file. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) // Set the preview output. setPreviewDisplay(preview?.holder?.surface) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) // Prepare configured MediaRecorder. return try { prepare() true } catch (e: IllegalStateException) { Log.d(TAG, "preparing MediaRecorder failed", e) releaseMediaRecorder() false } catch (e: IOException) { Log.d(TAG, "setting MediaRecorder file failed", e) releaseMediaRecorder() false } } return false } private fun releaseMediaRecorder() { mediaRecorder?.reset() mediaRecorder?.release() mediaRecorder = null camera?.lock() } private fun startStopVideo() { if (isRecording) { // Stop recording and release camera. mediaRecorder?.stop() releaseMediaRecorder() camera?.lock() isRecording = false // This is a good place to inform user that video recording has stopped. } else { // Initialize video camera. if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, now // you can start recording. mediaRecorder?.start() isRecording = true // This is a good place to inform the user that recording has // started. } else { // Prepare didn't work, release the camera. releaseMediaRecorder() // Inform user here. } } }
CameraX: CameraController
Dengan CameraController
CameraX, Anda dapat mengalihkan ImageCapture
,
VideoCapture
, dan UseCase
ImageAnalysis
secara independen,
selama daftar UseCases dapat digunakan secara bersamaan.
ImageCapture
dan UseCase
ImageAnalysis
diaktifkan secara default,
itulah sebabnya Anda tidak perlu memanggil setEnabledUseCases()
untuk mengambil foto.
Agar dapat menggunakan CameraController
untuk perekaman video, Anda harus menggunakan
setEnabledUseCases()
terlebih dahulu untuk memungkinkan UseCase
VideoCapture
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Jika ingin mulai merekam video, Anda dapat memanggil
fungsi
CameraController.startRecording()
. Fungsi ini dapat menyimpan rekaman video ke File
, seperti yang dapat Anda lihat
pada contoh di bawah. Selain itu, Anda harus meneruskan Executor
dan class
yang mengimplementasikan
OnVideoSavedCallback
untuk menangani callback berhasil dan error. Saat perekaman harus berakhir, panggil
CameraController.stopRecording()
.
Catatan: Jika Anda menggunakan CameraX 1.3.0-alpha02 atau yang lebih baru, ada parameter
AudioConfig
tambahan
yang memungkinkan Anda mengaktifkan atau menonaktifkan rekaman audio di video Anda. Untuk mengaktifkan
rekaman audio, Anda harus memastikan bahwa Anda memiliki izin mikrofon.
Selain itu, metode stopRecording()
dihapus di 1.3.0-alpha02, dan
startRecording()
menampilkan objek Recording
yang dapat digunakan untuk menjeda,
melanjutkan, dan menghentikan perekaman video.
// CameraX: implement video capture with CameraController. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" // Define a VideoSaveCallback class for handling success and error states. class VideoSaveCallback : OnVideoSavedCallback { override fun onVideoSaved(outputFileResults: OutputFileResults) { val msg = "Video capture succeeded: ${outputFileResults.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) { Log.d(TAG, "error saving video: $message", cause) } } private fun startStopVideo() { if (cameraController.isRecording()) { // Stop the current recording session. cameraController.stopRecording() return } // Define the File options for saving the video. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val outputFileOptions = OutputFileOptions .Builder(File(this.filesDir, name)) .build() // Call startRecording on the CameraController. cameraController.startRecording( outputFileOptions, ContextCompat.getMainExecutor(this), VideoSaveCallback() ) }
CameraX: CameraProvider
Jika menggunakan CameraProvider
, Anda harus membuat UseCase
VideoCapture
dan meneruskan objek Recorder
. Pada Recorder.Builder
, Anda dapat
menyetel kualitas video dan, secara opsional,
FallbackStrategy
, yang
menangani kasus saat perangkat tidak dapat memenuhi spesifikasi kualitas yang diinginkan. Lalu,
ikat instance VideoCapture
ke CameraProvider
dengan
UseCase
Anda yang lain.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
Pada tahap ini, Recorder
dapat diakses di properti
videoCapture.output
. Recorder
dapat memulai perekaman video yang disimpan ke File
,
ParcelFileDescriptor
, atau MediaStore
. Contoh ini menggunakan MediaStore
.
Di Recorder
, ada beberapa metode yang harus dipanggil untuk mempersiapkannya. Panggil
prepareRecording()
untuk menyetel opsi output MediaStore
. Jika aplikasi Anda memiliki
izin untuk menggunakan mikrofon perangkat, panggil withAudioEnabled()
juga.
Kemudian, panggil start()
untuk mulai merekam, dengan meneruskan konteks dan
pemroses peristiwa Consumer<VideoRecordEvent>
untuk menangani peristiwa perekaman video. Jika
berhasil, Recording
yang ditampilkan dapat digunakan untuk menjeda, melanjutkan, atau menghentikan
perekaman.
// CameraX: implement video capture with CameraProvider. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun startStopVideo() { val videoCapture = this.videoCapture ?: return if (recording != null) { // Stop the current recording session. recording.stop() recording = null return } // Create and start a new recording session. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText( baseContext, msg, Toast.LENGTH_SHORT ).show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "video capture ends with error", recordEvent.error) } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } }
Referensi lainnya
Kami memiliki beberapa aplikasi CameraX lengkap di Repositori GitHub Contoh Kamera. Contoh ini menunjukkan bagaimana skenario dalam panduan ini sesuai dengan aplikasi Android yang lengkap.
Jika Anda menginginkan dukungan tambahan saat bermigrasi ke CameraX atau memiliki pertanyaan terkait rangkaian API Android Camera, hubungi kami di Grup Diskusi CameraX.