Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên sử dụng CameraX, trừ phi ứng dụng của bạn yêu cầu các tính năng cụ thể, cấp thấp của Camera2. Cả CameraX và Camera2 đều hỗ trợ Android 5.0 (API cấp 21) trở lên.
Tính năng nhiều camera được ra mắt cùng với Android 9 (cấp độ API 28). Kể từ khi API này được phát hành, các thiết bị hỗ trợ API này đã được ra mắt trên thị trường. Nhiều trường hợp sử dụng nhiều camera được kết hợp chặt chẽ với một cấu hình phần cứng cụ thể. Nói cách khác, không phải trường hợp sử dụng nào cũng tương thích với mọi thiết bị, điều này khiến các tính năng nhiều camera trở thành một lựa chọn phù hợp cho Play Feature Delivery.
Sau đây là một số trường hợp sử dụng thường gặp:
- Thu phóng: chuyển đổi giữa các camera tuỳ thuộc vào vùng cắt hoặc tiêu cự đã chọn.
- Chiều sâu: sử dụng nhiều camera để tạo bản đồ chiều sâu.
- Bokeh: sử dụng thông tin độ sâu được suy luận để mô phỏng phạm vi lấy nét hẹp giống như máy ảnh DSLR.
Sự khác biệt giữa camera logic và camera vật lý
Để hiểu được API nhiều camera, bạn cần hiểu được sự khác biệt giữa camera logic và camera vật lý. Để tham khảo, hãy xem xét một thiết bị có ba camera sau. Trong ví dụ này, mỗi camera trong số 3 camera sau được coi là một camera vật lý. Sau đó, máy ảnh logic sẽ là một nhóm gồm hai hoặc nhiều máy ảnh vật lý đó. Đầu ra của camera logic có thể là một luồng đến từ một trong các camera vật lý cơ bản hoặc một luồng kết hợp đến từ nhiều camera vật lý cơ bản cùng một lúc. Dù bằng cách nào, luồng cũng được xử lý bằng Lớp trừu tượng phần cứng (HAL) của máy ảnh.
Nhiều nhà sản xuất điện thoại phát triển các ứng dụng máy ảnh của bên thứ nhất, thường được cài đặt trước trên thiết bị của họ. Để sử dụng tất cả các chức năng của phần cứng, họ có thể sử dụng API riêng tư hoặc ẩn hoặc nhận được sự hỗ trợ đặc biệt từ việc triển khai trình điều khiển mà các ứng dụng khác không có quyền truy cập. Một số thiết bị triển khai khái niệm máy ảnh logic bằng cách cung cấp một luồng khung hình kết hợp từ nhiều máy ảnh thực, nhưng chỉ cho một số ứng dụng đặc quyền nhất định. Thông thường, chỉ có một trong các camera vật lý được hiển thị cho khung. Tình huống dành cho nhà phát triển bên thứ ba trước Android 9 được minh hoạ trong sơ đồ sau:
Kể từ Android 9, các API riêng tư không còn được cho phép trong ứng dụng Android. Khi đưa tính năng hỗ trợ nhiều camera vào khung, các phương pháp hay nhất của Android đặc biệt khuyến nghị nhà sản xuất điện thoại nên cung cấp một camera logic cho tất cả camera vật lý hướng về cùng một phía. Sau đây là những gì nhà phát triển bên thứ ba sẽ thấy trên các thiết bị chạy Android 9 trở lên:
Những gì máy ảnh logic cung cấp hoàn toàn phụ thuộc vào cách triển khai OEM của HAL máy ảnh. Ví dụ: một thiết bị như Pixel 3 triển khai camera logic theo cách chọn một trong các camera vật lý dựa trên tiêu cự và vùng cắt được yêu cầu.
API nhiều camera
API mới bổ sung các hằng số, lớp và phương thức mới sau đây:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationvàSessionConfiguration
Do những thay đổi đối với Tài liệu định nghĩa về khả năng tương thích (CDD) của Android, API nhiều camera cũng đi kèm với một số kỳ vọng nhất định từ nhà phát triển. Thiết bị có camera kép đã tồn tại trước Android 9, nhưng việc mở nhiều camera cùng lúc liên quan đến việc thử và sai. Trên Android 9 trở lên, tính năng nhiều camera cung cấp một bộ quy tắc để chỉ định thời điểm có thể mở một cặp camera thực thuộc cùng một camera logic.
Trong hầu hết trường hợp, các thiết bị chạy Android 9 trở lên đều hiển thị tất cả camera vật lý (ngoại trừ các loại cảm biến ít phổ biến hơn như hồng ngoại) cùng với một camera logic dễ sử dụng hơn. Đối với mọi tổ hợp luồng được đảm bảo hoạt động, một luồng thuộc camera logic có thể được thay thế bằng hai luồng từ camera vật lý cơ bản.
Nhiều luồng đồng thời
Sử dụng đồng thời nhiều luồng video của camera bao gồm các quy tắc sử dụng đồng thời nhiều luồng trong một camera. Có một điểm bổ sung đáng chú ý là các quy tắc tương tự cũng áp dụng cho nhiều camera.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA giải thích cách thay thế một luồng YUV_420_888 logic hoặc luồng thô bằng hai luồng vật lý. Tức là mỗi luồng thuộc loại YUV hoặc RAW có thể được thay thế bằng hai luồng có cùng loại và kích thước. Bạn có thể bắt đầu bằng một luồng video của camera có cấu hình được đảm bảo sau đây cho các thiết bị có một camera:
- Luồng 1: Loại YUV, kích thước
MAXIMUMtừ camera logicid = 0
Sau đó, một thiết bị hỗ trợ nhiều camera sẽ cho phép bạn tạo một phiên thay thế luồng YUV logic đó bằng hai luồng vật lý:
- Luồng 1: Loại YUV, kích thước
MAXIMUMtừ camera vật lýid = 1 - Luồng 2: Loại YUV, kích thước
MAXIMUMtừ camera thực tếid = 2
Bạn có thể thay thế một luồng YUV hoặc RAW bằng hai luồng tương đương nếu và chỉ khi hai camera đó thuộc một nhóm camera logic, được liệt kê trong CameraCharacteristics.getPhysicalCameraIds().
Các đảm bảo do khung cung cấp chỉ là mức tối thiểu cần thiết để lấy khung hình từ nhiều camera vật lý cùng một lúc. Hầu hết các thiết bị đều hỗ trợ các luồng bổ sung, đôi khi còn cho phép mở nhiều thiết bị camera vật lý một cách độc lập. Vì đây không phải là một sự đảm bảo chắc chắn từ khung, nên việc này đòi hỏi bạn phải thực hiện kiểm thử và điều chỉnh theo từng thiết bị bằng cách thử và sai.
Tạo phiên có nhiều camera thực tế
Khi sử dụng máy ảnh vật lý trên thiết bị hỗ trợ nhiều máy ảnh, hãy mở một CameraDevice (máy ảnh logic) và tương tác với máy ảnh đó trong một phiên duy nhất. Tạo một phiên duy nhất bằng API CameraDevice.createCaptureSession(SessionConfiguration config), được thêm vào cấp độ API 28. Cấu hình phiên có một số cấu hình đầu ra, mỗi cấu hình có một tập hợp các mục tiêu đầu ra và có thể có một mã nhận dạng máy ảnh thực đã chọn.
Yêu cầu chụp có một mục tiêu đầu ra được liên kết với chúng. Khung này xác định máy ảnh vật lý (hoặc logic) nào mà các yêu cầu được gửi đến dựa trên mục tiêu đầu ra được đính kèm. Nếu mục tiêu đầu ra tương ứng với một trong các mục tiêu đầu ra được gửi dưới dạng cấu hình đầu ra cùng với mã camera thực, thì camera thực đó sẽ nhận và xử lý yêu cầu.
Sử dụng một cặp camera vật lý
Một bổ sung khác cho API máy ảnh cho nhiều máy ảnh là khả năng xác định máy ảnh logic và tìm máy ảnh vật lý phía sau chúng. Bạn có thể xác định một hàm để giúp xác định các cặp máy ảnh thực tiềm năng mà bạn có thể dùng để thay thế một trong các luồng máy ảnh logic:
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;
}
Việc xử lý trạng thái của camera vật lý do camera logic kiểm soát. Để mở "camera kép", hãy mở camera logic tương ứng với camera vật lý:
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);
}
});
}
Ngoài việc chọn camera để mở, quy trình này cũng giống như việc mở camera trong các phiên bản Android trước đây. Việc tạo phiên chụp bằng API cấu hình phiên mới sẽ yêu cầu khung liên kết một số mục tiêu nhất định với mã camera thực tế cụ thể:
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));
}
Hãy xem createCaptureSession để biết thông tin về tổ hợp luồng được hỗ trợ. Việc kết hợp các luồng là dành cho nhiều luồng trên một camera logic. Khả năng tương thích mở rộng đến việc sử dụng cùng một cấu hình và thay thế một trong các luồng đó bằng hai luồng từ hai camera vật lý thuộc cùng một camera logic.
Khi phiên máy ảnh đã sẵn sàng, hãy gửi yêu cầu chụp đã chọn. Mỗi mục tiêu của yêu cầu chụp sẽ nhận dữ liệu từ camera thực tế được liên kết (nếu có) hoặc quay lại camera logic.
Trường hợp sử dụng mẫu của Zoom
Bạn có thể hợp nhất các camera vật lý thành một luồng duy nhất để người dùng có thể chuyển đổi giữa các camera vật lý khác nhau nhằm trải nghiệm một trường nhìn khác, giúp chụp ảnh hiệu quả ở một "mức thu phóng" khác.
Bắt đầu bằng cách chọn cặp camera vật lý để cho phép người dùng chuyển đổi giữa các camera. Để đạt hiệu quả tối đa, bạn có thể chọn cặp camera cung cấp tiêu cự tối thiểu và tối đa hiện có.
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;
}
Cấu trúc hợp lý cho trường hợp này là có hai SurfaceViews, mỗi luồng một trình phát. Các SurfaceViews này được
hoán đổi dựa trên hoạt động tương tác của người dùng để chỉ có một hiển thị tại một thời điểm nhất định.
Mã sau đây cho biết cách mở camera logic, định cấu hình đầu ra của camera, tạo phiên camera và bắt đầu hai luồng xem trước:
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();
}
});
Tất cả những gì còn lại cần làm là cung cấp giao diện người dùng để người dùng chuyển đổi giữa hai nền tảng, chẳng hạn như một nút hoặc nhấn đúp vào SurfaceView. Bạn thậm chí có thể thực hiện một số hình thức phân tích cảnh và tự động chuyển đổi giữa hai luồng.
Độ méo ống kính
Tất cả ống kính đều tạo ra một mức độ méo nhất định. Trong Android, bạn có thể truy vấn độ méo do ống kính tạo ra bằng cách sử dụng CameraCharacteristics.LENS_DISTORTION, thay thế cho CameraCharacteristics.LENS_RADIAL_DISTORTION hiện không còn được dùng nữa. Đối với camera logic, độ méo là tối thiểu và ứng dụng của bạn có thể sử dụng các khung hình ít nhiều khi chúng đến từ camera. Đối với máy ảnh vật lý, có thể có các cấu hình ống kính rất khác nhau, đặc biệt là trên ống kính rộng.
Một số thiết bị có thể triển khai tính năng tự động sửa méo hình bằng cách sử dụng CaptureRequest.DISTORTION_CORRECTION_MODE.
Tính năng sửa méo hình được bật theo mặc định trên hầu hết các thiết bị.
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(), ...);
Việc đặt yêu cầu chụp ở chế độ này có thể ảnh hưởng đến tốc độ khung hình mà máy ảnh có thể tạo ra. Bạn có thể chọn chỉ đặt tính năng sửa méo hình cho ảnh tĩnh.