Opcje konfiguracji

Każdy przypadek użycia CameraX konfigurujesz tak, aby kontrolować różne aspekty jego działania.

Na przykład w przypadku robienia zdjęć możesz ustawić docelowy współczynnik proporcji i tryb lampy błyskowej. Poniższy kod pokazuje jeden z przykładów:

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

Oprócz opcji konfiguracji niektóre przypadki użycia udostępniają interfejsy API, które umożliwiają dynamiczną zmianę ustawień po utworzeniu przypadku użycia. Informacje o konfiguracji, która jest specyficzna dla poszczególnych przypadków użycia, znajdziesz w sekcjach Wdrażanie podglądu, Analizowanie obrazówPrzechwytywanie obrazów.

CameraXConfig

Aby uprościć obsługę, CameraX ma domyślne konfiguracje, takie jak wewnętrzne moduły wykonawcze i moduły obsługi, które są odpowiednie w większości scenariuszy użycia. Jeśli jednak Twoja aplikacja ma specjalne wymagania lub woli dostosować te konfiguracje, możesz użyć interfejsu CameraXConfig.

Dzięki CameraXConfig aplikacja może:

Model wykorzystania

Poniżej znajdziesz opis korzystania z CameraXConfig:

  1. Utwórz obiekt CameraXConfig ze skonfigurowanymi przez siebie ustawieniami.
  2. Zaimplementuj interfejs CameraXConfig.ProviderApplication i zwróć obiekt CameraXConfiggetCameraXConfig().
  3. Dodaj zajęcia Application do pliku AndroidManifest.xml zgodnie z opisem tutaj.

Na przykład poniższy przykładowy kod ogranicza rejestrowanie w CameraX tylko do komunikatów o błędach:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

Zachowaj lokalną kopię obiektu CameraXConfig, jeśli aplikacja musi znać konfigurację CameraX po jej ustawieniu.

Ogranicznik kamery

Podczas pierwszego wywołania funkcji ProcessCameraProvider.getInstance() CameraX wylicza i sprawdza charakterystykę kamer dostępnych na urządzeniu. Ponieważ CameraX musi komunikować się z komponentami sprzętowymi, ten proces może zająć sporo czasu w przypadku każdego aparatu, zwłaszcza na urządzeniach z niższej półki. Jeśli aplikacja używa tylko określonych aparatów na urządzeniu, np. domyślnego przedniego aparatu, możesz skonfigurować CameraX tak, aby ignorował inne aparaty. Może to skrócić czas oczekiwania na uruchomienie aparatów używanych przez aplikację.

Jeśli wartość CameraSelector przekazana do CameraXConfig.Builder.setAvailableCamerasLimiter() odfiltruje kamerę, CameraX będzie się zachowywać tak, jakby ta kamera nie istniała. Na przykład poniższy kod ogranicza aplikację do korzystania tylko z domyślnego tylnego aparatu urządzenia:

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

Wątki

Wiele interfejsów API platformy, na których opiera się CameraX, wymaga blokowania komunikacji międzyprocesowej (IPC) ze sprzętem, co czasami może trwać setki milisekund. Dlatego CameraX wywołuje te interfejsy API tylko z wątków w tle, aby wątek główny nie był blokowany, a interfejs użytkownika działał płynnie. CameraX wewnętrznie zarządza tymi wątkami w tle, dzięki czemu to zachowanie jest niewidoczne. Niektóre aplikacje wymagają jednak ścisłej kontroli wątków. CameraXConfig umożliwia aplikacji ustawianie wątków w tle, które są używane przez CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler().

Wykonawca aparatu

Wykonawca kamery jest używany do wszystkich wewnętrznych wywołań interfejsu API platformy Camera, a także do wywołań zwrotnych z tych interfejsów API. CameraX przydziela i zarządza wewnętrznym Executor, aby wykonywać te zadania. Jeśli jednak aplikacja wymaga ściślejszej kontroli nad wątkami, użyj CameraXConfig.Builder.setCameraExecutor().

Moduł obsługi harmonogramu

Procedura obsługi harmonogramu służy do planowania wewnętrznych zadań w stałych odstępach czasu, np. ponawiania próby otwarcia kamery, gdy jest niedostępna. Ten moduł obsługi nie wykonuje zadań, a jedynie przekazuje je do wykonawcy kamery. Jest on też czasami używany na starszych platformach API, które wymagają Handler w przypadku wywołań zwrotnych. W takich przypadkach wywołania zwrotne są nadal wysyłane bezpośrednio do wykonawcy kamery. CameraX przydziela i zarządza wewnętrznym HandlerThread do wykonywania tych zadań, ale możesz go zastąpić za pomocą CameraXConfig.Builder.setSchedulerHandler().

Logowanie

Logowanie CameraX umożliwia aplikacjom filtrowanie wiadomości logcat, co może być dobrym rozwiązaniem, aby uniknąć szczegółowych wiadomości w kodzie produkcyjnym. CameraX obsługuje 4 poziomy rejestrowania, od najbardziej szczegółowego do najbardziej krytycznego:

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

Szczegółowe opisy tych poziomów logowania znajdziesz w dokumentacji logów Androida. Użyj CameraXConfig.Builder.setMinimumLoggingLevel(int) , aby ustawić odpowiedni poziom rejestrowania dla aplikacji.

Automatyczny wybór

CameraX automatycznie udostępnia funkcje specyficzne dla urządzenia, na którym działa aplikacja. Na przykład CameraX automatycznie określa najlepszą rozdzielczość, jeśli nie podasz rozdzielczości lub jeśli podana rozdzielczość jest nieobsługiwana. Wszystko to jest obsługiwane przez bibliotekę, co eliminuje konieczność pisania kodu specyficznego dla urządzenia.

Celem CameraX jest pomyślne zainicjowanie sesji aparatu. Oznacza to, że CameraX dostosowuje rozdzielczość i format obrazu do możliwości urządzenia. Może to nastąpić z tych powodów:

  • Urządzenie nie obsługuje żądanej rozdzielczości.
  • Urządzenie ma problemy ze zgodnością, np. starsze urządzenia, które do prawidłowego działania wymagają określonych rozdzielczości.
  • Na niektórych urządzeniach niektóre formaty są dostępne tylko przy określonych proporcjach obrazu.
  • Urządzenie preferuje „najbliższy mod16” w przypadku kodowania JPEG lub wideo. Więcej informacji znajdziesz w sekcji SCALER_STREAM_CONFIGURATION_MAP.

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

Obrót

Domyślnie podczas tworzenia przypadku użycia orientacja kamery jest ustawiana tak, aby odpowiadała orientacji domyślnego wyświetlacza. W tym domyślnym przypadku CameraX generuje dane wyjściowe, aby aplikacja mogła dopasować je do tego, co widzisz w podglądzie. Aby obsługiwać urządzenia z wieloma wyświetlaczami, możesz zmienić rotację na wartość niestandardową, przekazując bieżącą orientację wyświetlacza podczas konfigurowania obiektów przypadków użycia lub dynamicznie po ich utworzeniu.

Aplikacja może ustawić docelową orientację za pomocą ustawień konfiguracji. Może wtedy aktualizować ustawienia rotacji za pomocą metod z interfejsów API przypadków użycia (np. ImageAnalysis.setTargetRotation()), nawet gdy cykl życia jest w stanie działania. Może to być przydatne, gdy aplikacja jest zablokowana w trybie pionowym, więc po obróceniu urządzenia nie następuje zmiana konfiguracji, ale w przypadku zdjęć lub analizy trzeba uwzględnić bieżące obrót urządzenia. Na przykład świadomość rotacji może być potrzebna, aby twarze były prawidłowo zorientowane do wykrywania twarzy lub aby zdjęcia były ustawione w orientacji poziomej lub pionowej.

Dane dotyczące przechwyconych obrazów mogą być przechowywane bez informacji o obrocie. Dane Exif zawierają informacje o obrocie, dzięki czemu aplikacje galerii mogą wyświetlać obraz w prawidłowej orientacji po zapisaniu.

Aby wyświetlać dane podglądu w prawidłowej orientacji, możesz użyć metadanych wyjściowych z Preview.PreviewOutput() do tworzenia przekształceń.

Poniższy przykładowy kod pokazuje, jak ustawić rotację w zdarzeniu orientacji:

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

W zależności od ustawionej rotacji każdy przypadek użycia obraca dane obrazu bezpośrednio lub udostępnia metadane rotacji odbiorcom nieobróconych danych obrazu.

  • Podgląd: dane wyjściowe metadanych są podawane w taki sposób, aby można było określić rotację docelowej rozdzielczości za pomocą Preview.getTargetRotation().
  • ImageAnalysis: dane wyjściowe metadanych są dostarczane, aby współrzędne bufora obrazu były znane w stosunku do współrzędnych wyświetlania.
  • ImageCapture: metadane EXIF obrazu, bufor lub zarówno bufor, jak i metadane są zmieniane, aby odnotować ustawienie obrotu. Wartość zmieniona zależy od implementacji HAL.

Prostokąt przycięcia

Domyślnie prostokąt przycięcia jest pełnym prostokątem bufora. Możesz go dostosować za pomocą elementów ViewPortUseCaseGroup. Grupując przypadki użycia i ustawiając obszar wyświetlania, CameraX gwarantuje, że prostokąty przycięcia wszystkich przypadków użycia w grupie wskazują ten sam obszar na matrycy aparatu.

Poniższy fragment kodu pokazuje, jak używać tych 2 klas:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort określa widoczny dla użytkowników prostokąt bufora. Następnie CameraX oblicza największy możliwy prostokąt przycięcia na podstawie właściwości widocznego obszaru i dołączonych przypadków użycia. Aby uzyskać efekt WYSIWYG, możesz zwykle skonfigurować widoczny obszar na podstawie przypadku użycia podglądu. Prostym sposobem na uzyskanie widocznego obszaru jest użycie PreviewView.

Poniższe fragmenty kodu pokazują, jak uzyskać obiekt ViewPort:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

W powyższym przykładzie dane, które aplikacja otrzymuje z ImageAnalysisImageCapture, są zgodne z tym, co widzi użytkownik w PreviewView, pod warunkiem że typ skali PreviewView jest ustawiony na domyślny typ FILL_CENTER. Po zastosowaniu prostokąta przycinania i obrotu do bufora wyjściowego obraz we wszystkich przypadkach użycia jest taki sam, choć może mieć inną rozdzielczość. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w artykule Przekształcanie danych wyjściowych.

Wybór kamery

CameraX automatycznie wybiera najlepsze urządzenie z aparatem do wymagań i przypadków użycia aplikacji. Jeśli chcesz użyć innego urządzenia niż to, które zostało wybrane dla Ciebie, masz kilka opcji:

Poniższy przykładowy kod pokazuje, jak utworzyć CameraSelector, aby wpłynąć na wybór urządzenia:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

Wybieranie wielu kamer jednocześnie

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

Gdy korzystasz z funkcji jednoczesnego używania aparatów, urządzenie może obsługiwać 2 aparaty z obiektywami skierowanymi w różnych kierunkach lub 2 aparaty tylne jednocześnie. Poniższy blok kodu pokazuje, jak ustawić 2 kamery podczas wywoływania funkcji bindToLifecycle oraz jak uzyskać oba obiekty Camera z zwróconego obiektu ConcurrentCamera.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

Rozdzielczość aparatu

Możesz zezwolić CameraX na ustawienie rozdzielczości obrazu na podstawie kombinacji możliwości urządzenia, obsługiwanego poziomu sprzętu, przypadku użycia i podanego współczynnika proporcji. Możesz też ustawić konkretną rozdzielczość docelową lub określony współczynnik proporcji w przypadkach użycia, które obsługują taką konfigurację.

Automatyczne rozwiązywanie problemów

CameraX może automatycznie określać najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w cameraProcessProvider.bindToLifecycle(). Jeśli to możliwe, w jednym wywołaniu bindToLifecycle() określ wszystkie przypadki użycia, które mają być uruchamiane jednocześnie w ramach jednej sesji. CameraX określa rozdzielczości na podstawie zestawu przypadków użycia powiązanych z obsługiwanym poziomem sprzętowym urządzenia i uwzględniając różnice specyficzne dla urządzenia (gdy urządzenie przekracza lub nie spełnia dostępnych konfiguracji strumienia). Chodzi o to, aby aplikacja działała na wielu różnych urządzeniach przy jednoczesnym zminimalizowaniu ścieżek kodu specyficznych dla danego urządzenia.

Domyślny format obrazu w przypadku przechwytywania i analizowania obrazów to 4:3.

Przypadki użycia mają konfigurowalne proporcje, dzięki czemu aplikacja może określać pożądane proporcje na podstawie projektu interfejsu. Dane wyjściowe CameraX są generowane tak, aby jak najdokładniej odpowiadały żądanym formatom obrazu, w zakresie obsługiwanym przez urządzenie. Jeśli nie ma rozdzielczości o dokładnym dopasowaniu, wybierana jest ta, która spełnia najwięcej warunków. Aplikacja określa więc, jak aparat ma wyglądać w aplikacji, a CameraX ustala najlepsze ustawienia rozdzielczości aparatu, aby spełnić te wymagania na różnych urządzeniach.

Aplikacja może na przykład:

  • Określanie docelowej rozdzielczości 4:3 lub 16:9 w przypadku danego zastosowania
  • określić niestandardową rozdzielczość, do której CameraX próbuje znaleźć najbliższe dopasowanie;
  • Określ format przycinania dla ImageCapture

CameraX automatycznie wybiera wewnętrzne rozdzielczości powierzchni Camera2. W tabeli poniżej znajdziesz rozwiązania:

Przypadek użycia Rozdzielczość powierzchni wewnętrznej Rozdzielczość danych wyjściowych
Podgląd Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej. Metadane są udostępniane, aby umożliwić przycięcie, skalowanie i obracanie widoku do docelowego współczynnika proporcji.
Domyślna rozdzielczość: najwyższa rozdzielczość podglądu lub najwyższa rozdzielczość preferowana przez urządzenie, która pasuje do proporcji podglądu.
Maksymalna rozdzielczość: rozmiar podglądu, czyli najlepszy rozmiar dopasowany do rozdzielczości ekranu urządzenia lub do 1080p (1920 x 1080), w zależności od tego, który z nich jest mniejszy.
Analiza obrazu Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej.
Domyślna rozdzielczość: domyślne ustawienie rozdzielczości docelowej to 640x480. Dostosowanie zarówno rozdzielczości docelowej, jak i odpowiedniego formatu obrazu spowoduje uzyskanie najlepiej obsługiwanej rozdzielczości.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa urządzenia z aparatem w formacie YUV_420_888, która jest pobierana z StreamConfigurationMap.getOutputSizes(). Rozdzielczość docelowa jest domyślnie ustawiona na 640 x 480, więc jeśli chcesz uzyskać rozdzielczość większą niż 640 x 480, musisz użyć setTargetResolution() i setTargetAspectRatio() , aby uzyskać najbliższą obsługiwaną rozdzielczość.
Przechwytywanie obrazu Współczynnik proporcji: współczynnik proporcji, który najlepiej pasuje do ustawienia. Rozdzielczość powierzchni wewnętrznej.
Domyślna rozdzielczość: najwyższa dostępna rozdzielczość lub najwyższa rozdzielczość preferowana przez urządzenie, która pasuje do współczynnika proporcji ImageCapture.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby to zrobić, użyj StreamConfigurationMap.getOutputSizes().

Określanie rozdzielczości

Podczas tworzenia przypadków użycia możesz ustawić konkretne rozdzielczości za pomocą metody setTargetResolution(Size resolution), jak pokazano w tym przykładowym kodzie:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

Nie możesz ustawić zarówno docelowego współczynnika proporcji, jak i docelowej rozdzielczości w tym samym przypadku użycia. Podczas tworzenia obiektu konfiguracji spowoduje to zgłoszenie wyjątku IllegalArgumentException.

Wyraź rozdzielczość Size w układzie współrzędnych po obróceniu obsługiwanych rozmiarów o docelowy kąt obrotu. Na przykład urządzenie z orientacją pionową w naturalnym ustawieniu, które żąda obrazu w orientacji pionowej, może określić rozmiar 480 x 640. To samo urządzenie obrócone o 90 stopni i kierowane na orientację poziomą może określić rozmiar 640 x 480.

Rozdzielczość docelowa określa minimalną rozdzielczość obrazu. Rzeczywista rozdzielczość obrazu to najbliższa dostępna rozdzielczość, która nie jest mniejsza od rozdzielczości docelowej, zgodnie z implementacją aparatu.

Jeśli jednak nie ma rozdzielczości równej lub większej od docelowej, wybierana jest najbliższa dostępna rozdzielczość mniejsza od docelowej. Rozdzielczości o tym samym formacie obrazu co podany plik Size mają wyższy priorytet niż rozdzielczości o innych formatach obrazu.

CameraX stosuje najbardziej odpowiednią rozdzielczość na podstawie żądań. Jeśli głównym celem jest spełnienie wymagań dotyczących współczynnika proporcji, podaj tylko setTargetAspectRatio, a CameraX określi odpowiednią rozdzielczość na podstawie urządzenia. Jeśli głównym celem aplikacji jest określenie rozdzielczości, aby zwiększyć wydajność przetwarzania obrazu (np. małego lub średniego obrazu w zależności od możliwości przetwarzania urządzenia), użyj setTargetResolution(Size resolution).

Jeśli Twoja aplikacja wymaga dokładnej rozdzielczości, zapoznaj się z tabelą w createCaptureSession(), aby dowiedzieć się, jakie maksymalne rozdzielczości są obsługiwane na poszczególnych poziomach sprzętu. Aby sprawdzić, jakie rozdzielczości są obsługiwane przez bieżące urządzenie, kliknij StreamConfigurationMap.getOutputSizes(int).

Jeśli Twoja aplikacja działa na Androidzie 10 lub nowszym, możesz użyć isSessionConfigurationSupported() do weryfikacji konkretnego SessionConfiguration.

Sterowanie wyjściem kamery

Oprócz umożliwienia skonfigurowania wyjścia z aparatu zgodnie z potrzebami w każdym indywidualnym przypadku użycia CameraX implementuje też te interfejsy, aby obsługiwać operacje aparatu wspólne dla wszystkich powiązanych przypadków użycia:

  • CameraControl umożliwia konfigurowanie typowych funkcji aparatu.
  • CameraInfo umożliwia sprawdzanie stanów tych popularnych funkcji aparatu.

Oto obsługiwane funkcje aparatu w przypadku CameraControl:

  • Zoom
  • Latarka
  • Ostrość i pomiar światła (dotknij, aby ustawić ostrość)
  • Kompensacja ekspozycji

Uzyskiwanie instancji CameraControl i CameraInfo

Pobierz instancje CameraControlCameraInfo za pomocą obiektu Camera zwróconego przez ProcessCameraProvider.bindToLifecycle(). Poniższy kod zawiera przykład:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

Możesz na przykład przesłać operacje powiększania i inne operacje CameraControl po wywołaniu funkcji bindToLifecycle(). Po zatrzymaniu lub zniszczeniu aktywności używanej do powiązania instancji kamery CameraControl nie może już wykonywać operacji i zwraca nieudaną wartość ListenableFuture.

Zoom

CameraControl udostępnia 2 metody zmiany poziomu powiększenia:

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

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

  • setLinearZoom() ustawia bieżące powiększenie za pomocą liniowej wartości powiększenia w zakresie od 0 do 1,0.

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

CameraInfo.getZoomState() zwraca LiveData z bieżącym stanem powiększenia. Wartość zmienia się po zainicjowaniu aparatu lub jeśli poziom powiększenia zostanie ustawiony za pomocą setZoomRatio() lub setLinearZoom(). Wywołanie dowolnej z tych metod ustawia wartości, które są podstawą parametrów ZoomState.getZoomRatio()ZoomState.getLinearZoom(). Jest to przydatne, jeśli chcesz wyświetlać tekst z wartością powiększenia obok suwaka. Wystarczy obserwować ZoomState LiveData, aby zaktualizować oba typy bez konieczności konwersji.

Wartość ListenableFuture zwracana przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień o zakończeniu powtarzającego się żądania z określoną wartością powiększenia. Jeśli ustawisz nową wartość powiększenia, gdy poprzednia operacja jest jeszcze wykonywana, poprzednia operacja powiększenia natychmiast ListenableFuture zakończy się niepowodzeniemListenableFuture.

Latarka

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

CameraInfo.getTorchState() może służyć do sprawdzania bieżącego stanu latarki. Możesz sprawdzić wartość zwracaną przez funkcję CameraInfo.hasFlashUnit(), aby określić, czy latarka jest dostępna. W przeciwnym razie wywołanie funkcji CameraControl.enableTorch(boolean) spowoduje natychmiastowe zakończenie działania zwróconej funkcji ListenableFuture z wynikiem błędu i ustawi stan latarki na TorchState.OFF.

Gdy latarka jest włączona, pozostaje włączona podczas robienia zdjęć i nagrywania filmów niezależnie od ustawienia flashMode. flashModeImageCapture działa tylko wtedy, gdy latarka jest wyłączona.

Ostrość i pomiar

CameraControl.startFocusAndMetering() wyzwala autofokus i pomiar ekspozycji, ustawiając obszary pomiaru AF/AE/AWB na podstawie podanego parametru FocusMeteringAction. Jest to często używane do implementowania funkcji „kliknij, aby ustawić ostrość” w wielu aplikacjach aparatu.

MeteringPoint

Zacznij od utworzenia MeteringPoint za pomocą MeteringPointFactory.createPoint(float x, float y, float size). Symbol MeteringPoint reprezentuje pojedynczy punkt na kamerzeSurface. Jest on przechowywany w postaci znormalizowanej, dzięki czemu można go łatwo przekształcić na współrzędne czujnika w celu określenia regionów AF/AE/AWB.

Rozmiar MeteringPoint mieści się w zakresie od 0 do 1, a domyślny rozmiar to 0,15f. Podczas wywoływania funkcji MeteringPointFactory.createPoint(float x, float y, float size) biblioteka CameraX tworzy prostokątny region wyśrodkowany w punkcie (x, y) dla podanego parametru size.

Poniższy kod pokazuje, jak utworzyć MeteringPoint:

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)

}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering i FocusMeteringAction

Aby wywołać startFocusAndMetering(), aplikacje muszą utworzyć FocusMeteringAction, który składa się z co najmniej jednego MeteringPoints z opcjonalnymi kombinacjami trybu pomiaru z FLAG_AF, FLAG_AEFLAG_AWB. Poniższy kod ilustruje to zastosowanie:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

Jak widać w powyższym kodzie, funkcja startFocusAndMetering() przyjmuje FocusMeteringAction składający się z jednego MeteringPoint dla regionów pomiarowych AF/AE/AWB i drugiego MeteringPoint tylko dla regionów AF i AE.

Wewnętrznie CameraX przekształca go w Camera2 MeteringRectangles i ustawia odpowiednie parametry CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS w żądaniu przechwytywania.

Nie wszystkie urządzenia obsługują AF/AE/AWB i wiele regionów, więc CameraX wykonuje FocusMeteringAction z najlepszym możliwym wynikiem. CameraX używa maksymalnej liczby obsługiwanych punktów pomiarowych w kolejności, w jakiej zostały dodane. Wszystkie punkty pomiarowe dodane po osiągnięciu maksymalnej liczby są ignorowane. Jeśli np. w przypadku parametru a FocusMeteringAction podasz 3 punkty pomiarowe na platformie obsługującej tylko 2, użyte zostaną tylko pierwsze 2 punkty pomiarowe. Ostatni znak MeteringPoint jest ignorowany przez CameraX.

Kompensacja ekspozycji

Kompensacja ekspozycji jest przydatna, gdy aplikacje muszą dostosować wartości ekspozycji (EV) poza wynik automatycznej ekspozycji (AE). Wartości kompensacji ekspozycji są łączone w ten sposób, aby określić niezbędną ekspozycję w bieżących warunkach obrazu:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX udostępnia funkcję Camera.CameraControl.setExposureCompensationIndex() do ustawiania kompensacji ekspozycji jako wartości indeksu.

Dodatnie wartości indeksu rozjaśniają obraz, a ujemne go przyciemniają. Aplikacje mogą wysyłać zapytania o obsługiwany zakres, wykonując czynności opisane w CameraInfo.ExposureState.exposureCompensationRange() następnej sekcji. Jeśli wartość jest obsługiwana, zwrócony obiekt ListenableFuture zostanie ukończony, gdy wartość zostanie pomyślnie włączona w żądaniu przechwytywania. Jeśli określony indeks wykracza poza obsługiwany zakres, metoda setExposureCompensationIndex() spowoduje natychmiastowe ukończenie zwróconego obiektu ListenableFuture z wynikiem niepowodzenia.

CameraX przechowuje tylko najnowsze oczekujące żądanie setExposureCompensationIndex(), a wielokrotne wywoływanie funkcji przed wykonaniem poprzedniego żądania powoduje jego anulowanie.

Ten fragment kodu ustawia indeks kompensacji ekspozycji i rejestruje wywołanie zwrotne, które zostanie wykonane po zrealizowaniu żądania zmiany ekspozycji:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() pobiera bieżące ExposureState w tym:

    • Możliwość obsługi sterowania kompensacją ekspozycji.
    • Bieżący indeks kompensacji ekspozycji.
    • Zakres indeksu kompensacji ekspozycji.
    • Krok kompensacji ekspozycji używany w obliczeniach wartości kompensacji ekspozycji.

Na przykład poniższy kod inicjuje ustawienia ekspozycjiSeekBar z bieżącymi wartościamiExposureState:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

Dodatkowe materiały

Więcej informacji o CameraX znajdziesz w tych materiałach.

Ćwiczenia z programowania

  • Pierwsze kroki z CameraX
  • Przykładowy kod

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

    Grupa dyskusyjna Android CameraX