Każdy przypadek użycia CameraX konfigurujesz osobno, aby kontrolować różne aspekty jego działania.
Na przykład w przypadku przechwytywania obrazu możesz ustawić docelowy format obrazu oraz tryb lampy błyskowej. Przykładowy kod:
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 do dynamicznego zmieniania ustawień po utworzeniu przypadku użycia. Informacje o konfiguracji związanej z poszczególnymi przypadkami użycia znajdziesz w artykułach Wdrażanie podglądu, Analiza obrazów i Zapisywanie obrazów.
CameraXConfig
W celu uproszczenia 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 preferuje dostosowanie tych konfiguracji, możesz użyć interfejsu CameraXConfig
.
Dzięki CameraXConfig
aplikacja może:
- Zoptymalizuj opóźnienie uruchamiania za pomocą
setAvailableCameraLimiter()
. - Przekaż wykonawcę aplikacji do CameraX za pomocą interfejsu
setCameraExecutor()
. - Zastąp domyślny moduł obsługi harmonogramu elementem
setSchedulerHandler()
. - Zmień poziom rejestrowania za pomocą
setMinimumLoggingLevel()
.
Model użycia
Poniżej znajdziesz opis procedury korzystania z CameraXConfig
:
- Utwórz obiekt
CameraXConfig
z niestandardowymi konfiguracjami. - W funkcji
Application
zaimplementuj interfejsCameraXConfig.Provider
i zwróć obiektCameraXConfig
wgetCameraXConfig()
. - Dodaj zajęcia
Application
do plikuAndroidManifest.xml
w sposób opisany tutaj.
Na przykład ten przykładowy kod ogranicza rejestrowanie CameraX tylko do wiadomości 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 skonfigurowaniu.
Ogranicznik aparatu
Podczas pierwszego wywołania funkcji
ProcessCameraProvider.getInstance()
CameraX wylicza i wysyła zapytania o właściwości kamer dostępnych na urządzeniu. Ponieważ CameraX musi komunikować się ze sprzętowymi komponentami, ten proces może zająć sporo czasu w przypadku każdej kamery, zwłaszcza na urządzeniach niskiego poziomu. Jeśli Twoja aplikacja korzysta tylko z określonych kamer na urządzeniu, na przykład domyślnej przedniej kamery, możesz skonfigurować CameraX tak, aby ignorował inne kamery. Dzięki temu możesz skrócić czas uruchamiania kamer, których używa aplikacja.
Jeśli CameraSelector
przekazane do CameraXConfig.Builder.setAvailableCamerasLimiter()
odfiltrowuje 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ędzyprocesowej (IPC) z urządzeniami, która może czasami trwać setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko w wątkach w tle, aby wątek główny nie był zablokowany, a interfejs użytkownika działał płynnie. CameraX zarządza tymi wątkami w tle, aby to działanie było przezroczyste. Niektóre aplikacje wymagają jednak ścisłej kontroli wątków. CameraXConfig
pozwala aplikacji ustawić wątki w tle, które są używane przez CameraXConfig.Builder.setCameraExecutor()
i CameraXConfig.Builder.setSchedulerHandler()
.
Wykonawca DIME
Wykonawca kamery jest używany we wszystkich wewnętrznych wywołaniach interfejsu API platformy Camera oraz w przypadku wywołań zwrotnych z tych interfejsów API. CameraX przydziela i zarządza wewnętrznym Executor
, aby wykonywać te zadania.
Jeśli jednak Twoja aplikacja wymaga ściślej kontroli wątków, użyj CameraXConfig.Builder.setCameraExecutor()
.
Scheduler Handler
Obsługa harmonogramu służy do planowania zadań wewnętrznych w określonych odstępach czasu, na przykład do ponownego próby otwarcia kamery, gdy jest niedostępna. Ten handler nie wykonuje zadań, tylko przekazuje je do wykonawcy kamery. Czasami jest też używany na starszych platformach interfejsu API, które wymagają korzystania z 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
, 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ż może to być dobrym rozwiązaniem, aby uniknąć szczegółowych komunikatów w kodzie produkcyjnym. CameraX obsługuje 4 poziomy rejestrowania, od najbardziej obszernego do najbardziej rygorystycznego:
Log.DEBUG
(domyślnie)Log.INFO
Log.WARN
Log.ERROR
Szczegółowe opisy tych poziomów logowania znajdziesz w dokumentacji dzienników Androida. Aby ustawić odpowiedni poziom rejestrowania dla aplikacji, użyj parametru CameraXConfig.Builder.setMinimumLoggingLevel(int)
.
Automatyczny wybór
CameraX automatycznie udostępnia funkcje specyficzne dla urządzenia, na którym działa Twoja aplikacja. Na przykład CameraX automatycznie określa najlepszą rozdzielczość, jeśli nie określisz jej samodzielnie lub jeśli wybrana rozdzielczość nie jest obsługiwana. Biblioteka zajmuje się wszystkimi tymi zadaniami, dzięki czemu nie musisz pisać kodu dla poszczególnych urządzeń.
Celem CameraX jest inicjowanie sesji aparatu. Oznacza to, że CameraX musi iść na kompromisy w zakresie rozdzielczości i formatów obrazu ze względu na możliwości urządzenia. Do kompromisu może dojść, ponieważ:
- 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, aby działać prawidłowo.
- Na niektórych urządzeniach niektóre formaty są dostępne tylko w określonych proporcjach.
- Urządzenie preferuje „nearest mod16” w przypadku kodowania JPEG lub wideo. Więcej informacji znajdziesz w sekcji
SCALER_STREAM_CONFIGURATION_MAP
.
Mimo że CameraX tworzy sesję i ją zarządza, zawsze sprawdzaj zwracane rozmiary obrazu w przypadku danych wyjściowych w kodze i odpowiednio je dostosowuj.
Obrót
Domyślnie podczas tworzenia przykładu zastosowania obracanie kamery jest ustawione tak, aby pasowało do domyślnego obracania wyświetlacza. W tym domyślnym przypadku CameraX generuje dane wyjściowe, aby aplikacja mogła dopasować obraz do tego, co ma się wyświetlać w podglądzie. Aby obsługiwać urządzenia z wieloma wyświetlaczami, możesz zmienić rotację na wartość niestandardową, przekazując bieżący kierunek wyświetlania podczas konfigurowania obiektów przypadku użycia lub dynamicznie po ich utworzeniu.
Aplikacja może ustawić docelową orientację za pomocą ustawień konfiguracji. Następnie może zaktualizować ustawienia rotacji za pomocą metod z interfejsów API do obsługi przypadków użycia (np. ImageAnalysis.setTargetRotation()
), nawet gdy cykl życia jest w stanie uruchomionym. Możesz użyć tej metody, gdy aplikacja jest zablokowana w układzie pionowym (nie ma więc potrzeby rekonfiguracji po obróceniu), ale aplikacja do zdjęć lub analizy musi znać bieżący obrót urządzenia. Może na przykład być potrzebna świadomość obrotu, aby twarze były prawidłowo zorientowane pod kątem wykrywania twarzy lub aby zdjęcia były w orientacji poziomej bądź pionowej.
Dane dotyczące zarejestrowanych obrazów mogą być przechowywane bez informacji o ich 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ć danych wyjściowych metadanych z Preview.PreviewOutput()
do tworzenia przekształceń.
Poniższy przykładowy kod pokazuje, jak ustawić obrót 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 albo obraca dane obrazu bezpośrednio, albo udostępnia metadane rotacji odbiorcom nieobrócone dane obrazu.
- Podgląd: dane wyjściowe metadanych są dostarczane w taki sposób, aby rotacja docelowej rozdzielczości była znana za pomocą
Preview.getTargetRotation()
. - ImageAnalysis: dane wyjściowe metadanych są dostarczane w taki sposób, aby współrzędne bufora obrazu były znane w stosunku do współrzędnych wyświetlacza.
- ImageCapture: metadane Exif obrazu, bufor lub bufor i metadane są zmieniane, aby uwzględnić ustawienie obrotu. Zmieniona wartość zależy od implementacji HAL.
Ramka kadrowania
Domyślnie prostokąt przycięcia jest równy pełnemu prostokątowi bufora. Możesz go dostosować za pomocą pól ViewPort
i UseCaseGroup
. Dzięki grupowaniu przypadków użycia i ustawieniu widoku CameraX zapewnia, że prostokąty przycinania wszystkich przypadków użycia w grupie wskazują ten sam obszar na czujniku aparatu.
Ten 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 przycinania na podstawie właściwości widocznego obszaru i dołączonych przypadków użycia. Aby uzyskać efekt WYSIWYG, możesz skonfigurować widoczny obszar na podstawie przypadku użycia podglądu. Prosty sposób na uzyskanie widocznego obszaru to użycie PreviewView
.
Fragmenty kodu poniżej 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 użytkownik końcowy widzi w PreviewView
, o ile typ skali PreviewView
jest ustawiony na domyślny, czyli FILL_CENTER
. Po zastosowaniu prostokąta przycinania i obrotu do bufora wyjściowego obraz we wszystkich przypadkach użycia jest taki sam, ale może mieć różne rozdzielczości. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w sekcji transform.
Wybór kamery
CameraX automatycznie wybiera najlepszy aparat dopasowany do wymagań i zastosowania aplikacji. Jeśli chcesz użyć innego urządzenia niż wybrane przez nas, masz do wyboru kilka opcji:
- Poproś o domyślny przedni aparat, używając
CameraSelector.DEFAULT_FRONT_CAMERA
. - Poproś o domyślny tylny aparat, klikając
CameraSelector.DEFAULT_BACK_CAMERA
. - Filtruj listę dostępnych urządzeń według ich
CameraCharacteristics
CameraSelector.Builder.addCameraFilter()
.
Poniższy przykładowy kod pokazuje, jak utworzyć element CameraSelector
, aby wpływać 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 kilku kamer jednocześnie
Od wersji CameraX 1.3 możesz też wybierać kilka kamer jednocześnie. Możesz na przykład połączyć przedni i tylny aparat, aby robić zdjęcia lub nagrywać filmy z obu perspektyw jednocześnie.
Gdy korzystasz z funkcji jednoczesnego korzystania z aparatów, urządzenie może jednocześnie używać 2 aparatów z różnymi obiektywami lub 2 tylnych aparatów. Ten 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 pozwolić aplikacji CameraX na ustawienie rozdzielczości obrazu na podstawie kombinacji możliwości urządzenia, obsługiwanego przez nie poziomu sprzętowego, przypadku użycia i podanego formatu obrazu. Możesz też ustawić określoną rozdzielczość docelową lub określony współczynnik kształtu w przypadkach użycia, które obsługują tę konfigurację.
Automatyczne rozwiązanie
CameraX może automatycznie określać najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w cameraProcessProvider.bindToLifecycle()
. W miarę możliwości określ wszystkie przypadki użycia, które wymagają jednoczesnego działania w ramach jednej sesji, w jednym wywołaniu bindToLifecycle()
. CameraX określa rozdzielczości na podstawie zestawu przypadków użycia związanych z uwzględnianiem obsługiwanego poziomu sprzętu urządzenia oraz uwzględniając zmienność związaną z danym urządzeniem (gdy urządzenie przekracza lub nie spełnia dostępnych konfiguracji strumienia).
Celem jest umożliwienie uruchamiania aplikacji na wielu urządzeniach przy jednoczesnym minimalizowaniu ścieżek kodu związanych z konkretnym urządzeniem.
Domyślny format obrazu w przypadku rejestrowania i analizowania obrazów to 4:3.
W przypadku niektórych zastosowań można skonfigurować proporcje, aby aplikacja mogła określić pożądane proporcje na podstawie projektu interfejsu użytkownika. Wyjście CameraX jest tworzone w taki sposób, aby jak najdokładniej dopasować format obrazu do wymaganego, w miarę możliwości urządzenia. Jeśli nie ma obsługiwanego rozwiązania dopasowania do wyrażenia, wybierane jest to, które spełnia najwięcej warunków. Aplikacja określa wygląd aparatu 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śl docelową rozdzielczość 4:3 lub 16:9 w przypadku konkretnego przypadku użycia
- Określ niestandardową rozdzielczość, której CameraX spró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 te rozdzielczości:
Przypadek użycia | Rozdzielczość wewnętrznej powierzchni | Rozdzielczość danych wyjściowych |
---|---|---|
Podgląd | Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. | Rozdzielczość wewnętrznej powierzchni. Metadane umożliwiają kadrowanie, skalowanie i obracanie widoku w celu dostosowania do docelowego współczynnika proporcji. |
Domyślna rozdzielczość: najwyższa rozdzielczość podglądu lub najwyższa preferowana rozdzielczość urządzenia, która odpowiada formatowi obrazu podglądu. | ||
Maksymalna rozdzielczość: rozmiar podglądu, który jest najbardziej zbliżony do rozdzielczości ekranu urządzenia lub 1080p (1920 x 1080), w zależności od tego, która wartość jest mniejsza. | ||
Analiza obrazu | Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. | Rozdzielczość wewnętrznej powierzchni. |
Domyślna rozdzielczość: domyślne ustawienie docelowej rozdzielczości to 640 x 480. Dostosowanie zarówno docelowej rozdzielczości, jak i odpowiedniego współczynnika proporcji skutkuje uzyskaniem najlepszej obsługiwanej rozdzielczości. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie YUV_420_888, pobierana z StreamConfigurationMap.getOutputSizes() .
Domyślnie docelową rozdzielczość ustawia się na 640 x 480, więc jeśli chcesz użyć rozdzielczości większej 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ść wewnętrznej powierzchni. |
Domyślna rozdzielczość: najwyższa dostępna rozdzielczość lub najwyższa preferowana rozdzielczość urządzenia, która odpowiada formatowi obrazu ImageCapture. | ||
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby pobrać te dane, użyj kodu StreamConfigurationMap.getOutputSizes() .
|
Określ rozdzielczość
Podczas tworzenia przypadków użycia możesz ustawić określone rozdzielczości za pomocą metody setTargetResolution(Size resolution)
, jak pokazano w tym przykładzie kodu:
Kotlin
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .build()
Java
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .setTargetResolution(new Size(1280, 720)) .build();
Nie można ustawić jednocześnie docelowego formatu obrazu i docelowej rozdzielczości w tym samym przypadku użycia. W efekcie podczas tworzenia obiektu konfiguracji wystąpi błąd IllegalArgumentException
.
Wyraź rozdzielczość Size
w układzie współrzędnych po obróceniu obsługiwanych rozmiarów zgodnie z docelowym obrotem. Na przykład urządzenie z naturalną orientacją pionową w naturalnym docelowym obrocie, które wymaga obrazu w orientacji pionowej, może podać wymiary 480 x 640, a to samo urządzenie, które jest obracane o 90 stopni i kierowane na orientację poziomą, może podać wymiary 640 x 480.
Rozdzielczość docelowa próbuje ustawić minimalny limit rozdzielczości obrazu. Rzeczywista rozdzielczość obrazu to najbliższa dostępna rozdzielczość o rozmiarze, który nie jest mniejszy niż docelowy, zgodnie z implementacją aparatu.
Jeśli jednak nie ma rozdzielczości równej lub większej od docelowej, zostanie wybrana 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.
Aplikacja CameraX stosuje najodpowiedniejszą rozdzielczość na podstawie żądań. Jeśli głównym wymaganiem jest zachowanie współczynnika proporcji, określ tylko setTargetAspectRatio
, a CameraX określi odpowiednią rozdzielczość na podstawie urządzenia.
Jeśli aplikacja musi przede wszystkim określić rozdzielczość, aby usprawnić przetwarzanie obrazu (np. mały lub średni obraz na podstawie możliwości przetwarzania urządzenia), użyj setTargetResolution(Size resolution)
.
Jeśli Twoja aplikacja wymaga określonej rozdzielczości, w tabeli w createCaptureSession()
sprawdź, jakie maksymalne rozdzielczości są obsługiwane na poszczególnych poziomach sprzętowych. Aby sprawdzić, jakie rozdzielczości obsługuje Twoje urządzenie, zobacz StreamConfigurationMap.getOutputSizes(int)
.
Jeśli aplikacja działa na Androidzie 10 lub nowszym, możesz użyć isSessionConfigurationSupported()
do weryfikacji konkretnego SessionConfiguration
.
Sterowanie wyjściem z kamery
Oprócz możliwości konfigurowania wyjścia z kamery w razie potrzeby w każdym indywidualnym przypadku użycia CameraX wdraża też te interfejsy, aby obsługiwać operacje na kamerze wspólne dla wszystkich powiązanych przypadków użycia:
CameraControl
pozwala konfigurować typowe funkcje aparatu.CameraInfo
umożliwia wysyłanie zapytań dotyczących stanu tych typowych funkcji aparatu.
Te funkcje aparatu są obsługiwane przez CameraControl:
- Zoom
- Latarka
- ostrość i pomiar ekspozycji (dotknij, aby wyostrzyć);
- Kompensacja ekspozycji
Pobieranie wystąpień interfejsów CameraControl i CameraInfo
Pobieraj wystąpienia CameraControl
i CameraInfo
za pomocą obiektu Camera
zwracanego przez ProcessCameraProvider.bindToLifecycle()
.
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()
Po wywołaniu funkcji bindToLifecycle()
możesz na przykład przesłać operacje powiększania i inne operacje CameraControl
. Po zatrzymaniu lub usunięciu aktywności używanej do wiązania instancji kamery CameraControl
nie może już wykonywać operacji i zwraca błąd ListenableFuture
.
Zoom
Aplikacja 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 wartośćListenableFuture
.setLinearZoom()
ustawia bieżące powiększenie z wartością liniową w zakresie od 0 do 1,0.Zaletą liniowego powiększenia jest to, że pole widzenia (FOV) zmienia się wraz ze zmianami powiększenia. Dzięki temu doskonale nadaje się do korzystania z widoku
Slider
.
CameraInfo.getZoomState()
zwraca LiveData bieżącego stanu powiększenia. Wartość zmienia się po zainicjowaniu kamery lub ustawieniu poziomu powiększenia za pomocą setZoomRatio()
lub setLinearZoom()
. Wywołanie dowolnej z tych metod powoduje ustawienie wartości obsługujących ZoomState.getZoomRatio()
i ZoomState.getLinearZoom()
.
Jest to przydatne, jeśli chcesz wyświetlić tekst współczynnika powiększenia obok suwaka.
Wystarczy, że obserwujesz ZoomState
LiveData
, aby aktualizować oba te parametry bez konieczności przeprowadzania konwersji.
ListenableFuture
zwracany przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień po zrealizowaniu powtarzającego się żądania z określoną wartością powiększenia. Jeśli dodatkowo ustawisz nową wartość powiększenia, gdy poprzednia operacja jest nadal wykonywana, ListenableFuture
poprzedniej operacji powiększenia zakończy się niepowodzeniem.
Latarka
CameraControl.enableTorch(boolean)
włącza i wyłącza latarkę.
CameraInfo.getTorchState()
może służyć do wysyłania zapytań o obecny stan latarki. Aby sprawdzić, czy latarka jest dostępna, możesz sprawdzić zwróconą wartość:
CameraInfo.hasFlashUnit()
. W przeciwnym razie wywołanie funkcji CameraControl.enableTorch(boolean)
spowoduje natychmiastowe zakończenie działania funkcji ListenableFuture
z wynikiem niepowodzenia i ustawienie stanu latarki na TorchState.OFF
.
Gdy latarka jest włączona, pozostaje włączona podczas robienia zdjęć i filmowania niezależnie od ustawienia trybu lampy błyskowej. flashMode
w ImageCapture
działa tylko wtedy, gdy latarka jest wyłączona.
Ostrość i pomiar
CameraControl.startFocusAndMetering()
uruchamia autofokus i automatyczny 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 do obsługi aparatu.
MeteringPoint
Na początek utwórz MeteringPoint
za pomocą MeteringPointFactory.createPoint(float x, float y, float
size)
.
MeteringPoint
reprezentuje pojedynczy punkt na kamerze
Surface
. Jest on przechowywany w postaci unormowanej, aby można było łatwo przekształcić go w współrzędne czujnika na potrzeby określania regionów AF/AE/AWB.
Rozmiar MeteringPoint
waha się od 0 do 1, a domyślnie wynosi 0,15f. Podczas wywoływania metody MeteringPointFactory.createPoint(float x, float y, float
size)
CameraX tworzy prostokątny obszar z środkiem w miejscu (x, y)
dla podanego size
.
Ten kod pokazuje, jak utworzyć element 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ą tworzyć
FocusMeteringAction
,
które składają się z jednego lub większej liczby MeteringPoints
z opcjonalnymi kombinacjami trybu pomiaru z FLAG_AF
,
FLAG_AE
,
FLAG_AWB
. Poniżej znajduje się kod, który demonstruje 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 poprzednim kodzie,
startFocusAndMetering()
przyjmuje FocusMeteringAction
składający się z jednego MeteringPoint
dla regionów pomiaru AF/AE/AWB i innego punktu pomiaru 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 rejestrowania.
Nie wszystkie urządzenia obsługują AF/AE/AWB i wiele regionów, dlatego CameraX wykonuje FocusMeteringAction
w najlepszy możliwy sposób. CameraX używa maksymalnej liczby obsługiwanych punktów pomiarowych w kolejności dodawania. Wszystkie punkty pomiarowe dodane po przekroczeniu maksymalnej liczby są ignorowane. Jeśli na przykład FocusMeteringAction
jest dostarczany z 3 punktami pomiarowymi na platformie obsługującej tylko 2 punkty pomiarowe, używane są tylko 2 pierwsze punkty pomiarowe. Ostatni parametr MeteringPoint
jest ignorowany przez CameraX.
Kompensacja ekspozycji
Kompensacja ekspozycji jest przydatna, gdy aplikacje muszą dostosować wartości ekspozycji (EV) poza wynikiem wyjściowym automatycznej ekspozycji (AE). Wartości kompensacji ekspozycji są łączone w ten sposób, aby określić ekspozycję wymaganą w obecnych warunkach obrazu:
Exposure = ExposureCompensationIndex * ExposureCompensationStep
CameraX udostępnia funkcję Camera.CameraControl.setExposureCompensationIndex()
do ustawiania kompensacji ekspozycji jako wartości indeksu.
Wartości dodatnie powodują jaśniejsze obrazy, a ujemne – ciemniejsze. Aplikacje mogą wysyłać zapytania dotyczące obsługiwanego zakresu za pomocą CameraInfo.ExposureState.exposureCompensationRange()
, jak opisano w następnej sekcji. Jeśli wartość jest obsługiwana, zwrócona wartość ListenableFuture
jest realizowana po włączeniu tej wartości w żądaniu rejestrowania. Jeśli określony indeks wykracza poza obsługiwany zakres, funkcja setExposureCompensationIndex()
powoduje, że zwrócona wartość ListenableFuture
jest realizowana natychmiast z wynikiem niepowodzenia.
CameraX przechowuje tylko najnowsze oczekujące żądanie setExposureCompensationIndex()
. Wywołanie tej funkcji kilka razy przed wykonaniem poprzedniego żądania powoduje jego anulowanie.
Ten fragment kodu ustawia indeks kompensacji ekspozycji i rejestruje wywołanie zwrotne, które jest wykonywane po wykonaniu żą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żące daneExposureState
, w tym:- Obsługa kompensacji ekspozycji.
- Bieżący indeks kompensacji ekspozycji.
- Zakres indeksu kompensacji ekspozycji.
- Krok kompensacji ekspozycji używany w obliczeniach wartości kompensacji ekspozycji.
Na przykład ten kod inicjuje ustawienia ekspozycji SeekBar
z aktualnymi 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 dodatkowych materiałach.
Ćwiczenia z programowania
Przykładowy kod
Społeczność programistów
Grupa dyskusyjna CameraX na Androida