Każdy przypadek użycia CameraX konfigurujesz tak, aby kontrolować różne aspekty jego działania.
Na przykład w przypadku robienia zdjęć możesz ustawić docelowy format obrazu i tryb lampy błyskowej. Oto przykład:
Kotlin
val imageCapture = ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build()
Java
ImageCapture imageCapture = new ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build();
Oprócz opcji konfiguracji niektóre przypadki użycia udostępniają interfejsy API, które umożliwiają dynamiczną zmianę ustawień po utworzeniu przypadku użycia. Informacje o konfiguracji dotyczącej poszczególnych przypadków użycia znajdziesz w artykułach Implementowanie podglądu, Analizowanie obrazów i Robienie zdjęć.
CameraXConfig
Dla uproszczenia CameraX ma domyślne konfiguracje, takie jak wewnętrzne moduły wykonawcze i moduły obsługi, które są odpowiednie w przypadku większości scenariuszy użycia. Jeśli jednak Twoja aplikacja ma specjalne wymagania lub chcesz dostosować te konfiguracje, możesz użyć interfejsu CameraXConfig.
Dzięki CameraXConfig aplikacja może:
- Zoptymalizuj opóźnienie uruchamiania za pomocą
setAvailableCameraLimiter(). - Przekaż obiekt wykonujący aplikacji do CameraX za pomocą
setCameraExecutor(). - Zastąp domyślny moduł obsługi harmonogramu modułem
setSchedulerHandler(). - Zmień poziom logowania za pomocą
setMinimumLoggingLevel().
Model wykorzystania
Poniżej opisujemy, jak korzystać z CameraXConfig:
- Utwórz obiekt
CameraXConfigze spersonalizowanymi konfiguracjami. - Zaimplementuj interfejs
CameraXConfig.Providerw klasieApplicationi zwróć obiektCameraXConfigw metodziegetCameraXConfig(). - Dodaj klasę
Applicationdo plikuAndroidManifest.xml, jak opisano tutaj.
Na przykład ten przykładowy kod ogranicza rejestrowanie CameraX tylko do komunikatów o błędach:
Kotlin
class CameraApplication : Application(), CameraXConfig.Provider { override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setMinimumLoggingLevel(Log.ERROR).build() } }
Jeśli aplikacja musi znać konfigurację CameraX po jej ustawieniu, zachowaj lokalną kopię obiektu CameraXConfig.
Ogranicznik aparatu
Podczas pierwszego wywołania ProcessCameraProvider.getInstance() CameraX wylicza i sprawdza właściwości aparatów dostępnych na urządzeniu. Ponieważ CameraX musi komunikować się z komponentami sprzętowymi, ten proces może zająć sporo czasu w przypadku każdego aparatu, zwłaszcza na urządzeniach z niższej półki. Jeśli aplikacja używa tylko określonych aparatów na urządzeniu, np. domyślnego przedniego aparatu, możesz ustawić CameraX tak, aby ignorował inne aparaty. Może to skrócić czas oczekiwania na uruchomienie aparatów, których używa aplikacja.
Jeśli CameraSelector przekazany do CameraXConfig.Builder.setAvailableCamerasLimiter() odfiltruje kamerę, CameraX zachowuje się tak, jakby ta kamera nie istniała. Na przykład ten kod ogranicza aplikację do korzystania tylko z domyślnego tylnego aparatu urządzenia:
Kotlin
class MainApplication : Application(), CameraXConfig.Provider { override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) .build() } }
Wątki
Wiele interfejsów API platformy, na których opiera się CameraX, wymaga blokowania komunikacji między procesami (IPC) ze sprzętem, co może czasami zająć setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko z wątków w tle, dzięki czemu wątek główny nie jest blokowany, a interfejs pozostaje płynny. CameraX zarządza tymi wątkami w tle wewnętrznie, dzięki czemu to zachowanie jest przejrzyste. Jednak niektóre aplikacje wymagają ścisłej kontroli wątków. CameraXConfig umożliwia aplikacji ustawianie wątków w tle, które są używane przez CameraXConfig.Builder.setCameraExecutor() i CameraXConfig.Builder.setSchedulerHandler().
Camera Executor
Egzekutor aparatu jest używany we wszystkich wewnętrznych wywołaniach interfejsu API platformy aparatu, a także w wywołaniach zwrotnych z tych interfejsów API. CameraX przydziela wewnętrzny obiekt Executor i zarządza nim, aby wykonywać te zadania.
Jeśli jednak aplikacja wymaga bardziej rygorystycznej kontroli wątków, użyj CameraXConfig.Builder.setCameraExecutor().
Moduł obsługi algorytmu szeregowania
Moduł obsługi harmonogramu służy do planowania zadań wewnętrznych w stałych odstępach czasu, takich jak ponowna próba otwarcia aparatu, gdy jest on niedostępny. Ten moduł obsługi nie wykonuje zadań, tylko wysyła je do modułu wykonawczego kamery. Czasami jest też używany na starszych platformach API, które wymagają Handler do wywołań zwrotnych. W takich przypadkach wywołania zwrotne są nadal wysyłane bezpośrednio do wykonawcy aparatu. CameraX przydziela wewnętrzny obiekt HandlerThread i zarządza nim, aby wykonywać te zadania, ale możesz go zastąpić za pomocą CameraXConfig.Builder.setSchedulerHandler().
Logowanie
Logowanie CameraX umożliwia aplikacjom filtrowanie komunikatów logcat, ponieważ dobrą praktyką jest unikanie komunikatów szczegółowych w kodzie produkcyjnym. CameraX obsługuje 4 poziomy logowania, od najbardziej szczegółowego do najpoważniejszego:
Log.DEBUG(domyślnie)Log.INFOLog.WARNLog.ERROR
Szczegółowe opisy tych poziomów logowania znajdziesz w dokumentacji logów Androida. Aby ustawić odpowiedni poziom logowania dla aplikacji, użyj CameraXConfig.Builder.setMinimumLoggingLevel(int).
Wybór automatyczny
CameraX automatycznie udostępnia funkcje, które są specyficzne dla urządzenia, na którym działa aplikacja. Na przykład CameraX automatycznie określa najlepszą rozdzielczość, jeśli nie określisz rozdzielczości lub jeśli określona przez Ciebie rozdzielczość nie jest obsługiwana. Wszystkim zajmuje się biblioteka, dzięki czemu nie musisz pisać kodu dla konkretnego urządzenia.
Celem CameraX jest pomyślne zainicjowanie sesji aparatu. Oznacza to, że CameraX dostosowuje rozdzielczość i format obrazu do możliwości urządzenia. Może się tak zdarzyć, jeśli:
- Urządzenie nie obsługuje żądanej rozdzielczości.
- Urządzenie ma problemy ze zgodnością, np. starsze urządzenia, które wymagają określonych rozdzielczości do prawidłowego działania.
- Na niektórych urządzeniach pewne formaty są dostępne tylko w określonych formatach obrazu.
- Urządzenie preferuje „najbliższy mod16” w przypadku kodowania JPEG lub wideo. Więcej informacji:
SCALER_STREAM_CONFIGURATION_MAP.
Chociaż CameraX tworzy sesję i nią zarządza, zawsze sprawdzaj w kodzie rozmiary obrazów zwracane w danych wyjściowych przypadku użycia i odpowiednio je dostosowuj.
Obrót
Domyślnie obrót kamery jest ustawiony tak, aby odpowiadał domyślnemu obrotowi wyświetlacza podczas tworzenia przypadku użycia. W tym domyślnym przypadku CameraX generuje dane wyjściowe, dzięki którym aplikacja może dopasować to, co widzisz na podglądzie. Możesz zmienić obrót na wartość niestandardową, aby obsługiwać urządzenia z wieloma wyświetlaczami, przekazując bieżącą orientację wyświetlacza podczas konfigurowania obiektów przypadków użycia lub dynamicznie po ich utworzeniu.
Aplikacja może ustawić docelowy obrót za pomocą ustawień konfiguracji. Następnie może aktualizować ustawienia obracania za pomocą metod z interfejsów API przypadków użycia (takich jak ImageAnalysis.setTargetRotation()), nawet gdy cykl życia jest w stanie działania. Możesz użyć tej funkcji, gdy aplikacja jest zablokowana w trybie portretowym, więc nie następuje ponowna konfiguracja przy obracaniu, ale przypadek użycia zdjęcia lub analizy musi uwzględniać bieżący obrót urządzenia. Na przykład rozpoznawanie obrotu może być potrzebne do prawidłowego ustawienia twarzy na potrzeby wykrywania twarzy lub do ustawienia zdjęć w orientacji poziomej lub pionowej.
Dane dotyczące zrobionych zdjęć mogą być przechowywane bez informacji o obrocie. Dane Exif zawierają informacje o obrocie, dzięki czemu aplikacje galerii mogą wyświetlać obraz we właściwej orientacji po zapisaniu.
Aby wyświetlać dane podglądu we właściwej orientacji, możesz użyć metadanych wyjściowych z Preview.PreviewOutput() do utworzenia przekształceń.
Poniższy przykładowy kod pokazuje, jak ustawić obrót w przypadku zdarzenia orientacji:
Kotlin
override fun onCreate() { val imageCapture = ImageCapture.Builder().build() val orientationEventListener = object : OrientationEventListener(this as Context) { override fun onOrientationChanged(orientation : Int) { // Monitors orientation values to determine the target rotation value val rotation : Int = when (orientation) { in 45..134 -> Surface.ROTATION_270 in 135..224 -> Surface.ROTATION_180 in 225..314 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageCapture.targetRotation = rotation } } orientationEventListener.enable() }
Java
@Override public void onCreate() { ImageCapture imageCapture = new ImageCapture.Builder().build(); OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) { @Override public void onOrientationChanged(int orientation) { int rotation; // Monitors orientation values to determine the target rotation value if (orientation >= 45 && orientation < 135) { rotation = Surface.ROTATION_270; } else if (orientation >= 135 && orientation < 225) { rotation = Surface.ROTATION_180; } else if (orientation >= 225 && orientation < 315) { rotation = Surface.ROTATION_90; } else { rotation = Surface.ROTATION_0; } imageCapture.setTargetRotation(rotation); } }; orientationEventListener.enable(); }
W zależności od ustawionej rotacji każdy przypadek użycia powoduje bezpośrednie obrócenie danych obrazu lub dostarcza metadane rotacji użytkownikom nieobróconych danych obrazu.
- Podgląd: dane wyjściowe metadanych są dostarczane, aby można było określić obrót docelowej rozdzielczości za pomocą
Preview.getTargetRotation(). - ImageAnalysis: dane wyjściowe metadanych są dostarczane, aby współrzędne bufora obrazu były znane względem współrzędnych wyświetlania.
- ImageCapture: metadane Exif obrazu, bufor lub zarówno bufor, jak i metadane są zmieniane, aby uwzględnić ustawienie obrotu. Zmieniona wartość zależy od implementacji HAL.
Prostokąt przycięcia
Domyślnie prostokąt przycinania jest pełnym prostokątem bufora. Możesz go dostosować za pomocą ViewPort i UseCaseGroup. Dzięki grupowaniu przypadków użycia i ustawianiu wizjera CameraX gwarantuje, że prostokąty przycięcia wszystkich przypadków użycia w grupie wskazują ten sam obszar w czujniku aparatu.
Poniższy fragment kodu pokazuje, jak używać tych 2 klas:
Kotlin
val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build() val useCaseGroup = UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageAnalysis) .addUseCase(imageCapture) .setViewPort(viewPort) .build() cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)
Java
ViewPort viewPort = new ViewPort.Builder( new Rational(width, height), getDisplay().getRotation()).build(); UseCaseGroup useCaseGroup = new UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageAnalysis) .addUseCase(imageCapture) .setViewPort(viewPort) .build(); cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);
ViewPort określa prostokąt bufora widoczny dla użytkowników. Następnie CameraX oblicza największy możliwy prostokąt przycięcia na podstawie właściwości wizjera i dołączonych przypadków użycia. Zwykle, aby uzyskać efekt WYSIWYG, możesz skonfigurować widoczny obszar na podstawie przypadku użycia podglądu. Prostym sposobem na uzyskanie widocznego obszaru jest użycie PreviewView.
Poniższe fragmenty kodu pokazują, jak uzyskać obiekt ViewPort:
Kotlin
val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort
Java
ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();
W poprzednim przykładzie to, co aplikacja otrzymuje z ImageAnalysis i ImageCapture, odpowiada temu, co użytkownik widzi w PreviewView, przy założeniu, że typ skali PreviewView jest ustawiony na domyślny, czyli FILL_CENTER. Po zastosowaniu prostokąta przycięcia i obrotu do bufora wyjściowego obraz ze wszystkich przypadków użycia jest taki sam, choć może mieć inną rozdzielczość. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w sekcji Przekształcanie danych wyjściowych.
Wybór kamery
CameraX automatycznie wybiera najlepsze urządzenie z aparatem pod kątem wymagań i przypadków użycia aplikacji. Jeśli chcesz użyć innego urządzenia niż wybrane dla Ciebie, masz kilka opcji:
- Poproś o domyślny aparat przedni za pomocą
CameraSelector.DEFAULT_FRONT_CAMERA. - Poproś o domyślny tylny aparat za pomocą
CameraSelector.DEFAULT_BACK_CAMERA. - Filtruj listę dostępnych urządzeń według
CameraCharacteristicsza pomocąCameraSelector.Builder.addCameraFilter().
Poniższy przykładowy kod pokazuje, jak utworzyć CameraSelector, aby wpłynąć na wybór urządzenia:
Kotlin
fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? { val cam2Infos = provider.availableCameraInfos.map { Camera2CameraInfo.from(it) }.sortedByDescending { // HARDWARE_LEVEL is Int type, with the order of: // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) } return when { cam2Infos.isNotEmpty() -> { CameraSelector.Builder() .addCameraFilter { it.filter { camInfo -> // cam2Infos[0] is either EXTERNAL or best built-in camera val thisCamId = Camera2CameraInfo.from(camInfo).cameraId thisCamId == cam2Infos[0].cameraId } }.build() } else -> null } } // create a CameraSelector for the USB camera (or highest level internal camera) val selector = selectExternalOrBestCamera(processCameraProvider) processCameraProvider.bindToLifecycle(this, selector, preview, analysis)
Wybieranie wielu kamer jednocześnie
Od wersji CameraX 1.3 możesz też wybrać kilka kamer jednocześnie. Możesz na przykład powiązać przedni i tylny aparat, aby robić zdjęcia lub nagrywać filmy z obu perspektyw jednocześnie.
Podczas korzystania z funkcji jednoczesnego nagrywania urządzenie może jednocześnie obsługiwać 2 aparaty z obiektywami skierowanymi w różne strony lub 2 tylne aparaty. Poniższy blok kodu pokazuje, jak ustawić 2 kamery podczas wywoływania bindToLifecycle i jak uzyskać oba obiekty Camera ze zwróconego obiektu ConcurrentCamera.
Kotlin
// Build ConcurrentCameraConfig val primary = ConcurrentCamera.SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ) val secondary = ConcurrentCamera.SingleCameraConfig( secondaryCameraSelector, useCaseGroup, lifecycleOwner ) val concurrentCamera = cameraProvider.bindToLifecycle( listOf(primary, secondary) ) val primaryCamera = concurrentCamera.cameras[0] val secondaryCamera = concurrentCamera.cameras[1]
Java
// Build ConcurrentCameraConfig SingleCameraConfig primary = new SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ); SingleCameraConfig secondary = new SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ); ConcurrentCamera concurrentCamera = mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary)); Camera primaryCamera = concurrentCamera.getCameras().get(0); Camera secondaryCamera = concurrentCamera.getCameras().get(1);
Rozdzielczość aparatu
Możesz pozwolić CameraX na ustawienie rozdzielczości obrazu na podstawie kombinacji możliwości urządzenia, obsługiwanego poziomu sprzętowego, przypadku użycia i podanego formatu obrazu. Możesz też ustawić konkretną rozdzielczość docelową lub konkretny format obrazu w przypadkach użycia, które obsługują tę konfigurację.
Automatyczne rozwiązywanie problemów
CameraX może automatycznie określić najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w pliku cameraProcessProvider.bindToLifecycle(). Jeśli to możliwe, określ wszystkie przypadki użycia, które muszą być uruchamiane jednocześnie w jednej sesji, w jednym wywołaniu bindToLifecycle(). CameraX określa rozdzielczości na podstawie zestawu powiązanych przypadków użycia, biorąc pod uwagę obsługiwany poziom sprzętowy urządzenia i uwzględniając różnice między urządzeniami (gdy urządzenie przekracza lub nie spełnia dostępnych konfiguracji strumienia).
Celem jest umożliwienie uruchamiania aplikacji na wielu różnych urządzeniach przy jednoczesnym zminimalizowaniu ścieżek kodu związanych z konkretnymi urządzeniami.
Domyślny format obrazu w przypadku robienia zdjęć i analizy obrazu to 4:3.
Przypadki użycia mają konfigurowalny format obrazu, dzięki czemu aplikacja może określić pożądany format obrazu na podstawie projektu interfejsu. Dane wyjściowe CameraX są generowane tak, aby jak najlepiej pasowały do żądanych formatów obrazu obsługiwanych przez urządzenie. Jeśli nie ma obsługiwanej rozdzielczości, która dokładnie odpowiada tym wymaganiom, wybierana jest ta, która spełnia najwięcej warunków. W ten sposób aplikacja określa, jak aparat jest wyświetlany w aplikacji, a CameraX określa najlepsze ustawienia rozdzielczości aparatu, aby spełnić te wymagania na różnych urządzeniach.
Aplikacja może na przykład:
- Określanie docelowej rozdzielczości 4:3 lub 16:9 w przypadku konkretnego zastosowania
- Określ niestandardową rozdzielczość, do której CameraX spróbuje znaleźć najbliższe dopasowanie.
- Określ format obrazu przycinania dla:
ImageCapture
CameraX automatycznie wybiera wewnętrzne rozdzielczości powierzchni Camera2. W tabeli poniżej znajdziesz rozwiązania:
| Przypadek użycia | Rozdzielczość powierzchni wewnętrznej | Rozdzielczość danych wyjściowych |
|---|---|---|
| Podgląd | Format obrazu: rozdzielczość, która najlepiej pasuje do celu i ustawienia. | Rozdzielczość powierzchni wewnętrznej. Metadane są dostarczane, aby umożliwić przycinanie, skalowanie i obracanie widoku w celu uzyskania docelowego formatu obrazu. |
| Domyślna rozdzielczość: najwyższa rozdzielczość podglądu lub najwyższa rozdzielczość preferowana przez urządzenie, która odpowiada formatowi obrazu podglądu. | ||
| Maksymalna rozdzielczość: rozmiar podglądu, który odnosi się do najlepszego dopasowania rozmiaru do rozdzielczości ekranu urządzenia lub do 1080p (1920 x 1080) – w zależności od tego, która z tych wartości jest mniejsza. | ||
| Analiza obrazu | Format obrazu: rozdzielczość, która najlepiej dopasowuje cel do ustawienia. | Rozdzielczość powierzchni wewnętrznej. |
| Domyślna rozdzielczość: domyślne ustawienie rozdzielczości docelowej to 640 x 480. Dostosowanie zarówno docelowej rozdzielczości, jak i odpowiedniego formatu obrazu pozwala uzyskać najlepiej obsługiwaną rozdzielczość. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie YUV_420_888, która jest pobierana z StreamConfigurationMap.getOutputSizes().
Domyślna rozdzielczość docelowa to 640 x 480. Jeśli chcesz uzyskać większą rozdzielczość, musisz użyć właściwości setTargetResolution() i setTargetAspectRatio(), aby wybrać najbliższą z obsługiwanych rozdzielczości.
|
||
| Robienie zdjęć | Format obrazu: format obrazu, który najlepiej pasuje do ustawienia. | Rozdzielczość powierzchni wewnętrznej. |
| Domyślna rozdzielczość: najwyższa dostępna rozdzielczość lub najwyższa rozdzielczość preferowana przez urządzenie, która odpowiada formatowi obrazu ImageCapture. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby go pobrać, użyj StreamConfigurationMap.getOutputSizes().
|
Określanie rozdzielczości
Możesz ustawić konkretne rozdzielczości podczas tworzenia przypadków użycia za pomocą metody setTargetResolution(Size resolution), jak pokazano w tym przykładowym kodzie:
Kotlin
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .build()
Java
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .setTargetResolution(new Size(1280, 720)) .build();
Nie możesz ustawić docelowego formatu obrazu i docelowej rozdzielczości w tym samym przypadku użycia. Jeśli to zrobisz, podczas tworzenia obiektu konfiguracji zostanie wygenerowany błąd IllegalArgumentException.
Wyraź rozdzielczość Size w układzie współrzędnych po obróceniu obsługiwanych rozmiarów o docelowy kąt obrotu. Na przykład urządzenie z naturalną orientacją pionową w naturalnym docelowym obrocie, które żąda obrazu pionowego, może określić wymiary 480 x 640, a to samo urządzenie obrócone o 90 stopni i kierowane na orientację poziomą może określić wymiary 640 x 480.
Docelowa rozdzielczość próbuje ustalić minimalną granicę rozdzielczości obrazu. Rzeczywista rozdzielczość obrazu to najbliższa dostępna rozdzielczość, która nie jest mniejsza niż rozdzielczość docelowa, zgodnie z implementacją aparatu.
Jeśli jednak nie ma rozdzielczości równej docelowej lub większej od niej, wybierana jest najbliższa dostępna rozdzielczość mniejsza od docelowej. Rozdzielczości o tym samym formacie obrazu co podany Size mają wyższy priorytet niż rozdzielczości o innych formatach obrazu.
CameraX stosuje najbardziej odpowiednią rozdzielczość na podstawie żądań. Jeśli głównym celem jest zachowanie formatu obrazu, określ tylko setTargetAspectRatio, a CameraX określi odpowiednią rozdzielczość na podstawie urządzenia.
Jeśli główną potrzebą aplikacji jest określenie rozdzielczości w celu zwiększenia wydajności przetwarzania obrazu (np. małego lub średniego obrazu na podstawie możliwości przetwarzania urządzenia), użyj setTargetResolution(Size resolution).
Jeśli aplikacja wymaga dokładnej rozdzielczości, zapoznaj się z tabelą w createCaptureSession(), aby dowiedzieć się, jakie maksymalne rozdzielczości są obsługiwane przez poszczególne poziomy sprzętu. Aby sprawdzić, jakie rozdzielczości są obsługiwane przez bieżące urządzenie, zobacz StreamConfigurationMap.getOutputSizes(int).
Jeśli aplikacja działa na Androidzie 10 lub nowszym, możesz użyć isSessionConfigurationSupported(), aby zweryfikować konkretny SessionConfiguration.
Sterowanie obrazem z kamery
Oprócz możliwości skonfigurowania danych wyjściowych aparatu zgodnie z potrzebami w przypadku każdego przypadku użycia CameraX implementuje też te interfejsy, aby obsługiwać operacje aparatu wspólne dla wszystkich powiązanych przypadków użycia:
CameraControlumożliwia konfigurowanie typowych funkcji aparatu.CameraInfoumożliwia wysyłanie zapytań o stany tych typowych funkcji aparatu.
Oto funkcje aparatu obsługiwane przez CameraControl:
- Zoom
- Pochodnia
- Ostrość i pomiar (dotknij, aby ustawić ostrość)
- Kompensacja ekspozycji
Pobieranie instancji CameraControl i CameraInfo
Pobierz instancje CameraControl i CameraInfo za pomocą obiektu Camera zwróconego przez ProcessCameraProvider.bindToLifecycle().
Oto przykładowy kod:
Kotlin
val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // For performing operations that affect all outputs. val cameraControl = camera.cameraControl // For querying information and states. val cameraInfo = camera.cameraInfo
Java
Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // For performing operations that affect all outputs. CameraControl cameraControl = camera.getCameraControl() // For querying information and states. CameraInfo cameraInfo = camera.getCameraInfo()
Możesz na przykład przesłać operacje powiększenia i inne operacje CameraControl po wywołaniu bindToLifecycle(). Po zatrzymaniu lub zniszczeniu aktywności używanej do powiązania instancji kamery CameraControl nie może już wykonywać operacji i zwraca niepowodzenie ListenableFuture.
Zoom
CameraControl udostępnia 2 metody zmiany poziomu powiększenia:
setZoomRatio()ustawia powiększenie według współczynnika powiększenia.Współczynnik musi mieścić się w zakresie od
CameraInfo.getZoomState().getValue().getMinZoomRatio()doCameraInfo.getZoomState().getValue().getMaxZoomRatio(). W przeciwnym razie funkcja zwraca nieudanyListenableFuture.setLinearZoom()ustawia bieżące powiększenie z wartością powiększenia liniowego z zakresu od 0 do 1,0.Zaletą zoomu liniowego jest to, że pole widzenia (FOV) skaluje się wraz ze zmianami powiększenia. Dzięki temu idealnie nadaje się do użycia z widokiem
Slider.
CameraInfo.getZoomState() zwraca LiveData bieżącego stanu powiększenia. Wartość zmienia się, gdy kamera jest inicjowana lub gdy poziom powiększenia jest ustawiany za pomocą setZoomRatio() lub setLinearZoom(). Wywołanie dowolnej metody ustawia wartości obsługujące ZoomState.getZoomRatio() i ZoomState.getLinearZoom().
Jest to przydatne, jeśli chcesz wyświetlać tekst ze współczynnikiem powiększenia obok suwaka.
Wystarczy, że będziesz przestrzegać ZoomState LiveData, aby zaktualizować oba te elementy bez konieczności konwersji.
Obiekt ListenableFuture zwracany przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień o zakończeniu powtarzającego się żądania z określoną wartością powiększenia. Dodatkowo, jeśli ustawisz nową wartość powiększenia, gdy poprzednia operacja jest nadal wykonywana, ListenableFuture poprzedniej operacji powiększenia natychmiast zakończy się niepowodzeniem.
Pochodnia
CameraControl.enableTorch(boolean)
włącza lub wyłącza latarkę.
CameraInfo.getTorchState()
można użyć do sprawdzenia bieżącego stanu latarki. Możesz sprawdzić wartość zwracaną przez CameraInfo.hasFlashUnit(), aby określić, czy latarka jest dostępna. Jeśli nie, wywołanie CameraControl.enableTorch(boolean) powoduje natychmiastowe zakończenie zwracanego ListenableFuture z wynikiem niepowodzenia i ustawia stan latarki na TorchState.OFF.
Gdy latarka jest włączona, pozostaje włączona podczas robienia zdjęć i nagrywania filmów niezależnie od ustawienia flashMode. flashMode w aplikacji ImageCapture działa tylko wtedy, gdy latarka jest wyłączona.
Ostrość i pomiar
CameraControl.startFocusAndMetering()
wyzwala autofokus i pomiar ekspozycji, ustawiając regiony pomiaru AF/AE/AWB na podstawie podanego działania FocusMeteringAction. Jest to często używane do implementacji funkcji „dotknij, aby ustawić ostrość” w wielu aplikacjach aparatu.
MeteringPoint
Aby rozpocząć, utwórz MeteringPoint za pomocą MeteringPointFactory.createPoint(float x, float y, float
size).
MeteringPoint reprezentuje pojedynczy punkt na kamerze Surface. Jest przechowywana w znormalizowanej formie, dzięki czemu można ją łatwo przekonwertować na współrzędne czujnika, aby określić regiony AF/AE/AWB.
Rozmiar MeteringPoint mieści się w zakresie od 0 do 1, a domyślny rozmiar to 0,15f. Gdy wywołujesz metodę MeteringPointFactory.createPoint(float x, float y, float
size), CameraX tworzy prostokątny obszar wyśrodkowany w punkcie (x, y) dla podanego obiektu size.
Poniższy kod pokazuje, jak utworzyć obiekt MeteringPoint:
Kotlin
// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview. previewView.setOnTouchListener((view, motionEvent) -> { val meteringPoint = previewView.meteringPointFactory .createPoint(motionEvent.x, motionEvent.y) … } // Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for // preview. Please note that if the preview is scaled or cropped in the View, // it’s the application's responsibility to transform the coordinates properly // so that the width and height of this factory represents the full Preview FOV. // And the (x,y) passed to create MeteringPoint might need to be adjusted with // the offsets. val meteringPointFactory = DisplayOrientedMeteringPointFactory( surfaceView.display, camera.cameraInfo, surfaceView.width, surfaceView.height ) // Use SurfaceOrientedMeteringPointFactory if the point is specified in // ImageAnalysis ImageProxy. val meteringPointFactory = SurfaceOrientedMeteringPointFactory( imageWidth, imageHeight, imageAnalysis)
startFocusAndMetering i FocusMeteringAction
Aby wywołać startFocusAndMetering(), aplikacje muszą utworzyć FocusMeteringAction, który składa się z co najmniej 1 MeteringPoints z opcjonalnymi kombinacjami trybu pomiaru z FLAG_AF, FLAG_AE, FLAG_AWB. Ten kod pokazuje, jak to zrobić:
Kotlin
val meteringPoint1 = meteringPointFactory.createPoint(x1, x1) val meteringPoint2 = meteringPointFactory.createPoint(x2, y2) val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB // Optionally add meteringPoint2 for AF/AE. .addPoint(meteringPoint2, FLAG_AF | FLAG_AE) // The action is canceled in 3 seconds (if not set, default is 5s). .setAutoCancelDuration(3, TimeUnit.SECONDS) .build() val result = cameraControl.startFocusAndMetering(action) // Adds listener to the ListenableFuture if you need to know the focusMetering result. result.addListener({ // result.get().isFocusSuccessful returns if the auto focus is successful or not. }, ContextCompat.getMainExecutor(this)
Jak pokazano w kodzie powyżej, startFocusAndMetering() przyjmuje FocusMeteringAction składający się z jednego MeteringPoint dla regionów pomiaru AF/AE/AWB i drugiego MeteringPoint tylko dla AF i AE.
Wewnętrznie CameraX konwertuje go na Camera2 MeteringRectangles i ustawia odpowiednie parametry CONTROL_AF_REGIONS/CONTROL_AE_REGIONS/CONTROL_AWB_REGIONS w żądaniu przechwytywania.
Ponieważ nie każde urządzenie obsługuje AF/AE/AWB i wiele regionów, CameraX wykonuje FocusMeteringAction z najlepszym możliwym rezultatem. CameraX używa maksymalnej liczby obsługiwanych punktów pomiarowych w kolejności, w jakiej zostały dodane. Wszystkie punkty pomiarowe dodane po przekroczeniu maksymalnej liczby są ignorowane. Jeśli na przykład FocusMeteringAction zawiera 3 punkty pomiarowe na platformie obsługującej tylko 2, używane są tylko pierwsze 2 punkty pomiarowe. Końcowy MeteringPoint jest ignorowany przez CameraX.
Kompensacja ekspozycji
Kompensacja ekspozycji jest przydatna, gdy aplikacje muszą dostosować wartości ekspozycji (EV) poza wynik automatycznej ekspozycji (AE). Wartości kompensacji ekspozycji są łączone w następujący sposób, aby określić ekspozycję niezbędną w bieżących warunkach oświetleniowych:
Exposure = ExposureCompensationIndex * ExposureCompensationStep
CameraX udostępnia funkcję Camera.CameraControl.setExposureCompensationIndex() do ustawiania kompensacji ekspozycji jako wartości indeksu.
Dodatnie wartości indeksu rozjaśniają obraz, a ujemne go przyciemniają. Aplikacje mogą wysyłać zapytania o obsługiwany zakres za pomocą metody CameraInfo.ExposureState.exposureCompensationRange() opisanej w następnej sekcji. Jeśli wartość jest obsługiwana, zwracany obiekt ListenableFuture kończy działanie, gdy wartość zostanie pomyślnie włączona w żądaniu przechwytywania. Jeśli określony indeks wykracza poza obsługiwany zakres, setExposureCompensationIndex() powoduje, że zwracany obiekt ListenableFuture natychmiast kończy działanie z wynikiem niepowodzenia.
CameraX zachowuje tylko ostatnie żądanie setExposureCompensationIndex(), a wielokrotne wywołanie funkcji przed wykonaniem poprzedniego żądania powoduje jego anulowanie.
Poniższy fragment kodu ustawia indeks kompensacji ekspozycji i rejestruje wywołanie zwrotne, gdy żądanie zmiany ekspozycji zostanie wykonane:
Kotlin
camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex) .addListener({ // Get the current exposure compensation index, it might be // different from the asked value in case this request was // canceled by a newer setting request. val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex … }, mainExecutor)
Camera.CameraInfo.getExposureState()pobiera bieżąceExposureState, w tym:- Obsługa kontroli kompensacji ekspozycji.
- Bieżący indeks kompensacji ekspozycji.
- Zakres indeksu kompensacji ekspozycji.
- Krok kompensacji ekspozycji używany do obliczania wartości kompensacji ekspozycji.
Na przykład ten kod inicjuje ustawienia ekspozycji SeekBar z bieżącymi wartościami ExposureState:
Kotlin
val exposureState = camera.cameraInfo.exposureState binding.seekBar.apply { isEnabled = exposureState.isExposureCompensationSupported max = exposureState.exposureCompensationRange.upper min = exposureState.exposureCompensationRange.lower progress = exposureState.exposureCompensationIndex }
Dodatkowe materiały
Więcej informacji o CameraX znajdziesz w tych materiałach.
Ćwiczenie w Codelabs
Przykładowy kod
Społeczność deweloperów
Grupa dyskusyjna dotycząca Android CameraX