Opcje konfiguracji

Konfigurujesz każdy przypadek użycia AparatuX, aby kontrolować różne aspekty jego działań.

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 zastosowaniami znajdziesz w artykułach Wdrażanie podglądu, Analiza obrazówPrzechwytywanie obrazu.

CameraXConfig

W celu uproszczenia CameraX zawiera 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 własnymi konfiguracjami.
  2. Zaimplementuj interfejs CameraXConfig.Provider w Application i zwróć obiekt CameraXConfig w getCameraXConfig().
  3. Dodaj klasę Application do pliku AndroidManifest.xml zgodnie z opisem 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 aplikacja musi znać konfigurację CameraX po jej skonfigurowaniu.

Ogranicznik aparatu

Podczas pierwszego wywołania funkcji ProcessCameraProvider.getInstance() CameraX wylicza i wysyła zapytania o właściwości kamer dostępnych na urządzeniu. Ponieważ CameraX musi komunikować się ze sprzętowymi komponentami, ten proces może zająć sporo czasu w przypadku każdej kamery, zwłaszcza na urządzeniach niskiego poziomu. Jeśli Twoja aplikacja korzysta tylko z określonych kamer na urządzeniu, na przykład domyślnej przedniej kamery, możesz skonfigurować CameraX tak, aby ignorował inne kamery. Dzięki temu możesz skrócić czas uruchamiania kamer, których używa aplikacja.

Jeśli CameraSelector przekazane do CameraXConfig.Builder.setAvailableCamerasLimiter() odfiltrowuje kamerę, CameraX zachowuje się tak, jakby ta kamera nie istniała. Na przykład ten kod ogranicza aplikację do korzystania tylko z domyślnego tylnego aparatu urządzenia:

Kotlin

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

Wątki

Wiele interfejsów API platformy, na których opiera się CameraX, wymaga blokowania komunikacji międzyprocesowej (IPC) z urządzeniami, która może czasami trwać setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko 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. Do wykonywania tych zadań CameraX przydziela wewnętrzny Executor i zarządza nim. Jeśli jednak Twoja aplikacja wymaga ściślejszej kontroli nad wątkami, użyj CameraXConfig.Builder.setCameraExecutor().

Moduł obsługi algorytmu szeregowania

Moduł obsługi algorytmu szeregowania służy do planowania zadań wewnętrznych w stałych odstępach czasu, na przykład ponawianie próby otwarcia kamery, gdy jest ona niedostępna. Ten moduł nie wykonuje zadań, tylko przekazuje je do modułu wykonawczego 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 do wykonywania tych zadań wewnętrzny HandlerThread i nim zarządza, ale możesz zastąpić go ustawieniem CameraXConfig.Builder.setSchedulerHandler().

Logowanie

Logowanie w CameraX umożliwia aplikacjom filtrowanie komunikatów logcat, ponieważ dobrze jest unikać tych 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 pewne formaty są dostępne tylko przy określonych współczynnikach proporcji.
  • Urządzenie preferuje kodowanie JPEG lub wideo „nearest mod16”. Więcej informacji znajdziesz w sekcji SCALER_STREAM_CONFIGURATION_MAP.

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

Obrót

Domyślnie podczas tworzenia przykładu zastosowania 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ę wyświetlać w podglądzie. Aby obsługiwać urządzenia z wieloma wyświetlaczami, możesz zmienić rotację na wartość niestandardową, przekazując bieżący kierunek wyświetlania podczas konfigurowania obiektów przypadku użycia lub dynamicznie po ich utworzeniu.

Aplikacja może ustawić docelową orientację za pomocą ustawień konfiguracji. Dzięki temu może aktualizować ustawienia rotacji za pomocą metod z interfejsów API przypadku użycia (takich jak ImageAnalysis.setTargetRotation()), nawet gdy cykl życia jest uruchomiony. Możesz z niego korzystać, gdy aplikacja jest zablokowana w trybie pionowym – dzięki temu po obróceniu nie następuje ponowna konfiguracja – ale w przypadku zastosowania zdjęcia lub analizy trzeba pamiętać o bieżącym obróceniu 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 rotacji danych w każdym przypadku użycia następuje bezpośrednia rotacja danych obrazu lub dostarczanie metadanych obrotu konsumentom danych obrazów nieobróconych.

  • Podgląd: dane wyjściowe metadanych są dostarczane w taki sposób, aby rotacja docelowej rozdzielczości była znana za pomocą Preview.getTargetRotation().
  • Analiza obrazu: podawane są metadane, dzięki czemu współrzędne bufora obrazu są znane względem współrzędnych wyświetlania.
  • ImageCapture: metadane obrazu Exif, bufor lub zarówno bufor, jak i metadane są zmieniane, by uwzględnić ustawienie rotacji. 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.

Fragment kodu poniżej 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. Zwykle, aby uzyskać efekt WYSIWYG, trzeba skonfigurować widoczny obszar na podstawie przypadku użycia podglądu. Prosty sposób na uzyskanie widocznego obszaru to użycie PreviewView.

Poniższe fragmenty kodu pokazują, 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 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 output.

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

Począwszy od CameraX 1.3, można również wybrać wiele 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 używasz funkcji Równoczesny aparat, urządzenie może jednocześnie obsługiwać 2 aparaty z różnymi obiektywami lub 2 tylne aparaty jednocześnie. Ten blok kodu pokazuje, jak ustawić 2 kamery przy wywołaniu funkcji bindToLifecycle i jak pobierać obiekty Camera 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ć aplikacji CameraX na ustawianie 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.

Przypadki użycia mają konfigurowalny współczynnik proporcji, co pozwala aplikacji określić odpowiedni format na podstawie projektu UI. Wyjście CameraX jest tworzone w taki sposób, aby jak najdokładniej dopasować format obrazu do wymaganego, w miarę możliwości urządzenia. Jeśli nie ma obsługiwanego rozwiązania dopasowania do wyrażenia, wybierane jest to, które spełnia najwięcej warunków. Aplikacja określa wygląd aparatu w aplikacji, a CameraX określa najlepsze ustawienia rozdzielczości aparatu, aby spełnić te wymagania na różnych urządzeniach.

Aplikacja może na przykład wykonywać dowolne z tych czynności:

  • 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 Współczynnik proporcji: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość wewnętrznej powierzchni. Metadane umożliwiają kadrowanie, skalowanie i obracanie widoku w celu dostosowania do docelowego współczynnika proporcji.
Rozdzielczość domyślna: najwyższa rozdzielczość podglądu lub najwyższa rozdzielczość preferowana na urządzeniu, która odpowiada współczynnikowi proporcji podglądu.
Maksymalna rozdzielczość: rozmiar podglądu, który jest najbardziej zbliżony do rozdzielczości ekranu urządzenia lub 1080p (1920 x 1080), w zależności od tego, która wartość jest mniejsza.
Analiza obrazu Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość wewnętrznej powierzchni.
Domyślna rozdzielczość: domyślne ustawienie docelowej rozdzielczości to 640 x 480. Dostosowanie zarówno docelowej rozdzielczości, jak i odpowiedniego współczynnika proporcji skutkuje uzyskaniem najlepszej obsługiwanej rozdzielczości.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie YUV_420_888, pobierana z StreamConfigurationMap.getOutputSizes(). Domyślnie docelową rozdzielczość ustawia się na 640 x 480, więc jeśli chcesz użyć rozdzielczości większej niż 640 x 480, musisz użyć opcji setTargetResolution() i setTargetAspectRatio(), aby wybrać najbliższą obsługiwaną rozdzielczość.
Robienie zdjęć Format obrazu: format obrazu, który najlepiej pasuje do ustawienia. Rozdzielczość wewnętrznej powierzchni.
Domyślna rozdzielczość: najwyższa dostępna rozdzielczość lub najwyższa preferowana rozdzielczość urządzenia, która odpowiada formatowi obrazu ImageCapture.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby pobrać te dane, użyj kodu StreamConfigurationMap.getOutputSizes().

Określ rozdzielczość

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

Kotlin

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

Java

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

Nie można ustawić 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, która jest równa lub większa od docelowej, wybierana jest najbliższa dostępna rozdzielczość mniejsza od docelowej. Rozdzielczości o tym samym współczynniku proporcji w polu Size mają wyższy priorytet niż rozdzielczość o różnych współczynnikach proporcji.

Aparat X stosuje najlepszą odpowiednią rozdzielczość na podstawie liczby żą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 główną potrzebą aplikacji jest określenie rozdzielczości w celu wydajniejszego przetwarzania obrazu (np. małe lub średnie obrazy w zależności od możliwości przetwarzania na urządzeniu), użyj funkcji setTargetResolution(Size resolution).

Jeśli Twoja aplikacja wymaga dokładnej rozdzielczości, zapoznaj się z tabelą w sekcji createCaptureSession(), aby sprawdzić, jakie maksymalne rozdzielczości są obsługiwane przez poszczególne poziomy sprzętowe. 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 kamerze wspólne dla wszystkich powiązanych przypadków użycia:

  • CameraControl pozwala konfigurować typowe funkcje aparatu.
  • CameraInfo umożliwia wysyłanie zapytań dotyczących stanu tych typowych funkcji aparatu.

Te funkcje aparatu są obsługiwane przez CameraControl:

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

Pobieranie wystąpień interfejsów CameraControl i CameraInfo

Pobieraj wystąpienia CameraControl i CameraInfo za pomocą obiektu Camera zwracanego przez ProcessCameraProvider.bindToLifecycle(). Przykładowy kod:

Kotlin

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

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

Java

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

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

Po wywołaniu funkcji bindToLifecycle() możesz na przykład przesłać operacje powiększania i inne operacje CameraControl. Po zatrzymaniu lub usunięciu aktywności używanej do 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 obserwujesz ZoomState LiveData, aby aktualizować oba te parametry bez konieczności przeprowadzania konwersji.

ListenableFuture zwracany przez oba interfejsy API umożliwia aplikacjom otrzymywanie powiadomień po 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 zostanie natychmiast przerwana.

Latarka

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

CameraInfo.getTorchState() może służyć do wysyłania zapytań o obecny stan latarki. Aby sprawdzić, czy latarka jest dostępna, możesz sprawdzić zwróconą wartość: CameraInfo.hasFlashUnit(). W przeciwnym razie wywołanie 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 nagrywania filmów, niezależnie od ustawienia flashMode. flashModeImageCapture działa tylko wtedy, gdy latarka jest wyłączona.

Ostrość i pomiar

CameraControl.startFocusAndMetering() aktywuje autofokus i pomiar ekspozycji, ustawiając regiony pomiaru AF/AE/AWB na podstawie danego obiektu FocusMeteringAction. Jest to często wykorzystywane do implementacji funkcji „dotknij, aby ustawić ostrość” w wielu aplikacjach do obsługi aparatu.

MeteringPoint

Najpierw 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.

Wartość MeteringPoint ma zakres 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. Oto przykładowy kod:

Kotlin

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

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

Jak widać w poprzednim kodzie, startFocusAndMetering() przyjmuje FocusMeteringAction składający się z jednego MeteringPoint dla regionów pomiaru AF/AE/AWB i innego punktu pomiaru MeteringPoint tylko dla AF i AE.

Wewnętrznie CameraX konwertuje go na 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 punktów. Wszystkie pomiary 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 element MeteringPoint jest ignorowany przez aparat X.

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 indeksu dodatnie rozjaśniają obraz, a ujemne – go przyciemniają. Aplikacje mogą wysyłać zapytania o obsługiwany zakres 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 zachowuje tylko ostatnie oczekujące żądanie setExposureCompensationIndex() i wielokrotnie wywołuje tę funkcję, zanim poprzednie żądanie zostanie anulowane.

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

    • Obsługa kontroli 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 aparacie AparatX 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