Opcje konfiguracji

Konfigurujesz każdy przypadek użycia usługi CameraX, aby kontrolować różne aspekty jego działania.

Na przykład w przypadku przechwytywania obrazu możesz ustawić docelowy współczynnik proporcji 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 narażają interfejsy API na dynamiczne zmienianie ustawień po utworzeniu przypadku użycia. Informacje o konfiguracji w poszczególnych przypadkach użycia znajdziesz w artykułach o implementowaniu podglądu, analizowaniu obrazów i przechwytywaniu obrazów.

Konfiguracja CameraX

Dla uproszczenia usługa CameraX ma konfiguracje domyślne, takie jak wewnętrzne wykonawcy 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, służy do tego interfejs CameraXConfig.

Dzięki CameraXConfig aplikacja może:

Model wykorzystania

Poniżej opisano, jak korzystać z CameraXConfig:

  1. Utwórz obiekt CameraXConfig z dostosowanymi konfiguracjami.
  2. Zaimplementuj interfejs CameraXConfig.Provider w Application i zwróć obiekt CameraXConfig w getCameraXConfig().
  3. Dodaj klasę Application do pliku AndroidManifest.xml w sposób opisany tutaj.

Na przykład ten przykładowy kod ogranicza logowanie w aplikacji 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 po jej ustawieniu aplikacja musi poznać konfigurację CameraX.

Ogranicznik aparatu

Podczas pierwszego wywołania ProcessCameraProvider.getInstance() usługa CameraX oblicza i wysyła zapytania dotyczące kamer dostępnych na urządzeniu. Aparat CameraX musi komunikować się z komponentami sprzętowymi, więc czas potrzebny na obsługę każdej z kamer nie jest trywialny, zwłaszcza na słabszych urządzeniach. Jeśli aplikacja korzysta tylko z określonych kamer urządzenia, na przykład domyślnego przedniego aparatu, możesz skonfigurować CameraX tak, aby ignorowała inne kamery, co może skrócić czas oczekiwania na uruchomienie kamer, których używa Twoja aplikacja.

Jeśli obiekt CameraSelector przekazany do CameraXConfig.Builder.setAvailableCamerasLimiter() odfiltrowuje kamerę, CameraX zachowuje się tak, jakby ona nie istniała. Na przykład ten kod ogranicza aplikację do używania tylko 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 zbudowano CameraX, wymaga blokowania komunikacji międzyprocesowej (IPC) za pomocą sprzętu, którego działanie może czasem trwać 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 wewnętrznie zarządza tymi wątkami w tle, aby to zachowanie było przejrzyste. Niektóre aplikacje wymagają jednak ścisłej kontroli nad wątkami. CameraXConfig pozwala aplikacji ustawić wątki 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. Do wykonywania tych zadań CameraX przydziela wewnętrznemu Executor i nim zarządza. Jeśli jednak Twoja aplikacja wymaga bardziej rygorystycznej kontroli nad wątkami, użyj typu CameraXConfig.Builder.setCameraExecutor().

Moduł obsługi algorytmu szeregowania

Moduł obsługi algorytmu szeregowania służy do planowania zadań wewnętrznych w określonych odstępach czasu, np. ponawiania prób otwarcia kamery, gdy jest ona niedostępna. Ten moduł obsługi nie wykonuje zadań i wysyła je tylko do wykonawcy kamery. Jest on też czasem używany na starszych platformach interfejsów API, które wymagają Handler do wywołań zwrotnych. W takich przypadkach wywołania zwrotne są nadal wysyłane tylko bezpośrednio do wykonawcy kamery. CameraX przydziela wewnętrzny element HandlerThread i nim zarządza, ale możesz go zastąpić za pomocą polecenia CameraXConfig.Builder.setSchedulerHandler().

Logowanie

Logowanie w CameraX umożliwia aplikacjom filtrowanie komunikatów logcat, ponieważ dobrze jest unikać wyświetlania szczegółowych komunikatów w kodzie produkcyjnym. CameraX obsługuje 4 poziomy logowania: od najbardziej szczegółowego do najpoważniejszego:

  • Log.DEBUG (domyślna)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

Szczegółowy opis tych poziomów logów znajdziesz w dokumentacji Android Log. Użyj CameraXConfig.Builder.setMinimumLoggingLevel(int), aby ustawić odpowiedni poziom logowania dla swojej aplikacji.

Automatyczny wybór

CameraX automatycznie udostępnia funkcje dostosowane do urządzenia, na którym działa aplikacja. Na przykład, jeśli nie określisz rozdzielczości lub będzie ona nieobsługiwana, aplikacja CameraX automatycznie określi najlepszą do użycia rozdzielczość. Wszystkie te działania zajmuje się biblioteka, więc nie musisz pisać kodu przeznaczonego na konkretne urządzenia.

Celem aplikacji CameraX jest zainicjowanie sesji kamery. Oznacza to, że CameraX zmienia rozdzielczość i format obrazu w zależności od możliwości urządzenia. Może to nastąpić, ponieważ:

  • Urządzenie nie obsługuje żądanej rozdzielczości.
  • Na urządzeniu występują problemy ze zgodnością, na przykład starsze urządzenia, które wymagają określonego rozdzielczości, aby działać prawidłowo.
  • Na niektórych urządzeniach pewne formaty są dostępne tylko o określonym współczynniku proporcji.
  • Urządzenie ma preferowany „najbliższy mod16” do kodowania JPEG lub wideo. Więcej informacji: SCALER_STREAM_CONFIGURATION_MAP.

Mimo że CameraX tworzy sesję i nimi zarządza, zawsze sprawdzaj rozmiary zwracanych obrazów w danych wyjściowych przypadku użycia w kodzie i odpowiednio dostosowuj je.

Obrót

Domyślnie obrót kamery jest ustawiany tak, aby odpowiadał obrotowi domyślnego wyświetlacza podczas tworzenia danego zastosowania. W tym domyślnym przypadku CameraX generuje dane wyjściowe, dzięki którym aplikacja odpowiada oczekiwanym danym na podglądzie. Jeśli chcesz obsługiwać urządzenia z kilkoma wyświetlaczami, możesz zmienić obrót na wartość niestandardową, przekazując bieżącą orientację wyświetlacza podczas konfigurowania obiektów przypadku użycia lub dynamicznie po ich utworzeniu.

Aplikacja może określać rotację docelową za pomocą ustawień konfiguracji. Może wtedy aktualizować ustawienia rotacji za pomocą metod z interfejsów API przypadków użycia (takich jak ImageAnalysis.setTargetRotation()), nawet gdy cykl życia jest uruchomiony. Możesz z niej korzystać, gdy aplikacja jest zablokowana w trybie pionowym – i dzięki temu podczas obracania nie dochodzi do ponownej konfiguracji – ale w przypadku użycia zdjęcia lub analizy trzeba uwzględnić obecny obrót urządzenia. Na przykład rozpoznanie rotacji może być potrzebne, aby rozpoznanie twarzy było prawidłowe, lub aby zdjęcia były ustawione w orientacji poziomej lub pionowej.

Dane przechwyconych obrazów mogą być przechowywane bez informacji o rotacji. Dane Exif zawierają informacje o obrotach, dzięki którym aplikacje galerii mogą wyświetlać obrazy w prawidłowej orientacji po ich zapisaniu.

Aby wyświetlać dane podglądu we właściwej orientacji, możesz utworzyć przekształcenia za pomocą danych wyjściowych metadanych z Preview.PreviewOutput().

Poniższy przykładowy kod pokazuje, jak ustawić obrót dla 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 ustawionego rotacji każdy przypadek użycia powoduje rotację danych obrazu bezpośrednio lub udostępnia metadane rotacji konsumentom danych o obrazie bez obrócenia.

  • Podgląd: dane wyjściowe metadanych są podawane, aby rotacja rozdzielczości docelowej była znana za pomocą Preview.getTargetRotation().
  • Analiza obrazu: dostarczane są dane wyjściowe metadanych, dzięki którym współrzędne bufora obrazów są określane względem współrzędnych wyświetlania.
  • ImageCapture: zmieniane są metadane Exif, bufor lub zarówno bufor, jak i metadane, aby uwzględnić ustawienie rotacji. Zmieniona wartość zależy od implementacji HAL.

Prostokąt przycięcia

Domyślnie prostokąt przycinania to pełny bufor. Możesz go dostosować za pomocą elementów ViewPort i UseCaseGroup. Łącząc przypadki użycia i określając widoczny obszar, CameraX gwarantuje, że przycięcie we wszystkich przypadkach użycia w grupie odnosi się do tego samego obszaru w czujniku aparatu.

Z podanego niżej fragmentu kodu dowiesz się, jak korzystać z 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 definiuje 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 widocznego obszaru i załączonych przypadków użycia. Aby osiągnąć efekt WYSIWYG, można zwykle skonfigurować widoczny obszar na podstawie przykładu użycia podglądu. Aby w prosty sposób uzyskać widoczny obszar, użyj narzędzia PreviewView.

Z tych fragmentów kodu dowiesz się, jak pobrać 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 wartości funkcji ImageAnalysis i ImageCapture są zgodne z tym, co użytkownik widzi w narzędziu PreviewView, przy założeniu, że typ skalowania PreviewView jest ustawiony na domyślny (FILL_CENTER). Po zastosowaniu operacji przycinania i obrót do bufora wyjściowego obraz we wszystkich przypadkach użycia jest taki sam, choć możliwe w różnej rozdzielczości. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w sekcji Przekształć dane wyjściowe.

Wybór aparatu

CameraX automatycznie wybierze aparat najlepiej spełniający wymagania danej aplikacji i jej zastosowanie. Jeśli chcesz użyć innego urządzenia niż wybrane, masz kilka możliwości:

Poniższy przykładowy kod pokazuje, jak utworzyć element 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)

Wybierz kilka kamer jednocześnie

Od wersji CameraX 1.3 możesz też wybrać kilka kamer jednocześnie. Możesz na przykład powiązać aparat przedni i tylny, aby robić zdjęcia lub nagrywać filmy z obu perspektyw jednocześnie.

Dzięki funkcji Równoczesna kamera urządzenie może jednocześnie obsługiwać 2 aparaty z różnymi obiektywami lub dwa tylne aparaty. Poniższy blok kodu pokazuje, jak skonfigurować 2 kamery podczas wywoływania funkcji bindToLifecycle oraz jak pobrać oba obiekty kamery 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 zezwolić Aparatowi X na ustawianie rozdzielczości zdjęcia na podstawie możliwości urządzenia, obsługiwanego poziomu sprzętu, przypadku użycia i dostarczonych formatów obrazu. W przypadkach, które obsługują tę konfigurację, możesz też ustawić określoną rozdzielczość docelową lub format obrazu.

Automatyczna rozdzielczość

CameraX może automatycznie określić najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w zasadzie cameraProcessProvider.bindToLifecycle(). Jeśli to możliwe, określ wszystkie przypadki użycia potrzebne do równoczesnego działania w ramach jednej sesji w pojedynczym wywołaniu bindToLifecycle(). CameraX określa rozdzielczości na podstawie zbioru przypadków użycia powiązanych z poziomem obsługiwanego sprzętu i uwzględnieniem wariancji dla poszczególnych urządzeń (w przypadku gdy urządzenie przekracza lub nie spełnia dostępnych konfiguracji strumienia). Jego celem jest umożliwienie działania aplikacji na wielu różnych urządzeniach przy jednoczesnym zminimalizowaniu ścieżek kodu dla konkretnych urządzeń.

Domyślny współczynnik proporcji w przypadkach użycia do przechwytywania i analizy obrazu to 4:3.

Przypadki użycia obejmują konfigurowalny format obrazu, który pozwala aplikacji określić pożądany format obrazu na podstawie projektu UI. Dane wyjściowe aparatu CameraX są dopasowane do żądanych formatów obrazu tak samo dokładnie jak urządzenie. Jeśli nie ma obsługiwanej rozdzielczości dopasowania, wybierana jest ta, która spełnia większość warunków. Aplikacja decyduje o tym, jak kamera wyświetla się w aplikacji, a CameraX określa najlepsze ustawienia rozdzielczości, aby dopasować ją do różnych urządzeń.

Na przykład aplikacja może wykonywać te czynności:

  • Określ docelową rozdzielczość na 4:3 lub 16:9 dla danego przypadku użycia.
  • Określ rozdzielczość niestandardową, do której CameraX będzie starać się znaleźć jak najbliższe dopasowanie.
  • Określ współczynnik proporcji przycinania dla ImageCapture

CameraX automatycznie wybiera rozdzielczości powierzchni Aparatu2. W tabeli poniżej znajdziesz rozdzielczości:

Przypadek użycia Rozdzielczość powierzchni wewnętrznej Rozdzielczość danych wyjściowych
Podgląd Współczynnik proporcji: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej. Udostępniane są metadane, które umożliwiają wyświetlanie przycinania, skalowania i obracania zgodnie z docelowym formatem obrazu.
Rozdzielczość domyślna: najwyższa rozdzielczość podglądu lub najwyższa rozdzielczość preferowana na urządzeniu, która pasuje do współczynnika proporcji podglądu.
Maksymalna rozdzielczość: rozmiar podglądu, który najlepiej pasuje do rozdzielczości ekranu urządzenia, lub 1080p (1920 x 1080), która jest mniejsza.
Analiza obrazu Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej.
Rozdzielczość domyślna: domyślne ustawienie rozdzielczości docelowej to 640 x 480. Dostosowanie docelowej rozdzielczości i odpowiadającego jej formatu obrazu pozwala uzyskać najlepszą obsługiwaną rozdzielczość.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa urządzenia kamery to YUV_420_888, która jest pobierana z StreamConfigurationMap.getOutputSizes(). Domyślnie ustawiona jest rozdzielczość 640 × 480, więc jeśli chcesz uzyskać rozdzielczość większą niż 640 × 480, musisz użyć setTargetResolution() i setTargetAspectRatio(), aby uzyskać najbliższą z obsługiwanych rozdzielczości.
Robienie zdjęć Format obrazu: format obrazu najlepiej pasujący do otoczenia. Rozdzielczość powierzchni wewnętrznej.
Rozdzielczość domyślna: najwyższa dostępna rozdzielczość lub najwyższa rozdzielczość preferowana przez urządzenie, która pasuje do współczynnika proporcji obrazu ImageCapture.
Maksymalna rozdzielczość:maksymalna rozdzielczość wyjściowa urządzenia kamery w formacie JPEG. Aby je pobrać, użyj instancji StreamConfigurationMap.getOutputSizes().

Określ rozdzielczość

Podczas tworzenia przypadków użycia za pomocą metody setTargetResolution(Size resolution) możesz ustawić określone rozdzielczości. Znajdziesz je 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ć jednocześnie docelowego współczynnika proporcji i docelowej rozdzielczości w tym samym przypadku użycia. Powoduje to zgłoszenie błędu IllegalArgumentException podczas tworzenia obiektu konfiguracji.

Zapisz rozdzielczość Size w ramce współrzędnych po obróceniu obsługiwanych rozmiarów za pomocą rotacji docelowej. Na przykład urządzenie z naturalną orientacją pionową podczas naturalnej rotacji docelowej, która wymaga obrazu pionowego, może mieć rozmiar 480 x 640, a to samo urządzenie – obrócone o 90 stopni i kierowane w orientacji poziomej – 640 x 480.

Docelowa rozdzielczość to próba określenia minimalnej granicy rozdzielczości obrazu. Rzeczywista rozdzielczość zdjęcia to najbliższa dostępna rozdzielczość, która nie jest mniejsza od docelowej, określona przez implementację aparatu.

Jeśli jednak żadna rozdzielczość nie jest równa rozdzielczości docelowej lub większa, wybierana jest najbliższa dostępna rozdzielczość mniejsza od tej. Rozdzielczości z tym samym współczynnikiem proporcji obrazu Size mają wyższy priorytet niż rozdzielczości o różnych współczynnikach proporcji.

CameraX ustawia najlepszą rozdzielczość na podstawie żądań. Jeśli główną potrzebą jest dobranie formatu obrazu, wybierz tylko setTargetAspectRatio, a Aparat X wybierze konkretną rozdzielczość odpowiednią do danego urządzenia. Jeśli główną potrzebą aplikacji jest określenie rozdzielczości, aby usprawnić przetwarzanie obrazu (np. obrazu o małym lub średnim rozmiarze na podstawie możliwości przetwarzania danych przez urządzenie), użyj setTargetResolution(Size resolution).

Jeśli Twoja aplikacja wymaga dokładnej rozdzielczości, sprawdź w tabeli w createCaptureSession(), jakie maksymalne rozdzielczości są obsługiwane na poszczególnych poziomach sprzętu. Aby sprawdzić, jakie rozdzielczości obsługuje obecnie urządzenie, przeczytaj artykuł StreamConfigurationMap.getOutputSizes(int).

Jeśli Twoja aplikacja działa w Androidzie 10 lub nowszym, możesz użyć isSessionConfigurationSupported(), aby zweryfikować konkretną właściwość SessionConfiguration.

Steruj wyjściem aparatu

Poza możliwością konfigurowania wyjścia kamery w razie potrzeby w każdym przypadku użycia CameraX implementuje też te interfejsy, które obsługują operacje na kamerze wspólne dla wszystkich powiązanych przypadków użycia:

  • CameraControl pozwala skonfigurować typowe funkcje kamery.
  • CameraInfo umożliwia zapytania o stany tych popularnych funkcji kamery.

Oto funkcje aparatu obsługiwane przy użyciu funkcji CameraControl:

  • Zoom
  • Latarka
  • Ostrość i miernik (kliknij, 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ł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ć powiększenie i inne operacje CameraControl po wywołaniu funkcji bindToLifecycle(). Gdy zatrzymasz lub zniszczysz aktywność służącą do powiązania instancji kamery, CameraControl nie może już wykonywać operacji i zwraca błąd ListenableFuture.

Zoom

W aplikacji CameraControl dostępne są dwie metody zmiany poziomu powiększenia:

  • setZoomRatio() ustawia powiększenie według współczynnika powiększenia.

    Współczynnik musi mieścić się w przedziale od CameraInfo.getZoomState().getValue().getMinZoomRatio() do CameraInfo.getZoomState().getValue().getMaxZoomRatio(). W przeciwnym razie zwraca błąd ListenableFuture.

  • setLinearZoom() ustawia bieżące powiększenie o wartości powiększenia liniowego z zakresu od 0 do 1,0.

    Zaletą powiększenia liniowego jest to, że skaluje ono pole widzenia wraz ze zmianami powiększenia. Dzięki temu idealnie nadaje się do użycia z widokiem Slider.

CameraInfo.getZoomState() zwraca LiveData aktualnego stanu powiększenia. Ta wartość zmienia się po zainicjowaniu kamery lub po ustawieniu poziomu powiększenia za pomocą setZoomRatio() bądź setLinearZoom(). Wywołanie dowolnej metody ustawia wartości zapasowe ZoomState.getZoomRatio() i ZoomState.getLinearZoom(). Jest to przydatne, gdy obok suwaka chcesz wyświetlać tekst współczynnika powiększenia. Obserwuj tag ZoomState LiveData, aby aktualizować dane bez konieczności dokonania konwersji.

Parametr ListenableFuture zwracany przez oba interfejsy API umożliwia aplikacjom powiadamianie o ukończeniu powtarzających się żądań z określoną wartością powiększenia. Dodatkowo, jeśli ustawisz nową wartość powiększenia podczas wykonywania poprzedniej operacji, operacja ListenableFuture poprzedniej operacji powiększenia zakończy się niepowodzeniem natychmiast.

Latarka

CameraControl.enableTorch(boolean) włącza lub wyłącza latarkę.

CameraInfo.getTorchState() może być używany do wysyłania zapytań dotyczących bieżącego stanu pochodni. Możesz sprawdzić wartość zwracaną przez CameraInfo.hasFlashUnit(), aby określić, czy latarka jest dostępna. Jeśli nie, wywołanie funkcji CameraControl.enableTorch(boolean) spowoduje, że zwrócony ListenableFuture natychmiast zostanie zakończony niepowodzeniem i ustawia stan pochodnia 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() uruchamia autofokus i pomiar ekspozycji, ustawiając regiony pomiaru AF/AE/AWB na podstawie podanej wartości FocusMeteringAction. Używa się ich często do wdrażania funkcji „dotknij, aby ustawić ostrość” w wielu aplikacjach aparatu.

Punkt pomiaru

Najpierw utwórz MeteringPoint, używając MeteringPointFactory.createPoint(float x, float y, float size). MeteringPoint reprezentuje jeden punkt na kamerze Surface. Jest przechowywany w znormalizowanej postaci, dzięki czemu można go łatwo przekonwertować na współrzędne czujnika do określania regionów AF/AE/AWB.

MeteringPoint ma zakres od 0 do 1, a domyślny rozmiar to 0,15f. Wywołując MeteringPointFactory.createPoint(float x, float y, float size), CameraX tworzy prostokątny obszar wyśrodkowany w punkcie (x, y) dla podanej wartości size.

Ten 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ć kompilację FocusMeteringAction, która składa się z co najmniej 1 elementu MeteringPoints z opcjonalnymi kombinacjami trybu pomiaru z FLAG_AF, FLAG_AE i FLAG_AWB. Ten kod ilustruje użycie tego narzędzia:

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 poprzednim kodzie, startFocusAndMetering() pobiera element FocusMeteringAction zawierający 1 MeteringPoint dla regionów pomiaru AF/AE/AWB i drugi MeteringPoint tylko dla AF i AE.

Wewnętrznie CameraX konwertuje go na Aparat2 MeteringRectangles i ustawia odpowiednie parametry CONTROL_AF_REGIONS/CONTROL_AE_REGIONS/CONTROL_AWB_REGIONS na żądanie nagrywania.

Ponieważ nie każde urządzenie obsługuje AF/AE/AWB i wiele regionów, CameraX wykonuje FocusMeteringAction z najwyższą starannością. Aplikacja CameraX używa maksymalnej liczby obsługiwanych punktów pomiarowych w kolejności, w jakiej zostały dodane. Wszystkie punkty pomiaru dodane po maksymalnej liczbie są ignorowane. Jeśli np. obiekt FocusMeteringAction jest dostarczany z 3 punktami MeteringPoint na platformie obsługującej tylko 2, używane są tylko pierwsze 2 punkty MeteringPoint. Ostatni MeteringPoint jest ignorowany przez aplikację CameraX.

Kompensacja ekspozycji

Kompensacja ekspozycji przydaje się, gdy aplikacje muszą dostosować wartości ekspozycji (EV), a nie wyniki automatycznej ekspozycji. Wartości kompensacji ekspozycji są łączone w ten sposób, aby określić potrzebną ekspozycję w przypadku aktualnych warunków zdjęcia:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX udostępnia funkcję Camera.CameraControl.setExposureCompensationIndex() służącą do ustawiania kompensacji ekspozycji jako wartości indeksu.

Wartości indeksu dodatnie rozjaśniają zdjęcie, a wartości ujemne – zaciemniają. Aplikacje mogą wysyłać zapytania do obsługiwanego zakresu za pomocą funkcji CameraInfo.ExposureState.exposureCompensationRange() opisanej w następnej sekcji. Jeśli wartość jest obsługiwana, zwracana wartość ListenableFuture jest kończona po włączeniu wartości w żądaniu przechwytywania. Jeśli podany indeks nie mieści się w obsługiwanym zakresie, funkcja setExposureCompensationIndex() powoduje, że zwracana wartość ListenableFuture jest natychmiast kończona i nie kończy się jej niepowodzeniem.

CameraX zachowuje tylko ostatnie oczekujące żądanie setExposureCompensationIndex(), a wywołanie funkcji wielokrotnie, zanim poprzednie żądanie zostanie wykonane, skutkuje jego anulowaniem.

Ten fragment kodu ustawia indeks kompensacji ekspozycji i rejestruje wywołanie zwrotne 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żącą wartość ExposureState, w tym:

    • Możliwość 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 wykorzystaniem bieżących wartości 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 aplikacji CameraX znajdziesz w tych dodatkowych materiałach.

Ćwiczenia z programowania

  • Pierwsze kroki z Aparatem X
  • Przykładowy kod

  • Przykładowe aplikacje CameraX
  • Społeczność programistów

    Grupa dyskusyjna Google CameraX