Catatan: Halaman ini merujuk ke paket Camera2. Kecuali aplikasi Anda memerlukan fitur tingkat rendah tertentu dari Camera2, sebaiknya gunakan CameraX. CameraX dan Camera2 mendukung Android 5.0 (level API 21) dan versi yang lebih baru.
Aplikasi kamera dapat menggunakan lebih dari satu streaming frame secara bersamaan. Di beberapa beberapa kasus, streaming yang berbeda bahkan memerlukan resolusi frame atau piksel yang berbeda format font. Beberapa kasus penggunaan umum antara lain:
- Rekaman video: satu streaming untuk pratinjau, streaming lainnya dienkode dan disimpan menjadi file.
- Pemindaian kode batang: satu streaming untuk pratinjau, streaming lainnya untuk deteksi kode batang.
- Fotografi komputasi: satu streaming untuk pratinjau, satu streaming untuk wajah/adegan deteksi.
Ada biaya kinerja yang luar biasa saat memproses {i>frame<i}, dan biayanya adalah dikalikan saat melakukan pemrosesan streaming atau pipeline paralel.
Sumber daya seperti CPU, GPU, dan DSP mungkin dapat memanfaatkan pemrosesan ulang kemampuan, tetapi sumber daya seperti memori akan berkembang secara linear.
Beberapa target per permintaan
Beberapa streaming kamera dapat digabungkan menjadi satu
CameraCaptureRequest
Cuplikan kode berikut mengilustrasikan cara menyiapkan sesi kamera dengan satu sesi
streaming untuk pratinjau kamera dan streaming lainnya untuk pemrosesan gambar:
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
Jika Anda mengonfigurasi platform target dengan benar, kode ini hanya akan menghasilkan
streaming yang memenuhi FPS minimum yang ditentukan oleh
StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
dan
StreamComfigurationMap.GetOutputStallDuration(int, Size)
.
Performa aktual berbeda-beda di setiap perangkat, meskipun Android menyediakan beberapa
jaminan untuk mendukung kombinasi tertentu bergantung pada tiga variabel:
jenis output, ukuran output, dan tingkat hardware.
Menggunakan kombinasi variabel yang tidak didukung dapat berfungsi pada kecepatan frame yang rendah; jika
tidak, tindakan itu akan memicu salah satu callback kegagalan.
Dokumentasi untuk createCaptureSession
menjelaskan apa yang dijamin akan berhasil.
Jenis output
Jenis output mengacu pada format tempat frame dienkode. Tujuan
nilai yang dimungkinkan adalah PRIV, YUV, JPEG, dan RAW. Dokumentasi untuk
createCaptureSession
menjelaskan data tersebut.
Saat memilih jenis output aplikasi, jika tujuannya adalah untuk memaksimalkan
kompatibilitas, kemudian gunakan
ImageFormat.YUV_420_888
untuk analisis {i>frame<i} dan
ImageFormat.JPEG
untuk gambar diam
gambar. Untuk skenario pratinjau dan perekaman, Anda mungkin akan menggunakan
SurfaceView
,
TextureView
,
MediaRecorder
,
MediaCodec
, atau
RenderScript.Allocation
. Di beberapa
untuk kasus itu, jangan menetapkan format gambar. Untuk kompatibilitas, ini akan dihitung sebagai
ImageFormat.PRIVATE
,
terlepas dari format aktual yang
digunakan secara internal. Untuk mengkueri format yang didukung,
oleh perangkat yang diberikan
CameraCharacteristics
,
gunakan kode berikut:
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
Ukuran output
Semua ukuran output yang tersedia dicantumkan oleh
StreamConfigurationMap.getOutputSizes()
,
namun hanya dua yang terkait dengan kompatibilitas: PREVIEW
dan MAXIMUM
. Ukuran
bertindak sebagai batas atas. Jika sesuatu berukuran PREVIEW
berfungsi, maka apa pun dengan
yang lebih kecil dari PREVIEW
juga akan berfungsi. Hal yang sama berlaku untuk MAXIMUM
. Tujuan
dokumentasi untuk
CameraDevice
menjelaskan ukuran ini.
Ukuran output yang tersedia bergantung pada pilihan format. Mengingat
CameraCharacteristics
dan format, Anda dapat membuat kueri untuk ukuran {i>output<i} yang tersedia seperti ini:
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
Dalam kasus penggunaan pratinjau dan perekaman kamera, gunakan class target untuk menentukan ukuran yang didukung. Format ini akan ditangani oleh framework kamera itu sendiri:
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
Untuk mendapatkan ukuran MAXIMUM
, urutkan ukuran output berdasarkan area dan tampilkan ukuran terbesar
satu:
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW
mengacu pada ukuran yang paling cocok dengan resolusi layar perangkat atau
1080p (1920x1080), mana yang lebih kecil. Rasio aspek mungkin tidak cocok dengan
rasio aspek layar dengan tepat, sehingga Anda
mungkin perlu menerapkan tampilan lebar atau
memangkas ke aliran untuk
menampilkannya dalam mode layar penuh. Untuk mendapatkan jawaban yang benar
ukuran pratinjau, bandingkan ukuran output yang tersedia dengan ukuran layar,
karena tampilan bisa diputar.
Kode berikut menentukan class bantuan, SmartSize
, yang akan membuat ukuran
perbandingan dengan sedikit lebih mudah:
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
Memeriksa level hardware yang didukung
Untuk menentukan kemampuan yang tersedia saat runtime, periksa hardware yang didukung
level menggunakan
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
Dengan
CameraCharacteristics
Anda bisa mengambil tingkat hardware dengan satu pernyataan:
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
Menggabungkan semua bagian
Dengan jenis {i>output<i}, ukuran {i>output<i}, dan tingkat perangkat keras, Anda dapat menentukan
kombinasi aliran data adalah valid. Bagan berikut adalah {i>snapshot<i} dari
konfigurasi yang didukung oleh CameraDevice
dengan
LEGACY
di tingkat hardware.
Target 1 | Target 2 | Target 3 | Contoh kasus penggunaan | |||
---|---|---|---|---|---|---|
Jenis | Ukuran maksimal | Jenis | Ukuran maksimal | Jenis | Ukuran maksimal | |
PRIV |
MAXIMUM |
Pratinjau sederhana, pemrosesan video GPU, atau perekaman video tanpa pratinjau. | ||||
JPEG |
MAXIMUM |
Tanpa jendela bidik pengambilan gambar diam. | ||||
YUV |
MAXIMUM |
Pemrosesan video/gambar dalam aplikasi. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Penggambaran masih standar. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Pemrosesan dalam aplikasi plus gambar tetap. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Perekaman standar. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Pratinjau plus pemrosesan dalam aplikasi. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Pratinjau plus pemrosesan dalam aplikasi. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Tetap ambil gambar plus pemrosesan dalam aplikasi. |
LEGACY
adalah level hardware terendah. Tabel ini menunjukkan bahwa setiap
yang mendukung Camera2 (level API 21 dan yang lebih tinggi) dapat menghasilkan hingga tiga
streaming secara serentak menggunakan konfigurasi
yang benar dan jika tidak terlalu banyak
{i>overhead<i} yang membatasi kinerja, seperti
batasan memori, CPU, atau termal.
Aplikasi Anda juga perlu mengonfigurasi buffering output penargetan. Misalnya, untuk
menargetkan perangkat dengan level hardware LEGACY
, Anda dapat menyiapkan dua output target
platform, satu menggunakan ImageFormat.PRIVATE
dan satu lagi menggunakan
ImageFormat.YUV_420_888
. Kombinasi ini adalah kombinasi yang didukung saat menggunakan
Ukuran PREVIEW
. Dengan menggunakan fungsi yang ditentukan sebelumnya dalam topik ini, mendapatkan
ukuran pratinjau yang diperlukan untuk ID kamera memerlukan kode berikut:
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
Perlu menunggu hingga SurfaceView
siap menggunakan callback yang diberikan:
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
Anda dapat memaksa SurfaceView
agar cocok dengan ukuran output kamera dengan memanggil
SurfaceHolder.setFixedSize()
atau Anda dapat mengambil
pendekatan yang mirip dengan
AutoFitSurfaceView
dari objek
modul
sampel kamera di GitHub, yang menyetel ukuran absolut, dengan
mempertimbangkan rasio aspek dan ruang yang tersedia, sekaligus
menyesuaikan kapan perubahan aktivitas dipicu.
Menyiapkan permukaan
lainnya dari
ImageReader
dengan format yang diinginkan adalah
lebih mudah, karena tidak ada callback yang harus ditunggu:
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
Saat menggunakan buffer target pemblokiran seperti ImageReader
, hapus frame setelahnya
menggunakannya:
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
Tingkat hardware LEGACY
menargetkan perangkat penyebut umum terendah. Anda dapat
tambahkan percabangan bersyarat dan gunakan ukuran RECORD
untuk salah satu target output
muncul di perangkat dengan level hardware LIMITED
, atau bahkan meningkatkannya ke
Ukuran MAXIMUM
untuk perangkat dengan tingkat hardware FULL
.