Opcje konfiguracji

Każde zastosowanie 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ówPrzechwytywanie obrazu.

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:

Model użycia

Poniżej znajdziesz opis korzystania z CameraXConfig:

  1. Utwórz obiekt CameraXConfig z niestandardowymi konfiguracjami.
  2. W funkcji Application zaimplementuj interfejs CameraXConfig.Provider i zwróć obiekt CameraXConfiggetCameraXConfig().
  3. Dodaj zajęcia Application do pliku AndroidManifest.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, na odpowiedź których czasami trzeba czekać setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko z wątków 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()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 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 zarządza nią, zawsze sprawdzaj zwracane rozmiary obrazu w przypadku wyjścia w kodze i odpowiednio je dostosowuj.

Obrót

Domyślnie podczas tworzenia przypadku użycia obrót kamery jest ustawiony tak, aby pasował do obrotu domyślnego wyświetlacza. W tym domyślnym przypadku CameraX generuje dane wyjściowe, aby aplikacja mogła dopasować obraz do tego, co ma się pojawić na 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 związanych z przypadkiem 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 ImageAnalysisImageCapture, 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 kilka opcji:

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 proporcji w przypadkach, 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ć wymagane formaty obrazu, z uwzględnieniem 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 więc, jak ma wyglądać aparat w aplikacji, a CameraX określa najlepsze ustawienia rozdzielczości aparatu, aby spełniały 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 ustawień docelowych. Rozdzielczość wewnętrznej powierzchni. Metadane umożliwiają kadrowanie, skalowanie i obracanie widoku zgodnie z docelowym współczynnikiem 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 ustawień docelowych. 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 najlepiej pasujący 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ć zarówno docelowego formatu obrazu, jak i docelowej rozdzielczości w ramach tego samego przypadku użycia. Spowoduje to wyjątek IllegalArgumentException podczas tworzenia obiektu konfiguracji.

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ślać rozdzielczość, aby umożliwić wydajniejsze przetwarzanie obrazów (np. mały lub średni obraz w zależności od 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 implementuje też te interfejsy, aby obsługiwać operacje na aparacie 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 CameraControlCameraInfo 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 powią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() do CameraInfo.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()ZoomState.getLinearZoom(). Jest to przydatne, jeśli chcesz wyświetlić tekst współczynnika powiększenia obok suwaka. Wystarczy, że będziesz obserwować ZoomState LiveData, aby aktualizować oba te parametry bez konieczności przeprowadzania konwersji.

ListenableFuture zwracany przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień po wykonaniu 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ść za pomocą funkcji 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. flashModeImageCapture 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 wykorzystywane 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,15. 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. Przykładowy 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 poprzednim kodzie, startFocusAndMetering() przyjmuje FocusMeteringAction składający się z jednego MeteringPoint dla regionów pomiaru AF/AE/AWB i drugiego punktu pomiaru MeteringPoint tylko dla AF i AE.

Wewnętrznie CameraX konwertuje go na Camera2MeteringRectanglesi ustawia odpowiednie parametryCONTROL_AF_REGIONSCONTROL_AE_REGIONSCONTROL_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 zostanie wykonane 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ą ExposureState wartość, 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

  • Pierwsze kroki z CameraX
  • Przykładowy kod

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

    Grupa dyskusyjna CameraX na Androida