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.
Kamera dan pratinjau kamera tidak selalu dalam orientasi yang sama di perangkat Android.
Kamera berada dalam posisi tetap pada sebuah perangkat, terlepas dari apakah perangkat tersebut berupa ponsel, tablet, atau komputer. Saat orientasi perangkat berubah, orientasi kamera juga akan berubah.
Akibatnya, aplikasi kamera umumnya mengasumsikan hubungan tetap antara orientasi perangkat dan rasio aspek pratinjau kamera. Jika ponsel berada dalam orientasi potret, pratinjau kamera dianggap lebih tinggi daripada lebarnya. Saat ponsel (dan kamera) diputar ke lanskap, pratinjau kamera diharapkan lebih lebar daripada tingginya.
Namun, asumsi ini ditantang oleh faktor bentuk baru, seperti perangkat foldable, dan mode tampilan seperti multi-aplikasi dan multi-tampilan. Perangkat foldable mengubah ukuran layar dan rasio aspek tanpa mengubah orientasi. Mode multi-aplikasi membatasi aplikasi kamera ke sebagian layar, menskalakan pratinjau kamera terlepas dari orientasi perangkat. Mode multi-tampilan memungkinkan penggunaan tampilan sekunder yang mungkin tidak memiliki orientasi yang sama dengan tampilan utama.
Orientasi kamera
Android Compatibility Definition menetapkan bahwa sensor gambar kamera "HARUS diorientasikan sehingga dimensi panjang kamera selaras dengan dimensi panjang layar. Artinya, jika perangkat dipegang dalam orientasi lanskap, kamera HARUS mengambil gambar dalam orientasi lanskap. Ini berlaku terlepas dari orientasi alami perangkat; yaitu, ini berlaku untuk perangkat utama lanskap serta perangkat utama potret."
Pengaturan kamera ke layar memaksimalkan area tampilan jendela bidik kamera di aplikasi kamera. Selain itu, sensor gambar biasanya menghasilkan datanya dalam rasio aspek lanskap, yaitu 4:3 adalah yang paling umum.
Orientasi alami sensor kamera adalah lanskap. Pada Gambar 1, sensor kamera depan (kamera yang menunjuk ke arah yang sama dengan layar) diputar 270 derajat secara relatif terhadap ponsel untuk mematuhi Definisi Kompatibilitas Android.
Untuk mengekspos rotasi sensor ke aplikasi, camera2 API menyertakan
konstanta
SENSOR_ORIENTATION
. Untuk sebagian besar ponsel dan tablet, perangkat melaporkan orientasi sensor
270 derajat untuk kamera depan dan 90 derajat (sudut pandang dari
bagian belakang perangkat) untuk kamera belakang, yang menyelaraskan tepi panjang
sensor dengan tepi panjang perangkat. Kamera laptop umumnya melaporkan
orientasi sensor 0 atau 180 derajat.
Karena sensor gambar kamera menghasilkan datanya (buffer gambar) dalam
orientasi alami sensor (lanskap), buffering gambar harus diputar
sesuai jumlah derajat yang ditentukan oleh SENSOR_ORIENTATION
agar pratinjau kamera
muncul tegak dalam orientasi alami perangkat. Untuk kamera depan,
rotasi berlawanan arah jarum jam; untuk kamera belakang, searah jarum jam.
Misalnya, untuk kamera depan pada gambar 1, buffering gambar yang dihasilkan oleh sensor kamera akan terlihat seperti ini:
Gambar harus diputar 270 derajat berlawanan arah jarum jam agar orientasi pratinjau cocok dengan orientasi perangkat:
Kamera belakang akan menghasilkan buffering gambar dengan orientasi yang sama
dengan buffering di atas, tetapi SENSOR_ORIENTATION
memiliki sudut 90 derajat. Akibatnya, buffer diputar 90 derajat searah jarum jam.
Rotasi perangkat
Rotasi perangkat adalah jumlah derajat perangkat yang diputar dari orientasi alaminya. Misalnya, ponsel dalam orientasi lanskap memiliki rotasi perangkat 90 atau 270 derajat, bergantung pada arah rotasi.
Buffer gambar sensor kamera harus diputar dengan jumlah yang sama dengan rotasi perangkat (selain derajat orientasi sensor) agar pratinjau kamera tampak tegak.
Penghitungan orientasi
Orientasi pratinjau kamera yang tepat mempertimbangkan orientasi sensor dan rotasi perangkat.
Rotasi keseluruhan buffering gambar sensor dapat dihitung menggunakan formula berikut:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
dengan sign
adalah 1
untuk kamera depan, -1
untuk kamera belakang.
Untuk kamera depan, buffering gambar diputar berlawanan arah jarum jam (dari orientasi alami sensor). Untuk kamera belakang, buffering gambar sensor diputar searah jarum jam.
Ekspresi deviceOrientationDegrees * sign + 360
mengonversi rotasi perangkat
dari berlawanan arah jarum jam menjadi searah jarum jam untuk kamera belakang (misalnya,
mengonversi 270 derajat berlawanan arah jarum jam menjadi 90 derajat searah jarum jam). Operasi modulo
menskalakan hasilnya hingga kurang dari 360 derajat (misalnya, menskalakan 540
derajat rotasi menjadi 180).
API yang berbeda melaporkan rotasi perangkat secara berbeda:
Display#getRotation()
menyediakan rotasi perangkat berlawanan arah jarum jam (dari sudut pandang pengguna). Nilai ini sesuai dengan formula di atas sebagaimana adanya.OrientationEventListener#onOrientationChanged()
menampilkan rotasi perangkat searah jarum jam (dari sudut pandang pengguna). Menegasi nilai untuk digunakan dalam formula di atas.
Kamera depan
Berikut buffering gambar yang dihasilkan oleh sensor kamera pada gambar 2:
Buffer harus diputar 270 derajat berlawanan arah jarum jam agar dapat menyesuaikan orientasi sensor (lihat Orientasi kamera di atas):
Kemudian buffer diputar 90 derajat tambahan berlawanan arah jarum jam untuk memperhitungkan rotasi perangkat, sehingga menghasilkan orientasi pratinjau kamera yang benar pada gambar 2:
Berikut ini kamera yang diputar ke kanan untuk orientasi lanskap:
Berikut buffering gambar:
Buffer harus diputar 270 derajat berlawanan arah jarum jam agar dapat menyesuaikan orientasi sensor:
Kemudian buffer diputar 270 derajat lagi berlawanan arah jarum jam untuk memperhitungkan rotasi perangkat:
Kamera belakang
Kamera belakang biasanya memiliki orientasi sensor 90 derajat (seperti yang dilihat dari bagian belakang perangkat). Saat mengorientasikan pratinjau kamera, buffer gambar sensor diputar searah jarum jam berdasarkan jumlah rotasi sensor (bukan berlawanan arah jarum jam seperti kamera depan), lalu buffer gambar diputar berlawanan arah jarum jam dengan jumlah rotasi perangkat.
Berikut adalah buffering gambar dari sensor kamera pada gambar 4:
Buffer harus diputar 90 derajat searah jarum jam untuk menyesuaikan orientasi sensor:
Kemudian, buffer diputar 270 derajat berlawanan arah jarum jam untuk memperhitungkan rotasi perangkat:
Rasio aspek
Rasio aspek tampilan berubah saat orientasi perangkat berubah, tetapi juga saat perangkat foldable dilipat dan dibentangkan, saat jendela diubah ukurannya di lingkungan multi-aplikasi, dan saat aplikasi terbuka pada tampilan sekunder.
Buffering gambar sensor kamera harus diorientasikan dan diskalakan agar sesuai dengan orientasi dan rasio aspek elemen UI jendela bidik karena UI berubah orientasi secara dinamis—dengan atau tanpa orientasi perubahan perangkat.
Pada faktor bentuk baru atau di lingkungan multi-aplikasi atau multi-tampilan, jika aplikasi Anda mengasumsikan bahwa pratinjau kamera memiliki orientasi yang sama dengan perangkat (potret atau lanskap), pratinjau mungkin diorientasikan secara salah, diskalakan dengan salah, atau keduanya.
Pada Gambar 5, aplikasi salah mengasumsikan perangkat diputar 90 derajat berlawanan arah jarum jam; sehingga aplikasi memutar pratinjau dengan jumlah yang sama.
Pada Gambar 6, aplikasi tidak menyesuaikan rasio aspek buffering gambar untuk memungkinkannya diskalakan dengan benar agar sesuai dengan dimensi baru elemen UI pratinjau kamera.
Aplikasi kamera berorientasi tetap biasanya mengalami masalah pada perangkat foldable dan perangkat layar besar lainnya seperti laptop:
Pada Gambar 7, UI aplikasi kamera miring karena orientasi aplikasi dibatasi untuk potret saja. Gambar jendela bidik diorientasikan dengan benar sesuai dengan sensor kamera.
Mode potret inset
Aplikasi kamera yang tidak mendukung mode multi-aplikasi
(resizeableActivity="false"
)
dan membatasi orientasinya
(screenOrientation="portrait"
atau screenOrientation="landscape"
)
dapat ditempatkan dalam mode potret inset di perangkat layar besar untuk mengorientasikan
pratinjau kamera dengan benar.
Inset tampilan lebar aplikasi potret (inset) dalam orientasi potret meskipun rasio aspek tampilan adalah lanskap. Aplikasi khusus lanskap memiliki tampilan lebar dalam orientasi lanskap meskipun rasio aspek tampilan adalah potret. Gambar kamera diputar agar selaras dengan UI aplikasi, dipangkas agar sesuai dengan rasio aspek pratinjau kamera, lalu diskalakan untuk mengisi pratinjau.
Mode potret inset dipicu jika rasio aspek sensor gambar kamera dan rasio aspek aktivitas utama aplikasi tidak cocok.
Pada Gambar 8, aplikasi kamera khusus potret telah diputar untuk menampilkan UI tepat di layar laptop. Aplikasi memiliki tampilan lebar karena adanya perbedaan rasio aspek antara aplikasi potret dan tampilan lanskap. Gambar pratinjau kamera telah diputar untuk mengimbangi rotasi UI aplikasi (karena mode potret inset), dan gambar telah dipangkas serta diskalakan agar sesuai dengan orientasi potret, sehingga mengurangi ruang pandang.
Putar, pangkas, skalakan
Mode potret inset dipanggil untuk aplikasi kamera khusus potret pada layar yang memiliki rasio aspek lanskap:
Aplikasi memiliki tampilan lebar dalam orientasi potret:
Gambar kamera diputar 90 derajat untuk menyesuaikan reorientasi aplikasi:
Gambar dipangkas sesuai rasio aspek pratinjau kamera, lalu diskalakan untuk mengisi pratinjau (ruang pandang dikurangi):
Pada perangkat foldable, orientasi sensor kamera dapat berupa potret sedangkan rasio aspek tampilan adalah lanskap:
Karena pratinjau kamera diputar untuk menyesuaikan orientasi sensor, gambar diorientasikan dengan benar di jendela bidik, tetapi aplikasi khusus potret berorientasi ke samping.
Mode potret inset hanya perlu membuat tampilan lebar aplikasi dalam orientasi potret untuk mengorientasikan aplikasi dan pratinjau kamera dengan benar:
API
Mulai Android 12 (API level 31) aplikasi juga dapat secara eksplisit mengontrol mode potret
inset menggunakan properti
SCALER_ROTATE_AND_CROP
dari class
CaptureRequest
.
Nilai defaultnya adalah
SCALER_ROTATE_AND_CROP_AUTO
,
yang memungkinkan sistem memanggil mode potret inset.
SCALER_ROTATE_AND_CROP_90
adalah perilaku mode potret inset seperti yang dijelaskan di atas.
Tidak semua perangkat mendukung semua nilai SCALER_ROTATE_AND_CROP
. Untuk mendapatkan daftar
nilai yang didukung, referensikan
CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES
.
CameraX
Library Jetpack CameraX menjadikan pembuatan jendela bidik kamera yang mengakomodasi orientasi sensor dan rotasi perangkat sebagai tugas sederhana.
Elemen tata letak PreviewView
membuat pratinjau kamera, yang secara otomatis menyesuaikan untuk orientasi sensor,
rotasi perangkat, dan penskalaan. PreviewView
mempertahankan rasio aspek
gambar kamera dengan menerapkan
jenis skala FILL_CENTER
,
yang menempatkan gambar di tengah, tetapi dapat memangkasnya agar sesuai dengan dimensi
PreviewView
. Untuk membuat gambar kamera menjadi tampilan lebar, tetapkan jenis skala ke
FIT_CENTER
.
Untuk mempelajari dasar-dasar pembuatan pratinjau kamera dengan PreviewView
, lihat
Mengimplementasikan pratinjau.
Untuk contoh implementasi lengkap, lihat repositori CameraXBasic
di GitHub.
KameraViewfinder
Serupa dengan kasus penggunaan Pratinjau, library CameraViewfinder menyediakan serangkaian alat untuk menyederhanakan pembuatan pratinjau kamera. Ini tidak bergantung pada CameraX Core, sehingga Anda dapat mengintegrasikannya dengan lancar ke codebase Camera2 yang ada.
Daripada menggunakan
Surface
secara langsung, Anda dapat menggunakan widget
CameraViewfinder
untuk menampilkan feed kamera untuk Camera2.
CameraViewfinder
secara internal menggunakan TextureView
atau SurfaceView
untuk menampilkan feed kamera, dan menerapkan transformasi yang diperlukan pada feed tersebut untuk
menampilkan jendela bidik dengan benar.
Hal ini melibatkan koreksi rasio aspek, skala, dan rotasi.
Untuk meminta platform dari objek CameraViewfinder
, Anda harus
membuat ViewfinderSurfaceRequest
.
Permintaan ini berisi persyaratan untuk resolusi permukaan dan informasi perangkat
kamera dari CameraCharacteristics
.
Memanggil requestSurfaceAsync()
akan mengirim permintaan ke penyedia platform, yang dapat berupa TextureView
atau
SurfaceView
dan mendapatkan ListenableFuture
Surface
.
Memanggil markSurfaceSafeToRelease()
akan memberi tahu penyedia platform bahwa platform tidak diperlukan dan resource
terkait dapat dilepaskan.
Kotlin
fun startCamera(){ val previewResolution = Size(width, height) val viewfinderSurfaceRequest = ViewfinderSurfaceRequest(previewResolution, characteristics) val surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest) Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> { override fun onSuccess(surface: Surface) { /* create a CaptureSession using this surface as usual */ } override fun onFailure(t: Throwable) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)) }
Java
void startCamera(){ Size previewResolution = new Size(width, height); ViewfinderSurfaceRequest viewfinderSurfaceRequest = new ViewfinderSurfaceRequest(previewResolution, characteristics); ListenableFuture<Surface> surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest); Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() { @Override public void onSuccess(Surface result) { /* create a CaptureSession using this surface as usual */ } @Override public void onFailure(Throwable t) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)); }
SurfaceView
SurfaceView
adalah
pendekatan mudah untuk membuat pratinjau kamera jika pratinjau tidak
memerlukan pemrosesan dan tidak dianimasikan.
SurfaceView
otomatis memutar buffering gambar sensor kamera agar sesuai
dengan orientasi tampilan, dengan mempertimbangkan orientasi sensor dan rotasi
perangkat. Namun, buffering gambar diskalakan agar sesuai dengan dimensi SurfaceView
tanpa mempertimbangkan rasio aspek.
Anda harus memastikan bahwa rasio aspek buffering gambar cocok dengan rasio aspek
SurfaceView
, yang dapat Anda capai dengan menskalakan konten
SurfaceView
dalam metode
onMeasure()
komponen:
(Kode sumber computeRelativeRotation()
ada di
Rotasi relatif di bawah.)
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val width = MeasureSpec.getSize(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees) if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ val scaleX = if (relativeRotation % 180 == 0) { width.toFloat() / previewWidth } else { width.toFloat() / previewHeight } /* Scale factor required to scale the preview to its original size on the y-axis. */ val scaleY = if (relativeRotation % 180 == 0) { height.toFloat() / previewHeight } else { height.toFloat() / previewWidth } /* Scale factor required to fit the preview to the SurfaceView size. */ val finalScale = min(scaleX, scaleY) setScaleX(1 / scaleX * finalScale) setScaleY(1 / scaleY * finalScale) } setMeasuredDimension(width, height) }
Java
@Override void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees); if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ float scaleX = (relativeRotation % 180 == 0) ? (float) width / previewWidth : (float) width / previewHeight; /* Scale factor required to scale the preview to its original size on the y-axis. */ float scaleY = (relativeRotation % 180 == 0) ? (float) height / previewHeight : (float) height / previewWidth; /* Scale factor required to fit the preview to the SurfaceView size. */ float finalScale = Math.min(scaleX, scaleY); setScaleX(1 / scaleX * finalScale); setScaleY(1 / scaleY * finalScale); } setMeasuredDimension(width, height); }
Untuk detail selengkapnya tentang cara menerapkan SurfaceView
sebagai pratinjau kamera, lihat
Orientasi kamera.
TampilanTeks
TextureView
berperforma lebih rendah daripada
SurfaceView
—dan lebih banyak pekerjaan—tetapi TextureView
memberi Anda kontrol
maksimum atas pratinjau kamera.
TextureView
memutar buffering gambar sensor berdasarkan orientasi sensor, tetapi
tidak menangani rotasi perangkat atau penskalaan pratinjau.
Penskalaan dan rotasi dapat dienkode dalam transformasi Matrix. Untuk mempelajari cara
menskalakan dan memutar TextureView
dengan benar, lihat
Mendukung platform yang dapat diubah ukurannya di aplikasi kamera Anda
Rotasi relatif
Rotasi relatif sensor kamera adalah jumlah rotasi yang diperlukan untuk menyelaraskan output sensor kamera dengan orientasi perangkat.
Rotasi relatif digunakan oleh komponen seperti SurfaceView
dan TextureView
untuk menentukan faktor penskalaan x dan y untuk gambar pratinjau. Parameter ini juga digunakan untuk
menentukan rotasi buffering gambar sensor.
Class
CameraCharacteristics
dan
Surface
memungkinkan penghitungan
rotasi relatif sensor kamera:
Kotlin
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public fun computeRelativeRotation( characteristics: CameraCharacteristics, surfaceRotationDegrees: Int ): Int { val sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! // Reverse device orientation for back-facing cameras. val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ) 1 else -1 // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360 }
Java
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public int computeRelativeRotation( CameraCharacteristics characteristics, int surfaceRotationDegrees ){ Integer sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // Reverse device orientation for back-facing cameras. int sign = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1; // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360; }
Metrik jendela aplikasi
Ukuran layar tidak boleh digunakan untuk menentukan dimensi jendela bidik kamera; aplikasi kamera mungkin berjalan di sebagian layar, baik dalam mode multi-aplikasi di perangkat seluler maupun mode free-from di ChromeOS.
WindowManager#getCurrentWindowMetrics()
(ditambahkan di API level 30) menampilkan ukuran jendela aplikasi, bukan
ukuran layar. Metode library Jetpack WindowManager
WindowMetricsCalculator#computeCurrentWindowMetrics()
dan
WindowInfoTracker#currentWindowMetrics()
memberikan dukungan serupa dengan kompatibilitas mundur ke API level 14.
Rotasi 180 derajat
Rotasi perangkat 180 derajat (misalnya, dari orientasi alami ke
orientasi alami terbalik) tidak memicu
callback
onConfigurationChanged()
. Akibatnya, pratinjau kamera mungkin terbalik.
Untuk mendeteksi rotasi 180 derajat, terapkan
DisplayListener
dan periksa rotasi perangkat dengan panggilan ke
Display#getRotation()
di
callback
onDisplayChanged()
.
Referensi eksklusif
Sebelum Android 10, hanya aktivitas yang paling terlihat di lingkungan
multi-aplikasi yang berada dalam status RESUMED
. Hal ini membingungkan pengguna karena
sistem tidak memberikan indikasi aktivitas mana yang dilanjutkan.
Android 10 (API level 29) memperkenalkan multi-resume dengan semua aktivitas yang terlihat
dalam status RESUMED
. Aktivitas yang terlihat tetap dapat memasuki status PAUSED
jika, misalnya, aktivitas transparan berada di atas aktivitas atau
aktivitas tidak dapat difokuskan, seperti dalam mode picture-in-picture (lihat
Dukungan picture-in-picture).
Aplikasi yang menggunakan kamera, mikrofon, atau resource eksklusif atau
singleton apa pun pada API level 29 atau yang lebih tinggi harus mendukung multi-resume. Misalnya, jika tiga aktivitas yang dilanjutkan ingin menggunakan kamera, hanya satu yang dapat
mengakses resource eksklusif ini. Setiap aktivitas harus menerapkan callback
onDisconnected()
agar tetap mengetahui akses preemtif ke kamera oleh aktivitas dengan prioritas
yang lebih tinggi.
Untuk mengetahui informasi selengkapnya, lihat Multi-resume.
Referensi lainnya
- Untuk contoh Camera2, lihat aplikasi Camera2Basic di GitHub.
- Untuk mempelajari kasus penggunaan pratinjau CameraX, lihat CameraX Mengimplementasikan pratinjau.
- Untuk contoh implementasi pratinjau kamera CameraX, lihat repositori CameraXBasic di GitHub.
- Untuk mengetahui informasi tentang pratinjau kamera di ChromeOS, lihat Orientasi kamera.
- Untuk informasi tentang pengembangan perangkat foldable, lihat Mempelajari perangkat foldable.