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 współczynnik proporcji i tryb lampy błyskowej. Poniższy kod pokazuje jeden z przykładów:
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, która jest specyficzna dla poszczególnych przypadków użycia, znajdziesz w sekcjach Wdrażanie podglądu, Analizowanie obrazów i Przechwytywanie obrazów.
CameraXConfig
Aby uprościć obsługę, CameraX ma domyślne konfiguracje, takie jak wewnętrzne moduły wykonawcze i moduły obsługi, które są odpowiednie w większości scenariuszy użycia. Jeśli jednak Twoja aplikacja ma specjalne wymagania lub woli dostosować te konfiguracje, możesz użyć interfejsu CameraXConfig
.
Dzięki CameraXConfig
aplikacja może:
- Optymalizacja opóźnienia uruchomienia za pomocą
setAvailableCameraLimiter()
. - Przekaż wykonawcę aplikacji do CameraX za pomocą
setCameraExecutor()
. - Zastąp domyślny moduł obsługi harmonogramu metodą
setSchedulerHandler()
. - Zmień poziom rejestrowania za pomocą polecenia
setMinimumLoggingLevel()
.
Model wykorzystania
Poniżej znajdziesz opis korzystania z CameraXConfig
:
- Utwórz obiekt
CameraXConfig
ze skonfigurowanymi przez siebie ustawieniami. - Zaimplementuj interfejs
CameraXConfig.Provider
wApplication
i zwróć obiektCameraXConfig
wgetCameraXConfig()
. - Dodaj zajęcia
Application
do plikuAndroidManifest.xml
zgodnie z opisem tutaj.
Na przykład poniższy przykładowy kod ogranicza rejestrowanie w 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() } }
Zachowaj lokalną kopię obiektu CameraXConfig
, jeśli aplikacja musi znać konfigurację CameraX po jej ustawieniu.
Ogranicznik kamery
Podczas pierwszego wywołania funkcji
ProcessCameraProvider.getInstance()
CameraX wylicza i sprawdza charakterystykę kamer 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 skonfigurować CameraX tak, aby ignorował inne aparaty. Może to skrócić czas oczekiwania na uruchomienie aparatów używanych przez aplikację.
Jeśli wartość CameraSelector
przekazana do CameraXConfig.Builder.setAvailableCamerasLimiter()
odfiltruje kamerę, CameraX będzie się zachowywać tak, jakby ta kamera nie istniała. Na przykład poniższy 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ędzyprocesowej (IPC) ze sprzętem, co czasami może trwać setki milisekund. Dlatego CameraX wywołuje te interfejsy API tylko z wątków w tle, aby wątek główny nie był blokowany, a interfejs użytkownika działał płynnie. CameraX wewnętrznie zarządza tymi wątkami w tle, dzięki czemu to zachowanie jest niewidoczne. Niektóre aplikacje wymagają jednak ś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()
.
Wykonawca aparatu
Wykonawca kamery jest używany do wszystkich wewnętrznych wywołań interfejsu API platformy Camera, a także do wywołań zwrotnych z tych interfejsów API. CameraX przydziela i zarządza wewnętrznym Executor
, aby wykonywać te zadania.
Jeśli jednak aplikacja wymaga ściślejszej kontroli nad wątkami, użyj CameraXConfig.Builder.setCameraExecutor()
.
Moduł obsługi harmonogramu
Procedura obsługi harmonogramu służy do planowania wewnętrznych zadań w stałych odstępach czasu, np. ponawiania próby otwarcia kamery, gdy jest niedostępna. Ten moduł obsługi nie wykonuje zadań, a jedynie przekazuje je do wykonawcy kamery. Jest on też czasami używany na starszych platformach API, które wymagają Handler
w przypadku wywołań zwrotnych. W takich przypadkach wywołania zwrotne są nadal wysyłane bezpośrednio do wykonawcy kamery. CameraX przydziela i zarządza wewnętrznym HandlerThread
do wykonywania tych zadań, ale możesz go zastąpić za pomocą CameraXConfig.Builder.setSchedulerHandler()
.
Logowanie
Logowanie CameraX umożliwia aplikacjom filtrowanie wiadomości logcat, co może być dobrym rozwiązaniem, aby uniknąć szczegółowych wiadomości w kodzie produkcyjnym. CameraX obsługuje 4 poziomy rejestrowania, od najbardziej szczegółowego do najbardziej krytycznego:
Log.DEBUG
(domyślnie)Log.INFO
Log.WARN
Log.ERROR
Szczegółowe opisy tych poziomów logowania znajdziesz w dokumentacji logów Androida. Użyj
CameraXConfig.Builder.setMinimumLoggingLevel(int)
, aby ustawić odpowiedni poziom rejestrowania dla aplikacji.
Automatyczny wybór
CameraX automatycznie udostępnia funkcje specyficzne dla urządzenia, na którym działa aplikacja. Na przykład CameraX automatycznie określa najlepszą rozdzielczość, jeśli nie podasz rozdzielczości lub jeśli podana rozdzielczość jest nieobsługiwana. Wszystko to jest obsługiwane przez bibliotekę, co eliminuje konieczność pisania kodu specyficznego dla 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 to nastąpić z tych powodów:
- Urządzenie nie obsługuje żądanej rozdzielczości.
- Urządzenie ma problemy ze zgodnością, np. starsze urządzenia, które do prawidłowego działania wymagają określonych rozdzielczości.
- Na niektórych urządzeniach niektóre formaty są dostępne tylko przy określonych proporcjach obrazu.
- Urządzenie preferuje „najbliższy mod16” w przypadku kodowania JPEG lub wideo. Więcej informacji znajdziesz w sekcji
SCALER_STREAM_CONFIGURATION_MAP
.
Mimo że CameraX tworzy sesję i nią zarządza, zawsze sprawdzaj w kodzie rozmiary zwracanych obrazów w danych wyjściowych przypadku użycia i odpowiednio je dostosowuj.
Obrót
Domyślnie podczas tworzenia przypadku użycia orientacja kamery jest ustawiana tak, aby odpowiadała orientacji domyślnego wyświetlacza. W tym domyślnym przypadku CameraX generuje dane wyjściowe, aby aplikacja mogła dopasować je do tego, co widzisz w podglądzie. Aby obsługiwać urządzenia z wieloma wyświetlaczami, możesz zmienić rotację na wartość niestandardową, przekazując bieżącą orientację wyświetlacza podczas konfigurowania obiektów przypadków użycia lub dynamicznie po ich utworzeniu.
Aplikacja może ustawić docelową orientację za pomocą ustawień konfiguracji. Może wtedy aktualizować ustawienia rotacji za pomocą metod z interfejsów API przypadków użycia (np. ImageAnalysis.setTargetRotation()
), nawet gdy cykl życia jest w stanie działania. Może to być przydatne, gdy aplikacja jest zablokowana w trybie pionowym, więc po obróceniu urządzenia nie następuje zmiana konfiguracji, ale w przypadku zdjęć lub analizy trzeba uwzględnić bieżące obrót urządzenia. Na przykład świadomość rotacji może być potrzebna, aby twarze były prawidłowo zorientowane do wykrywania twarzy lub aby zdjęcia były ustawione w orientacji poziomej lub pionowej.
Dane dotyczące przechwyconych obrazów mogą być przechowywane bez informacji o obrocie. Dane Exif zawierają informacje o obrocie, dzięki czemu aplikacje galerii mogą wyświetlać obraz w prawidłowej orientacji po zapisaniu.
Aby wyświetlać dane podglądu w prawidłowej orientacji, możesz użyć metadanych wyjściowych z Preview.PreviewOutput()
do tworzenia przekształceń.
Poniższy przykładowy kod pokazuje, jak ustawić rotację w zdarzeniu 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 obraca dane obrazu bezpośrednio lub udostępnia metadane rotacji odbiorcom nieobróconych danych obrazu.
- Podgląd: dane wyjściowe metadanych są podawane w taki sposób, aby można było określić rotację docelowej rozdzielczości za pomocą
Preview.getTargetRotation()
. - ImageAnalysis: dane wyjściowe metadanych są dostarczane, aby współrzędne bufora obrazu były znane w stosunku do współrzędnych wyświetlania.
- ImageCapture: metadane EXIF obrazu, bufor lub zarówno bufor, jak i metadane są zmieniane, aby odnotować ustawienie obrotu. Wartość zmieniona zależy od implementacji HAL.
Prostokąt przycięcia
Domyślnie prostokąt przycięcia jest pełnym prostokątem bufora. Możesz go dostosować za pomocą elementów ViewPort
i UseCaseGroup
. Grupując przypadki użycia i ustawiając obszar wyświetlania, CameraX gwarantuje, że prostokąty przycięcia wszystkich przypadków użycia w grupie wskazują ten sam obszar na matrycy 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 widoczny dla użytkowników prostokąt bufora. Następnie CameraX oblicza największy możliwy prostokąt przycięcia na podstawie właściwości widocznego obszaru i dołączonych przypadków użycia. Aby uzyskać efekt WYSIWYG, możesz zwykle 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 powyższym przykładzie dane, które aplikacja otrzymuje z ImageAnalysis
i ImageCapture
, są zgodne z tym, co widzi użytkownik w PreviewView
, pod warunkiem że typ skali PreviewView
jest ustawiony na domyślny typ FILL_CENTER
. Po zastosowaniu prostokąta przycinania i obrotu do bufora wyjściowego obraz we wszystkich przypadkach użycia jest taki sam, choć może mieć inną rozdzielczość. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w artykule Przekształcanie danych wyjściowych.
Wybór kamery
CameraX automatycznie wybiera najlepsze urządzenie z aparatem do wymagań i przypadków użycia aplikacji. Jeśli chcesz użyć innego urządzenia niż to, które zostało wybrane dla Ciebie, masz kilka opcji:
- Poproś o domyślny przedni aparat za pomocą kodu
CameraSelector.DEFAULT_FRONT_CAMERA
. - Poproś o domyślny tylny aparat za pomocą kodu
CameraSelector.DEFAULT_BACK_CAMERA
. - Przefiltruj listę dostępnych urządzeń według ich
CameraCharacteristics
za 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ż wybierać 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.
Gdy korzystasz z funkcji jednoczesnego używania aparatów, urządzenie może obsługiwać 2 aparaty z obiektywami skierowanymi w różnych kierunkach lub 2 aparaty tylne jednocześnie. Poniższy blok kodu pokazuje, jak ustawić 2 kamery podczas wywoływania funkcji bindToLifecycle
oraz jak uzyskać oba obiekty Camera z 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 zezwolić CameraX na ustawienie rozdzielczości obrazu na podstawie kombinacji możliwości urządzenia, obsługiwanego poziomu sprzętu, przypadku użycia i podanego współczynnika proporcji. Możesz też ustawić konkretną rozdzielczość docelową lub określony współczynnik proporcji w przypadkach użycia, które obsługują taką konfigurację.
Automatyczne rozwiązywanie problemów
CameraX może automatycznie określać najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w cameraProcessProvider.bindToLifecycle()
. Jeśli to możliwe, w jednym wywołaniu bindToLifecycle()
określ wszystkie przypadki użycia, które mają być uruchamiane jednocześnie w ramach jednej sesji. CameraX określa rozdzielczości na podstawie zestawu przypadków użycia powiązanych z obsługiwanym poziomem sprzętowym urządzenia i uwzględniając różnice specyficzne dla urządzenia (gdy urządzenie przekracza lub nie spełnia dostępnych konfiguracji strumienia).
Chodzi o to, aby aplikacja działała na wielu różnych urządzeniach przy jednoczesnym zminimalizowaniu ścieżek kodu specyficznych dla danego urządzenia.
Domyślny format obrazu w przypadku przechwytywania i analizowania obrazów to 4:3.
Przypadki użycia mają konfigurowalne proporcje, dzięki czemu aplikacja może określać pożądane proporcje na podstawie projektu interfejsu. Dane wyjściowe CameraX są generowane tak, aby jak najdokładniej odpowiadały żądanym formatom obrazu, w zakresie obsługiwanym przez urządzenie. Jeśli nie ma rozdzielczości o dokładnym dopasowaniu, wybierana jest ta, która spełnia najwięcej warunków. Aplikacja określa więc, jak aparat ma wyglądać w aplikacji, a CameraX ustala 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 danego zastosowania
- określić niestandardową rozdzielczość, do której CameraX próbuje znaleźć najbliższe dopasowanie;
- Określ format 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 ustawienia docelowego. | Rozdzielczość powierzchni wewnętrznej. Metadane są udostępniane, aby umożliwić przycięcie, skalowanie i obracanie widoku do docelowego współczynnika proporcji. |
Domyślna rozdzielczość: najwyższa rozdzielczość podglądu lub najwyższa rozdzielczość preferowana przez urządzenie, która pasuje do proporcji podglądu. | ||
Maksymalna rozdzielczość: rozmiar podglądu, czyli najlepszy rozmiar dopasowany do rozdzielczości ekranu urządzenia lub do 1080p (1920 x 1080), w zależności od tego, który z nich jest mniejszy. | ||
Analiza obrazu | Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. | Rozdzielczość powierzchni wewnętrznej. |
Domyślna rozdzielczość: domyślne ustawienie rozdzielczości docelowej to 640x480. Dostosowanie zarówno rozdzielczości docelowej, jak i odpowiedniego formatu obrazu spowoduje uzyskanie najlepiej obsługiwanej rozdzielczości. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa urządzenia z aparatem w formacie YUV_420_888, która jest pobierana z StreamConfigurationMap.getOutputSizes() .
Rozdzielczość docelowa jest domyślnie ustawiona na 640 x 480, więc jeśli chcesz uzyskać rozdzielczość większą niż 640 x 480, musisz użyć
setTargetResolution()
i
setTargetAspectRatio()
, aby uzyskać najbliższą obsługiwaną rozdzielczość.
|
||
Przechwytywanie obrazu | Współczynnik proporcji: współczynnik proporcji, 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 pasuje do współczynnika proporcji ImageCapture. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby to zrobić, użyj
StreamConfigurationMap.getOutputSizes() .
|
Określanie rozdzielczości
Podczas tworzenia przypadków użycia możesz ustawić konkretne rozdzielczości 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ć zarówno docelowego współczynnika proporcji, jak i docelowej rozdzielczości w tym samym przypadku użycia. Podczas tworzenia obiektu konfiguracji spowoduje to zgłoszenie wyjątku 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 orientacją pionową w naturalnym ustawieniu, które żąda obrazu w orientacji pionowej, może określić rozmiar 480 x 640. To samo urządzenie obrócone o 90 stopni i kierowane na orientację poziomą może określić rozmiar 640 x 480.
Rozdzielczość docelowa określa minimalną rozdzielczość obrazu. Rzeczywista rozdzielczość obrazu to najbliższa dostępna rozdzielczość, która nie jest mniejsza od rozdzielczości docelowej, zgodnie z implementacją aparatu.
Jeśli jednak nie ma rozdzielczości równej lub większej od docelowej, wybierana jest najbliższa dostępna rozdzielczość mniejsza od docelowej. Rozdzielczości o tym samym formacie obrazu co podany plik 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 spełnienie wymagań dotyczących współczynnika proporcji, podaj tylko setTargetAspectRatio
, a CameraX określi odpowiednią rozdzielczość na podstawie urządzenia.
Jeśli głównym celem aplikacji jest określenie rozdzielczości, aby zwiększyć wydajność przetwarzania obrazu (np. małego lub średniego obrazu w zależności od możliwości przetwarzania urządzenia), użyj setTargetResolution(Size resolution)
.
Jeśli Twoja aplikacja wymaga dokładnej rozdzielczości, zapoznaj się z tabelą w createCaptureSession()
, aby dowiedzieć się, jakie maksymalne rozdzielczości są obsługiwane na poszczególnych poziomach sprzętu. Aby sprawdzić, jakie rozdzielczości są obsługiwane przez bieżące urządzenie, kliknij StreamConfigurationMap.getOutputSizes(int)
.
Jeśli Twoja aplikacja działa na Androidzie 10 lub nowszym, możesz użyć
isSessionConfigurationSupported()
do weryfikacji konkretnego SessionConfiguration
.
Sterowanie wyjściem kamery
Oprócz umożliwienia skonfigurowania wyjścia z aparatu zgodnie z potrzebami w każdym indywidualnym przypadku użycia CameraX implementuje też te interfejsy, aby obsługiwać operacje aparatu wspólne dla wszystkich powiązanych przypadków użycia:
CameraControl
umożliwia konfigurowanie typowych funkcji aparatu.CameraInfo
umożliwia sprawdzanie stanów tych popularnych funkcji aparatu.
Oto obsługiwane funkcje aparatu w przypadku CameraControl:
- Zoom
- Latarka
- Ostrość i pomiar światła (dotknij, aby ustawić ostrość)
- Kompensacja ekspozycji
Uzyskiwanie instancji CameraControl i CameraInfo
Pobierz instancje CameraControl
i CameraInfo
za pomocą obiektu Camera
zwróconego przez ProcessCameraProvider.bindToLifecycle()
.
Poniższy kod zawiera przykład:
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ększania i inne operacje CameraControl
po wywołaniu funkcji bindToLifecycle()
. Po zatrzymaniu lub zniszczeniu aktywności używanej do powiązania instancji kamery CameraControl
nie może już wykonywać operacji i zwraca nieudaną wartość 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 nieudaną wartośćListenableFuture
.setLinearZoom()
ustawia bieżące powiększenie za pomocą liniowej wartości powiększenia w zakresie 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żywania z widokiem
Slider
.
CameraInfo.getZoomState()
zwraca LiveData z bieżącym stanem powiększenia. Wartość zmienia się po zainicjowaniu aparatu lub jeśli poziom powiększenia zostanie ustawiony za pomocą setZoomRatio()
lub setLinearZoom()
. Wywołanie dowolnej z tych metod ustawia wartości, które są podstawą parametrów ZoomState.getZoomRatio()
i ZoomState.getLinearZoom()
.
Jest to przydatne, jeśli chcesz wyświetlać tekst z wartością powiększenia obok suwaka.
Wystarczy obserwować ZoomState
LiveData
, aby zaktualizować oba typy bez konieczności konwersji.
Wartość ListenableFuture
zwracana przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień o zakończeniu powtarzającego się żądania z określoną wartością powiększenia. Jeśli ustawisz nową wartość powiększenia, gdy poprzednia operacja jest jeszcze wykonywana, poprzednia operacja powiększenia natychmiast ListenableFuture
zakończy się niepowodzeniemListenableFuture
.
Latarka
CameraControl.enableTorch(boolean)
włącza lub wyłącza latarkę.
CameraInfo.getTorchState()
może służyć do sprawdzania bieżącego stanu latarki. Możesz sprawdzić wartość zwracaną przez funkcję CameraInfo.hasFlashUnit()
, aby określić, czy latarka jest dostępna. W przeciwnym razie wywołanie funkcji
CameraControl.enableTorch(boolean)
spowoduje natychmiastowe zakończenie działania zwróconej funkcji ListenableFuture
z wynikiem błędu i ustawi 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 ImageCapture
działa tylko wtedy, gdy latarka jest wyłączona.
Ostrość i pomiar
CameraControl.startFocusAndMetering()
wyzwala autofokus i pomiar ekspozycji, ustawiając obszary pomiaru AF/AE/AWB
na podstawie podanego parametru FocusMeteringAction. Jest to często używane do implementowania funkcji „kliknij, aby ustawić ostrość” w wielu aplikacjach aparatu.
MeteringPoint
Zacznij od utworzenia MeteringPoint
za pomocą MeteringPointFactory.createPoint(float x, float y, float
size)
.
Symbol MeteringPoint
reprezentuje pojedynczy punkt na kamerzeSurface
. Jest on przechowywany w postaci znormalizowanej, dzięki czemu można go łatwo przekształcić na współrzędne czujnika w celu określenia regionów AF/AE/AWB.
Rozmiar MeteringPoint
mieści się w zakresie od 0 do 1, a domyślny rozmiar to 0,15f. Podczas wywoływania funkcji MeteringPointFactory.createPoint(float x, float y, float
size)
biblioteka CameraX tworzy prostokątny region wyśrodkowany w punkcie (x, y)
dla podanego parametru size
.
Poniższy kod pokazuje, jak utworzyć 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 jednego MeteringPoints
z opcjonalnymi kombinacjami trybu pomiaru z FLAG_AF
, FLAG_AE
i FLAG_AWB
. Poniższy kod ilustruje to zastosowanie:
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 widać w powyższym kodzie, funkcja
startFocusAndMetering()
przyjmuje FocusMeteringAction
składający się z jednego MeteringPoint
dla regionów pomiarowych AF/AE/AWB i drugiego MeteringPoint tylko dla regionów AF i AE.
Wewnętrznie CameraX przekształca go w Camera2
MeteringRectangles
i ustawia odpowiednie parametry
CONTROL_AF_REGIONS
/
CONTROL_AE_REGIONS
/
CONTROL_AWB_REGIONS
w żądaniu przechwytywania.
Nie wszystkie urządzenia obsługują AF/AE/AWB i wiele regionów, więc CameraX wykonuje FocusMeteringAction
z najlepszym możliwym wynikiem. CameraX używa maksymalnej liczby obsługiwanych punktów pomiarowych w kolejności, w jakiej zostały dodane. Wszystkie punkty pomiarowe dodane po osiągnięciu maksymalnej liczby są ignorowane. Jeśli np. w przypadku parametru a
FocusMeteringAction
podasz 3 punkty pomiarowe na platformie obsługującej tylko 2, użyte zostaną tylko pierwsze 2 punkty pomiarowe. Ostatni znak 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 ten sposób, aby określić niezbędną ekspozycję w bieżących warunkach obrazu:
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, wykonując czynności opisane w CameraInfo.ExposureState.exposureCompensationRange()
następnej sekcji. Jeśli wartość jest obsługiwana, zwrócony obiekt ListenableFuture
zostanie ukończony, gdy wartość zostanie pomyślnie włączona w żądaniu przechwytywania. Jeśli określony indeks wykracza poza obsługiwany zakres, metoda setExposureCompensationIndex()
spowoduje natychmiastowe ukończenie zwróconego obiektu ListenableFuture
z wynikiem niepowodzenia.
CameraX przechowuje tylko najnowsze oczekujące żądanie setExposureCompensationIndex()
, a wielokrotne wywoływanie funkcji przed wykonaniem poprzedniego żądania powoduje jego anulowanie.
Ten fragment kodu ustawia indeks kompensacji ekspozycji i rejestruje wywołanie zwrotne, które zostanie wykonane po zrealizowaniu żądania zmiany ekspozycji:
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:- Możliwość obsługi sterowania kompensacją ekspozycji.
- Bieżący indeks kompensacji ekspozycji.
- Zakres indeksu kompensacji ekspozycji.
- Krok kompensacji ekspozycji używany w obliczeniach wartości kompensacji ekspozycji.
Na przykład poniższy kod inicjuje ustawienia ekspozycjiSeekBar
z bieżącymi wartościamiExposureState
:
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.
Ćwiczenia z programowania
Przykładowy kod
Społeczność deweloperów
Grupa dyskusyjna Android CameraX