Catatan: Halaman ini merujuk ke paket Camera2. Kecuali jika aplikasi Anda memerlukan fitur khusus level rendah 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. Dalam beberapa kasus, streaming yang berbeda bahkan memerlukan resolusi frame atau format piksel yang berbeda. Beberapa kasus penggunaan umum antara lain:
- Perekaman video: satu streaming untuk pratinjau, streaming lainnya dienkode dan disimpan ke dalam file.
- Pemindaian kode batang: satu streaming untuk pratinjau, streaming lainnya untuk deteksi kode batang.
- Fotografi komputasional: satu streaming untuk pratinjau, satu lagi untuk deteksi wajah atau scene.
Ada biaya performa yang tidak sepele saat memproses frame, dan biaya tersebut dikalikan saat melakukan pemrosesan pipeline atau streaming paralel.
Resource seperti CPU, GPU, dan DSP mungkin dapat memanfaatkan kemampuan pemrosesan ulang framework, tetapi resource seperti memori bertambah secara linear.
Beberapa target per permintaan
Beberapa streaming kamera dapat digabungkan menjadi satu
CameraCaptureRequest. Cuplikan kode berikut
mengilustrasikan cara menyiapkan sesi kamera dengan satu 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 basic 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 basic 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 sebenarnya bervariasi dari satu perangkat ke perangkat lainnya, meskipun Android memberikan beberapa
jaminan untuk mendukung kombinasi tertentu bergantung pada tiga variabel:
jenis output, ukuran output, dan level hardware.
Menggunakan kombinasi variabel yang tidak didukung mungkin berfungsi pada kecepatan frame rendah; jika
tidak, hal ini akan memicu salah satu callback kegagalan. Dokumentasi untuk
createCaptureSession menjelaskan hal-hal yang dijamin
akan berfungsi.
Jenis output
Jenis output mengacu pada format encoding frame. Nilai yang mungkin adalah PRIV, YUV, JPEG, dan RAW. Dokumentasi untuk
createCaptureSession menjelaskannya.
Saat memilih jenis output aplikasi, jika sasarannya adalah memaksimalkan
kompatibilitas, gunakan
ImageFormat.YUV_420_888 untuk analisis frame dan
ImageFormat.JPEG untuk gambar diam. Untuk skenario pratinjau dan
perekaman, Anda mungkin akan menggunakan
SurfaceView, TextureView,
MediaRecorder, MediaCodec, atau
RenderScript.Allocation. Dalam kasus tersebut, jangan
tentukan format gambar. Untuk kompatibilitas, ini akan dihitung sebagai
ImageFormat.PRIVATE, terlepas dari format sebenarnya
yang digunakan secara internal. Untuk mengkueri format yang didukung oleh perangkat berdasarkan
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(), tetapi hanya dua yang
terkait dengan kompatibilitas: PREVIEW dan MAXIMUM. Ukuran berfungsi sebagai batas atas. Jika sesuatu dengan ukuran PREVIEW berfungsi, apa pun dengan ukuran yang lebih kecil
dari PREVIEW juga akan berfungsi. Hal yang sama berlaku untuk MAXIMUM. Dokumentasi
untuk CameraDevice menjelaskan ukuran ini.
Ukuran output yang tersedia bergantung pada pilihan format. Dengan
CameraCharacteristics dan format, Anda dapat membuat kueri
untuk ukuran output 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 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 yang terbesar:
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 dengan
1080p (1920x1080), mana pun yang lebih kecil. Rasio aspek mungkin tidak sama persis dengan
rasio aspek layar, sehingga Anda mungkin perlu menerapkan letterbox atau
memangkas streaming untuk menampilkannya dalam mode layar penuh. Untuk mendapatkan ukuran pratinjau yang tepat, bandingkan ukuran output yang tersedia dengan ukuran layar, sekaligus mempertimbangkan bahwa layar mungkin diputar.
Kode berikut menentukan class helper, SmartSize, yang akan membuat perbandingan ukuran
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 tingkat hardware yang didukung
Untuk menentukan kemampuan yang tersedia saat runtime, periksa level hardware yang didukung menggunakan
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.
Dengan objek CameraCharacteristics, Anda dapat
mengambil level 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);
Menyatukan semua aset
Dengan jenis output, ukuran output, dan level hardware, Anda dapat menentukan kombinasi
stream mana yang valid. Diagram berikut adalah ringkasan konfigurasi yang didukung oleh CameraDevice dengan tingkat hardware LEGACY.
| Target 1 | Target 2 | Target 3 | Contoh kasus penggunaan | |||
|---|---|---|---|---|---|---|
| Jenis | Ukuran maks | Jenis | Ukuran maks | Jenis | Ukuran maks | |
PRIV |
MAXIMUM |
Pratinjau sederhana, pemrosesan video GPU, atau perekaman video tanpa pratinjau. | ||||
JPEG |
MAXIMUM |
Pengambilan gambar diam tanpa jendela bidik. | ||||
YUV |
MAXIMUM |
Pemrosesan video/gambar dalam aplikasi. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Pencitraan diam standar. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Pemrosesan dalam aplikasi plus pengambilan gambar diam. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Perekaman standar. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Pratinjau plus pemrosesan dalam aplikasi. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Pengambilan gambar diam plus pemrosesan dalam aplikasi. |
LEGACY adalah level hardware terendah yang mungkin. Tabel ini menunjukkan bahwa setiap
perangkat yang mendukung Camera2 (API level 21 dan yang lebih tinggi) dapat menghasilkan hingga tiga
streaming simultan menggunakan konfigurasi yang tepat dan jika tidak ada terlalu banyak
overhead yang membatasi performa, seperti batasan memori, CPU, atau termal.
Aplikasi Anda juga perlu mengonfigurasi buffer output penargetan. Misalnya, untuk
menargetkan perangkat dengan level hardware LEGACY, Anda dapat menyiapkan dua platform output
target, satu menggunakan ImageFormat.PRIVATE dan satu lagi menggunakan
ImageFormat.YUV_420_888. Ini adalah kombinasi yang didukung saat menggunakan ukuran PREVIEW. Dengan menggunakan fungsi yang ditentukan sebelumnya dalam dokumen 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);
Anda harus menunggu hingga SurfaceView siap menggunakan callback yang disediakan:
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 sesuai dengan ukuran output kamera dengan memanggil
SurfaceHolder.setFixedSize() atau Anda dapat menggunakan pendekatan
yang mirip dengan AutoFitSurfaceView dari
Modul umum contoh kamera di GitHub, yang menetapkan
ukuran absolut, dengan mempertimbangkan rasio aspek dan
ruang yang tersedia, sekaligus melakukan penyesuaian otomatis saat perubahan aktivitas
dipicu.
Menyiapkan platform lain dari ImageReader dengan format yang dipilih
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
setelah 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);
Level hardware LEGACY menargetkan perangkat dengan penyebut umum terendah. Anda dapat
menambahkan percabangan bersyarat dan menggunakan ukuran RECORD untuk salah satu platform target
output di perangkat dengan tingkat hardware LIMITED, atau bahkan meningkatkannya ke
ukuran MAXIMUM untuk perangkat dengan tingkat hardware FULL.