Uwaga: ta strona dotyczy pakietu Camera2. Jeśli Twoja aplikacja nie wymaga określonych funkcji niskiego poziomu z Camera2, zalecamy używanie CameraX. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.
Funkcja wielu aparatów została wprowadzona w Androidzie 9 (poziom interfejsu API 28). Od czasu jego wydania na rynku pojawiły się urządzenia obsługujące ten interfejs API. Wiele przypadków użycia wielu kamer jest ściśle powiązanych z konkretną konfiguracją sprzętową. Innymi słowy, nie wszystkie przypadki użycia są zgodne z każdym urządzeniem, co sprawia, że funkcje korzystające z wielu aparatów są dobrym kandydatem do Play Feature Delivery.
Oto kilka typowych przypadków użycia:
- Powiększenie: przełączanie się między aparatami w zależności od przyciętego obszaru lub wybranej ogniskowej.
- Głębia: używanie wielu aparatów do tworzenia mapy głębi.
- Efekt bokeh: wykorzystanie informacji o głębi do symulowania wąskiej głębi ostrości, podobnej do tej uzyskiwanej za pomocą lustrzanki cyfrowej.
Różnica między aparatami logicznymi i fizycznymi
Aby zrozumieć interfejs API wielu kamer, musisz poznać różnicę między kamerami logicznymi i fizycznymi. Dla przykładu weźmy urządzenie z 3 tylnymi aparatami. W tym przykładzie każdy z 3 tylnych aparatów jest traktowany jako aparat fizyczny. Aparat logiczny to grupa co najmniej 2 aparatów fizycznych. Dane wyjściowe kamery logicznej mogą być strumieniem pochodzącym z jednej z podstawowych kamer fizycznych lub połączonym strumieniem pochodzącym z więcej niż jednej podstawowej kamery fizycznej jednocześnie. W obu przypadkach strumień jest obsługiwany przez warstwę abstrakcji sprzętowej (HAL) aparatu.
Wielu producentów telefonów tworzy własne aplikacje do obsługi aparatu, które zwykle są fabrycznie zainstalowane na urządzeniach. Aby korzystać ze wszystkich możliwości sprzętu, mogą one używać prywatnych lub ukrytych interfejsów API albo korzystać ze specjalnego traktowania przez implementację sterownika, do którego inne aplikacje nie mają dostępu. Niektóre urządzenia implementują koncepcję kamer logicznych, udostępniając połączony strumień klatek z różnych kamer fizycznych, ale tylko niektórym uprzywilejowanym aplikacjom. Często tylko jeden z aparatów fizycznych jest widoczny dla platformy. Sytuację deweloperów zewnętrznych przed Androidem 9 ilustruje ten diagram:
Od Androida 9 prywatne interfejsy API nie są już dozwolone w aplikacjach na Androida. W związku z wprowadzeniem obsługi wielu aparatów w ramach sprawdzonych metod dotyczących Androida zdecydowanie zalecamy producentom telefonów udostępnianie aparatu logicznego dla wszystkich aparatów fizycznych skierowanych w tę samą stronę. Oto czego deweloperzy zewnętrzni powinni się spodziewać na urządzeniach z Androidem 9 lub nowszym:
To, co zapewnia aparat logiczny, zależy całkowicie od implementacji komponentu HAL aparatu przez OEM. Na przykład urządzenie takie jak Pixel 3 implementuje aparat logiczny w taki sposób, że wybiera jeden z aparatów fizycznych na podstawie żądanej ogniskowej i obszaru przycięcia.
Interfejs API wielu aparatów
Nowy interfejs API dodaje te nowe stałe, klasy i metody:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationiSessionConfiguration
Ze względu na zmiany w dokumencie definicji zgodności (CDD) Androida interfejs API wielu aparatów wiąże się też z pewnymi oczekiwaniami wobec deweloperów. Urządzenia z 2 aparatami istniały już przed Androidem 9, ale otwieranie więcej niż 1 aparatu jednocześnie wymagało prób i błędów. W Androidzie 9 i nowszych funkcja wielu aparatów zawiera zestaw reguł określających, kiedy można otworzyć parę aparatów fizycznych, które są częścią tego samego aparatu logicznego.
W większości przypadków urządzenia z Androidem 9 lub nowszym udostępniają wszystkie aparaty fizyczne (z wyjątkiem mniej popularnych typów czujników, takich jak podczerwień) oraz łatwiejszy w użyciu aparat logiczny. W przypadku każdej kombinacji strumieni, które na pewno będą działać, jeden strumień należący do kamery logicznej można zastąpić 2 strumieniami z podstawowych kamer fizycznych.
Wiele transmisji jednocześnie
Korzystanie z wielu strumieni z kamery jednocześnie opisuje zasady korzystania z wielu strumieni jednocześnie w przypadku jednej kamery. Z jednym istotnym wyjątkiem te same zasady obowiązują w przypadku wielu kamer.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA wyjaśnia, jak zastąpić logiczny strumień YUV_420_888 lub RAW 2 strumieniami fizycznymi. Oznacza to, że każdy strumień typu YUV lub RAW można zastąpić 2 strumieniami tego samego typu i rozmiaru. Możesz zacząć od strumienia z kamery o następującej gwarantowanej konfiguracji w przypadku urządzeń z jednym aparatem:
- Strumień 1: typ YUV, rozmiar
MAXIMUMz kamery logicznejid = 0
Następnie urządzenie z obsługą wielu aparatów umożliwia utworzenie sesji, w której logiczny strumień YUV jest zastępowany 2 strumieniami fizycznymi:
- Strumień 1: typ YUV, rozmiar
MAXIMUMz kamery fizycznejid = 1 - Strumień 2: typ YUV, rozmiar
MAXIMUMz fizycznego aparatuid = 2
Strumień YUV lub RAW możesz zastąpić 2 równoważnymi strumieniami tylko wtedy, gdy te 2 kamery należą do logicznej grupy kamer, która jest wymieniona w sekcji CameraCharacteristics.getPhysicalCameraIds().
Gwarancje zapewniane przez platformę to tylko absolutne minimum wymagane do jednoczesnego pobierania klatek z więcej niż 1 kamery fizycznej. Większość urządzeń obsługuje dodatkowe strumienie, a niektóre umożliwiają nawet niezależne otwieranie wielu fizycznych aparatów. Ponieważ nie jest to twarda gwarancja ze strony platformy, wymaga to testowania i dostrajania na każdym urządzeniu metodą prób i błędów.
Tworzenie sesji z wieloma kamerami fizycznymi
Jeśli używasz fizycznych aparatów na urządzeniu z kilkoma aparatami, otwórz 1 CameraDevice (aparat logiczny) i korzystaj z niego w ramach 1 sesji. Utwórz pojedynczą sesję za pomocą interfejsu API CameraDevice.createCaptureSession(SessionConfiguration config), który został dodany na poziomie interfejsu API 28. Konfiguracja sesji ma kilka konfiguracji wyjściowych, z których każda ma zestaw celów wyjściowych i opcjonalnie wybrany identyfikator aparatu fizycznego.
Żądania przechwytywania mają powiązany z nimi cel wyjściowy. Platforma określa, do którego aparatu fizycznego (lub logicznego) są wysyłane żądania, na podstawie dołączonego celu wyjściowego. Jeśli cel wyjściowy odpowiada jednemu z celów wyjściowych, które zostały wysłane jako konfiguracja wyjściowa wraz z identyfikatorem aparatu fizycznego, ten aparat fizyczny odbiera i przetwarza żądanie.
Używanie pary fizycznych aparatów
Kolejnym dodatkiem do interfejsów API aparatu w przypadku wielu aparatów jest możliwość identyfikowania aparatów logicznych i znajdowania powiązanych z nimi aparatów fizycznych. Możesz zdefiniować funkcję, która pomoże zidentyfikować potencjalne pary kamer fizycznych, których możesz użyć do zastąpienia jednego ze strumieni z kamery logicznej:
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;
}
Obsługa stanu aparatów fizycznych jest kontrolowana przez aparat logiczny. Aby otworzyć „obraz z obu aparatów”, otwórz aparat logiczny odpowiadający aparatom fizycznym:
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);
}
});
}
Oprócz wyboru aparatu, który ma zostać otwarty, proces jest taki sam jak w przypadku otwierania aparatu w poprzednich wersjach Androida. Utworzenie sesji przechwytywania za pomocą nowego interfejsu API konfiguracji sesji powoduje, że platforma powiąże określone cele z konkretnymi identyfikatorami aparatów fizycznych:
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));
}
Informacje o obsługiwanych kombinacjach strumieni znajdziesz w artykule createCaptureSession. Łączenie strumieni dotyczy wielu strumieni
na jednej kamerze logicznej. Zgodność obejmuje używanie tej samej konfiguracji i zastąpienie jednego z tych strumieni 2 strumieniami z 2 aparatów fizycznych, które są częścią tego samego aparatu logicznego.
Gdy sesja aparatu będzie gotowa, wyślij wybrane żądania przechwytywania. Każdy cel żądania przechwytywania otrzymuje dane z powiązanego aparatu fizycznego, jeśli jest on używany, lub z aparatu logicznego.
Przykładowy przypadek użycia funkcji powiększenia
Można połączyć fizyczne kamery w jedną transmisję, aby użytkownicy mogli przełączać się między różnymi kamerami i uzyskiwać różne pola widzenia, co pozwala na efektywne rejestrowanie różnych „poziomów powiększenia”.
Zacznij od wybrania pary fizycznych aparatów, między którymi użytkownicy będą mogli się przełączać. Aby uzyskać maksymalny efekt, możesz wybrać parę aparatów, które zapewniają minimalną i maksymalną dostępną ogniskową.
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;
}
Rozsądna architektura w tym przypadku obejmowałaby 2 SurfaceViews – po jednym dla każdego strumienia. Te SurfaceViews są zamieniane na podstawie interakcji użytkownika, dzięki czemu w danym momencie widoczny jest tylko jeden z nich.
Poniższy kod pokazuje, jak otworzyć kamerę logiczną, skonfigurować wyjścia kamery, utworzyć sesję kamery i uruchomić 2 strumienie podglądu:
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();
}
});
Pozostaje tylko udostępnić użytkownikowi interfejs, który pozwoli mu przełączać się między tymi 2 powierzchniami, np. przycisk lub dwukrotne kliknięcie SurfaceView. Możesz nawet przeprowadzić analizę sceny i automatycznie przełączać się między tymi 2 strumieniami.
Zniekształcenie obiektywu
Wszystkie obiektywy powodują pewne zniekształcenia. W Androidzie możesz sprawdzić zniekształcenia powodowane przez obiektywy, używając CameraCharacteristics.LENS_DISTORTION, które zastępuje wycofane już CameraCharacteristics.LENS_RADIAL_DISTORTION. W przypadku kamer logicznych zniekształcenia są minimalne, a aplikacja może używać klatek mniej więcej w takiej postaci, w jakiej pochodzą z kamery. W przypadku fizycznych aparatów konfiguracje obiektywów mogą się znacznie różnić, zwłaszcza w przypadku obiektywów szerokokątnych.
Niektóre urządzenia mogą stosować automatyczną korektę zniekształceń za pomocą funkcji CaptureRequest.DISTORTION_CORRECTION_MODE.
Korekta zniekształceń jest domyślnie włączona na większości urządzeń.
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(), ...);
Ustawienie żądania przechwytywania w tym trybie może wpłynąć na liczbę klatek, które może wygenerować aparat. Możesz ustawić korektę zniekształceń tylko w przypadku robienia zdjęć.