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.
Multi-kamera diperkenalkan di Android 9 (level API 28). Sejak dirilis, perangkat yang mendukung API telah hadir di pasar. Banyak kasus penggunaan multi-kamera yang terkait erat dengan konfigurasi hardware tertentu. Dengan kata lain, tidak semua kasus penggunaan kompatibel dengan setiap perangkat, sehingga fitur multi-kamera menjadi kandidat yang baik untuk Play Feature Delivery.
Beberapa kasus penggunaan umum antara lain:
- Zoom: beralih antar-kamera bergantung pada area pangkas atau panjang fokal yang dipilih.
- Kedalaman: menggunakan beberapa kamera untuk membuat peta kedalaman.
- Bokeh: menggunakan informasi kedalaman yang disimpulkan untuk menyimulasikan rentang fokus sempit seperti DSLR.
Perbedaan antara kamera logis dan fisik
Memahami API multi-kamera memerlukan pemahaman tentang perbedaan antara kamera logis dan fisik. Sebagai referensi, pertimbangkan perangkat dengan tiga kamera belakang. Dalam contoh ini, masing-masing dari tiga kamera belakang dianggap sebagai kamera fisik. Kamera logis kemudian merupakan pengelompokan dua atau beberapa kamera fisik tersebut. Output kamera logis dapat berupa streaming yang berasal dari salah satu kamera fisik yang mendasarinya, atau streaming gabungan yang berasal dari lebih dari satu kamera fisik yang mendasarinya secara bersamaan. Bagaimanapun, streaming ditangani oleh Hardware Abstraction Layer (HAL) kamera.
Banyak produsen ponsel mengembangkan aplikasi kamera pihak pertama, yang biasanya sudah diinstal di perangkat mereka. Untuk menggunakan semua kemampuan hardware, aplikasi tersebut dapat menggunakan API pribadi atau tersembunyi atau menerima perlakuan khusus dari implementasi driver yang tidak dapat diakses oleh aplikasi lain. Beberapa perangkat menerapkan konsep kamera logis dengan menyediakan streaming frame gabungan dari berbagai kamera fisik, tetapi hanya untuk aplikasi tertentu yang memiliki hak istimewa. Sering kali, hanya satu kamera fisik yang terekspos ke framework. Situasi untuk developer pihak ketiga sebelum Android 9 diilustrasikan dalam diagram berikut:
Mulai Android 9, API pribadi tidak lagi diizinkan di aplikasi Android. Dengan menyertakan dukungan multi-kamera dalam framework, praktik terbaik Android sangat merekomendasikan agar produsen ponsel mengekspos kamera logis untuk semua kamera fisik yang menghadap ke arah yang sama. Berikut adalah hal yang akan dilihat oleh developer pihak ketiga di perangkat yang menjalankan Android 9 dan yang lebih tinggi:
Hal yang disediakan kamera logis sepenuhnya bergantung pada implementasi OEM dari Camera HAL. Misalnya, perangkat seperti Pixel 3 mengimplementasikan kamera logisnya sedemikian rupa sehingga memilih salah satu kamera fisiknya berdasarkan panjang fokal dan area pangkas yang diminta.
Multi-camera API
API baru menambahkan konstanta, class, dan metode baru berikut:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationdanSessionConfiguration
Karena adanya perubahan pada Compatibility Definition Document (CDD) Android, API multi-kamera juga disertai dengan ekspektasi tertentu dari developer. Perangkat dengan dua kamera sudah ada sebelum Android 9, tetapi membuka lebih dari satu kamera secara bersamaan memerlukan uji coba. Di Android 9 dan yang lebih tinggi, multi-kamera memberikan serangkaian aturan untuk menentukan kapan sepasang kamera fisik yang merupakan bagian dari kamera logis yang sama dapat dibuka.
Pada umumnya, perangkat yang menjalankan Android 9 dan yang lebih tinggi mengekspos semua kamera fisik (kecuali mungkin untuk jenis sensor yang kurang umum seperti inframerah) beserta kamera logis yang lebih mudah digunakan. Untuk setiap kombinasi streaming yang dijamin berfungsi, satu streaming milik kamera logis dapat diganti dengan dua streaming dari kamera fisik yang mendasarinya.
Beberapa streaming secara bersamaan
Menggunakan beberapa streaming kamera secara bersamaan membahas
aturan untuk menggunakan beberapa streaming secara bersamaan dalam satu kamera. Dengan satu
tambahan penting, aturan yang sama berlaku untuk beberapa kamera.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
menjelaskan cara mengganti YUV_420_888 logis atau streaming mentah dengan dua streaming
fisik. Artinya, setiap streaming jenis YUV atau RAW dapat diganti dengan dua
streaming dengan jenis dan ukuran yang sama. Anda dapat memulai dengan streaming kamera dari
konfigurasi terjamin berikut untuk perangkat kamera tunggal:
- Streaming 1: Jenis YUV, ukuran
MAXIMUMdari kamera logisid = 0
Kemudian, perangkat dengan dukungan multi-kamera memungkinkan Anda membuat sesi yang menggantikan streaming YUV logis tersebut dengan dua streaming fisik:
- Streaming 1: Jenis YUV, ukuran
MAXIMUMdari kamera fisikid = 1 - Streaming 2: Jenis YUV, ukuran
MAXIMUMdari kamera fisikid = 2
Anda dapat mengganti streaming YUV atau RAW dengan dua streaming yang setara jika dan hanya jika
kedua kamera tersebut merupakan bagian dari pengelompokan kamera logis, yang tercantum di bagian
CameraCharacteristics.getPhysicalCameraIds().
Jaminan yang diberikan oleh framework hanyalah persyaratan minimum yang diperlukan untuk mendapatkan frame dari lebih dari satu kamera fisik secara bersamaan. Streaming tambahan didukung di sebagian besar perangkat, terkadang bahkan memungkinkan pembukaan beberapa perangkat kamera fisik secara independen. Karena ini bukan jaminan mutlak dari framework, melakukannya memerlukan pengujian dan penyesuaian per perangkat menggunakan metode coba-coba.
Membuat sesi dengan beberapa kamera fisik
Saat menggunakan kamera fisik pada perangkat yang mendukung multi-kamera, buka satu
CameraDevice (kamera logis) dan berinteraksi dengannya dalam satu
sesi. Buat sesi tunggal menggunakan API
CameraDevice.createCaptureSession(SessionConfiguration config), yang
ditambahkan di level API 28. Konfigurasi sesi memiliki sejumlah konfigurasi
output, yang masing-masing memiliki serangkaian target output dan, secara opsional, ID
kamera fisik yang dipilih.
Permintaan pengambilan memiliki target output yang terkait dengannya. Framework menentukan kamera fisik (atau logis) mana yang akan menerima permintaan berdasarkan target output yang dilampirkan. Jika target output sesuai dengan salah satu target output yang dikirim sebagai konfigurasi output beserta ID kamera fisik, kamera fisik tersebut akan menerima dan memproses permintaan.
Menggunakan sepasang kamera fisik
Tambahan lain pada API kamera untuk multi-kamera adalah kemampuan untuk mengidentifikasi kamera logis dan menemukan kamera fisik di baliknya. Anda dapat menentukan fungsi untuk membantu mengidentifikasi potensi pasangan kamera fisik yang dapat digunakan untuk mengganti salah satu streaming kamera logis:
Kotlin
/**
* Helper class used to encapsulate a logical camera and two underlying
* physical cameras
*/
data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)
fun findDualCameras(manager: CameraManager, facing: Int? = null): List<DualCamera> {
val dualCameras = MutableList<DualCamera>()
// Iterate over all the available camera characteristics
manager.cameraIdList.map {
Pair(manager.getCameraCharacteristics(it), it)
}.filter {
// Filter by cameras facing the requested direction
facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
}.filter {
// Filter by logical cameras
// CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
}.forEach {
// All possible pairs from the list of physical cameras are valid results
// NOTE: There could be N physical cameras as part of a logical camera grouping
// getPhysicalCameraIds() requires API >= 28
val physicalCameras = it.first.physicalCameraIds.toTypedArray()
for (idx1 in 0 until physicalCameras.size) {
for (idx2 in (idx1 + 1) until physicalCameras.size) {
dualCameras.add(DualCamera(
it.second, physicalCameras[idx1], physicalCameras[idx2]))
}
}
}
return dualCameras
}
Java
/**
* Helper class used to encapsulate a logical camera and two underlying
* physical cameras
*/
final class DualCamera {
final String logicalId;
final String physicalId1;
final String physicalId2;
DualCamera(String logicalId, String physicalId1, String physicalId2) {
this.logicalId = logicalId;
this.physicalId1 = physicalId1;
this.physicalId2 = physicalId2;
}
}
List<DualCamera> findDualCameras(CameraManager manager, Integer facing) {
List<DualCamera> dualCameras = new ArrayList<>();
List<String> cameraIdList;
try {
cameraIdList = Arrays.asList(manager.getCameraIdList());
} catch (CameraAccessException e) {
e.printStackTrace();
cameraIdList = new ArrayList<>();
}
// Iterate over all the available camera characteristics
cameraIdList.stream()
.map(id -> {
try {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
return new Pair<>(characteristics, id);
} catch (CameraAccessException e) {
e.printStackTrace();
return null;
}
})
.filter(pair -> {
// Filter by cameras facing the requested direction
return (pair != null) &&
(facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
})
.filter(pair -> {
// Filter by logical cameras
// CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
IntPredicate logicalMultiCameraPred =
arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
.anyMatch(logicalMultiCameraPred);
})
.forEach(pair -> {
// All possible pairs from the list of physical cameras are valid results
// NOTE: There could be N physical cameras as part of a logical camera grouping
// getPhysicalCameraIds() requires API >= 28
String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
dualCameras.add(
new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
}
}
});
return dualCameras;
}
Penanganan status kamera fisik dikontrol oleh kamera logis. Untuk membuka "kamera ganda", buka kamera logis yang sesuai dengan kamera fisik:
Kotlin
fun openDualCamera(cameraManager: CameraManager,
dualCamera: DualCamera,
// AsyncTask is deprecated beginning API 30
executor: Executor = AsyncTask.SERIAL_EXECUTOR,
callback: (CameraDevice) -> Unit) {
// openCamera() requires API >= 28
cameraManager.openCamera(
dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) = callback(device)
// Omitting for brevity...
override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
override fun onDisconnected(device: CameraDevice) = device.close()
})
}
Java
void openDualCamera(CameraManager cameraManager,
DualCamera dualCamera,
Executor executor,
CameraDeviceCallback cameraDeviceCallback
) {
// openCamera() requires API >= 28
cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
cameraDeviceCallback.callback(cameraDevice);
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
onDisconnected(cameraDevice);
}
});
}
Selain memilih kamera yang akan dibuka, prosesnya sama dengan membuka kamera di versi Android sebelumnya. Membuat sesi pengambilan menggunakan API konfigurasi sesi baru akan memberi tahu framework untuk mengaitkan target tertentu dengan ID kamera fisik tertentu:
Kotlin
/**
* Helper type definition that encapsulates 3 sets of output targets:
*
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
typealias DualCameraOutputs =
Triple<MutableList<Surface>?, MutableList<Surface>?, MutableList<Surface>?>
fun createDualCameraSession(cameraManager: CameraManager,
dualCamera: DualCamera,
targets: DualCameraOutputs,
// AsyncTask is deprecated beginning API 30
executor: Executor = AsyncTask.SERIAL_EXECUTOR,
callback: (CameraCaptureSession) -> Unit) {
// Create 3 sets of output configurations: one for the logical camera, and
// one for each of the physical cameras.
val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
val outputConfigsPhysical1 = targets.second?.map {
OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
val outputConfigsPhysical2 = targets.third?.map {
OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }
// Put all the output configurations into a single flat array
val outputConfigsAll = arrayOf(
outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
.filterNotNull().flatMap { it }
// Instantiate a session configuration that can be used to create a session
val sessionConfiguration = SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) = callback(session)
// Omitting for brevity...
override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
})
// Open the logical camera using the previously defined function
openDualCamera(cameraManager, dualCamera, executor = executor) {
// Finally create the session and return using the callback
it.createCaptureSession(sessionConfiguration)
}
}
Java
/**
* Helper class definition that encapsulates 3 sets of output targets:
* <p>
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
final class DualCameraOutputs {
private final List<Surface> logicalCamera;
private final List<Surface> firstPhysicalCamera;
private final List<Surface> secondPhysicalCamera;
public DualCameraOutputs(List<Surface> logicalCamera, List<Surface> firstPhysicalCamera, List<Surface> third) {
this.logicalCamera = logicalCamera;
this.firstPhysicalCamera = firstPhysicalCamera;
this.secondPhysicalCamera = third;
}
public List<Surface> getLogicalCamera() {
return logicalCamera;
}
public List<Surface> getFirstPhysicalCamera() {
return firstPhysicalCamera;
}
public List<Surface> getSecondPhysicalCamera() {
return secondPhysicalCamera;
}
}
interface CameraCaptureSessionCallback {
void callback(CameraCaptureSession cameraCaptureSession);
}
void createDualCameraSession(CameraManager cameraManager,
DualCamera dualCamera,
DualCameraOutputs targets,
Executor executor,
CameraCaptureSessionCallback cameraCaptureSessionCallback) {
// Create 3 sets of output configurations: one for the logical camera, and
// one for each of the physical cameras.
List<OutputConfiguration> outputConfigsLogical = targets.getLogicalCamera().stream()
.map(OutputConfiguration::new)
.collect(Collectors.toList());
List<OutputConfiguration> outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream()
.map(s -> {
OutputConfiguration outputConfiguration = new OutputConfiguration(s);
outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1);
return outputConfiguration;
})
.collect(Collectors.toList());
List<OutputConfiguration> outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream()
.map(s -> {
OutputConfiguration outputConfiguration = new OutputConfiguration(s);
outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2);
return outputConfiguration;
})
.collect(Collectors.toList());
// Put all the output configurations into a single flat array
List<OutputConfiguration> outputConfigsAll = Stream.of(
outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2
)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Instantiate a session configuration that can be used to create a session
SessionConfiguration sessionConfiguration = new SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
outputConfigsAll, executor, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
cameraCaptureSessionCallback.callback(cameraCaptureSession);
}
// Omitting for brevity...
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
cameraCaptureSession.getDevice().close();
}
});
// Open the logical camera using the previously defined function
openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) ->
// Finally create the session and return using the callback
c.createCaptureSession(sessionConfiguration));
}
Lihat createCaptureSession untuk mengetahui informasi tentang kombinasi streaming yang didukung. Penggabungan streaming ditujukan untuk beberapa streaming
pada satu kamera logis. Kompatibilitas ini mencakup penggunaan konfigurasi yang sama
dan penggantian salah satu streaming tersebut dengan dua streaming dari dua
kamera fisik yang merupakan bagian dari kamera logis yang sama.
Setelah sesi kamera siap, kirim permintaan pengambilan gambar yang dipilih. Setiap target permintaan pengambilan menerima datanya dari kamera fisik terkait, jika ada yang digunakan, atau kembali ke kamera logis.
Contoh kasus penggunaan zoom
Anda dapat menggabungkan kamera fisik ke dalam satu streaming sehingga pengguna dapat beralih di antara kamera fisik yang berbeda untuk mendapatkan ruang pandang yang berbeda, sehingga secara efektif menangkap "tingkat zoom" yang berbeda.
Mulailah dengan memilih pasangan kamera fisik agar pengguna dapat beralih di antaranya. Untuk efek maksimum, Anda dapat memilih pasangan kamera yang memberikan panjang fokal minimum dan maksimum yang tersedia.
Kotlin
fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {
return findDualCameras(manager, facing).map {
val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)
// Query the focal lengths advertised by each physical camera
val focalLengths1 = characteristics1.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
val focalLengths2 = characteristics2.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
// Compute the largest difference between min and max focal lengths between cameras
val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!
// Return the pair of camera IDs and the difference between min and max focal lengths
if (focalLengthsDiff1 < focalLengthsDiff2) {
Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
} else {
Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
}
// Return only the pair with the largest difference, or null if no pairs are found
}.maxByOrNull { it.second }?.first
}
Java
// Utility functions to find min/max value in float[]
float findMax(float[] array) {
float max = Float.NEGATIVE_INFINITY;
for(float cur: array)
max = Math.max(max, cur);
return max;
}
float findMin(float[] array) {
float min = Float.NEGATIVE_INFINITY;
for(float cur: array)
min = Math.min(min, cur);
return min;
}
DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
return findDualCameras(manager, facing).stream()
.map(c -> {
CameraCharacteristics characteristics1;
CameraCharacteristics characteristics2;
try {
characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
} catch (CameraAccessException e) {
e.printStackTrace();
return null;
}
// Query the focal lengths advertised by each physical camera
float[] focalLengths1 = characteristics1.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
float[] focalLengths2 = characteristics2.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
// Compute the largest difference between min and max focal lengths between cameras
Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);
// Return the pair of camera IDs and the difference between min and max focal lengths
if (focalLengthsDiff1 < focalLengthsDiff2) {
return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
} else {
return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
}
}) // Return only the pair with the largest difference, or null if no pairs are found
.max(Comparator.comparing(pair -> pair.second)).get().first;
}
Arsitektur yang tepat untuk hal ini adalah memiliki dua
SurfaceViews—satu untuk setiap aliran data. SurfaceViews ini akan
ditukar berdasarkan interaksi pengguna sehingga hanya satu yang terlihat pada waktu tertentu.
Kode berikut menunjukkan cara membuka kamera logis, mengonfigurasi output kamera, membuat sesi kamera, dan memulai dua streaming pratinjau:
Kotlin
val cameraManager: CameraManager = ...
// Get the two output targets from the activity / fragment
val surface1 = ... // from SurfaceView
val surface2 = ... // from SurfaceView
val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
null, mutableListOf(surface1), mutableListOf(surface2))
// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->
// Create a single request which has one target for each physical camera
// NOTE: Each target receive frames from only its associated physical camera
val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
arrayOf(surface1, surface2).forEach { addTarget(it) }
}.build()
// Set the sticky request for the session and you are done
session.setRepeatingRequest(captureRequest, null, null)
}
Java
CameraManager manager = ...;
// Get the two output targets from the activity / fragment
Surface surface1 = ...; // from SurfaceView
Surface surface2 = ...; // from SurfaceView
DualCamera dualCamera = findShortLongCameraPair(manager, null);
DualCameraOutputs outputTargets = new DualCameraOutputs(
null, Collections.singletonList(surface1), Collections.singletonList(surface2));
// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
// Create a single request which has one target for each physical camera
// NOTE: Each target receive frames from only its associated physical camera
CaptureRequest.Builder captureRequestBuilder;
try {
captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);
// Set the sticky request for the session and you are done
session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
});
Yang perlu dilakukan hanyalah menyediakan UI bagi pengguna untuk beralih di antara dua
platform, seperti tombol atau mengetuk dua kali SurfaceView. Anda bahkan dapat
melakukan beberapa bentuk analisis adegan dan beralih di antara dua streaming
secara otomatis.
Distorsi lensa
Semua lensa menghasilkan sejumlah distorsi. Di Android, Anda dapat mengkueri
distorsi yang dibuat oleh lensa menggunakan
CameraCharacteristics.LENS_DISTORTION, yang menggantikan
CameraCharacteristics.LENS_RADIAL_DISTORTION
yang kini tidak digunakan lagi. Untuk
kamera logis, distorsinya minimal dan aplikasi Anda dapat menggunakan
frame kurang lebih seperti yang berasal dari kamera. Untuk kamera fisik, ada
kemungkinan konfigurasi lensa yang sangat berbeda, terutama pada lensa lebar.
Beberapa perangkat mungkin menerapkan koreksi distorsi otomatis menggunakan
CaptureRequest.DISTORTION_CORRECTION_MODE.
Koreksi distorsi diaktifkan secara default untuk sebagian besar perangkat.
Kotlin
val cameraSession: CameraCaptureSession = ...
// Use still capture template to build the capture request
val captureRequest = cameraSession.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
)
// Determine if this device supports distortion correction
val characteristics: CameraCharacteristics = ...
val supportsDistortionCorrection = characteristics.get(
CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
)?.contains(
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
) ?: false
if (supportsDistortionCorrection) {
captureRequest.set(
CaptureRequest.DISTORTION_CORRECTION_MODE,
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
)
}
// Add output target, set other capture request parameters...
// Dispatch the capture request
cameraSession.capture(captureRequest.build(), ...)
Java
CameraCaptureSession cameraSession = ...;
// Use still capture template to build the capture request
CaptureRequest.Builder captureRequestBuilder = null;
try {
captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
// Determine if this device supports distortion correction
CameraCharacteristics characteristics = ...;
boolean supportsDistortionCorrection = Arrays.stream(
characteristics.get(
CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
))
.anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
if (supportsDistortionCorrection) {
captureRequestBuilder.set(
CaptureRequest.DISTORTION_CORRECTION_MODE,
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
);
}
// Add output target, set other capture request parameters...
// Dispatch the capture request
cameraSession.capture(captureRequestBuilder.build(), ...);
Menetapkan permintaan pengambilan dalam mode ini dapat memengaruhi kecepatan frame yang dapat dihasilkan oleh kamera. Anda dapat memilih untuk menyetel koreksi distorsi hanya pada pengambilan gambar diam.