Opcje konfiguracji

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

Na przykład w przypadku przechwytywania obrazów możesz ustawić docelowy format obrazu i tryb lampy błyskowej. Oto przykład:

Kotlin

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

Java

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

Oprócz opcji konfiguracji w niektórych przypadkach użycia interfejsy API zmieniają ustawienia dynamicznie po utworzeniu przypadku użycia. Informacje o konfiguracji dostosowanej do poszczególnych przypadków użycia znajdziesz w artykułach Implementowanie podglądu, Analizowanie obrazów i Przechwytywanie obrazów.

CameraXConfig,

Dla uproszczenia Aparat X ma domyślne konfiguracje, takie jak wewnętrzne wykonawców i moduły obsługi, które nadają się do większości zastosowań. Jeśli jednak Twoja aplikacja ma specjalne wymagania lub woli dostosowywać te konfiguracje, do tego celu służy CameraXConfig.

Dzięki CameraXConfig aplikacja może wykonywać te czynności:

Model wykorzystania

Aby dowiedzieć się, jak używać właściwości CameraXConfig, wykonaj te czynności:

  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()
   }
}

Jeśli po ustawieniu kamery aplikacja musi znać konfigurację kamery X, zachowaj lokalną kopię obiektu CameraXConfig.

Ogranicznik aparatu

Podczas pierwszego wywoływania funkcji ProcessCameraProvider.getInstance() aparat CameraX wylicza właściwości kamer dostępnych na urządzeniu i wysyła zapytanie do tych parametrów. Aparat CameraX musi komunikować się z komponentami sprzętowymi, więc dla każdej kamery, zwłaszcza w przypadku słabszych urządzeń, proces ten może zająć dużo czasu. Jeśli Twoja aplikacja używa tylko określonych aparatów na urządzeniu, na przykład domyślnego przedniego, możesz skonfigurować ignorowanie innych kamer przez aparat X. Może to skrócić czas oczekiwania na uruchomienie przez aparat, którego używa Twoja aplikacja.

Jeśli parametr CameraSelector przekazany do CameraXConfig.Builder.setAvailableCamerasLimiter() odfiltrowuje kamerę, AparatX zachowuje się tak, jakby nie istniał. Na przykład ten kod ogranicza aplikację tylko do 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) za pomocą sprzętu, którego odpowiedź czasem trwa setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko z wątków w tle, dzięki czemu wątek główny nie jest blokowany, a interfejs pozostaje płynny. CameraX wewnętrznie zarządza wątkami w tle, dzięki czemu to działanie jest przejrzyste. 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 w CameraXConfig.Builder.setCameraExecutor() i CameraXConfig.Builder.setSchedulerHandler().

Wykonawca aparatu

Wykonawca kamery jest używany we wszystkich wewnętrznych wywołaniach interfejsu Camera Platform API oraz w wywołaniach 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ł obsługi nie wykonuje zadań, a jedynie wysyła je do wykonawcy kamery. Czasami jest on też używany na starszych platformach API, które wymagają Handler do obsługi 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. Aparat X obsługuje 4 poziomy rejestrowania: od najbardziej szczegółowego do najpoważniejszego:

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

Szczegółowy opis tych poziomów rejestrowania znajdziesz w dokumentacji logów Androida. Za pomocą CameraXConfig.Builder.setMinimumLoggingLevel(int) ustaw odpowiedni poziom logowania w aplikacji.

Automatyczny wybór

CameraX automatycznie udostępnia funkcje dostosowane do urządzenia, na którym działa Twoja aplikacja. Na przykład Aparat X automatycznie określa najlepszą rozdzielczość do użycia, gdy nie określisz rozdzielczości lub gdy zostanie ona nieobsługiwana. Wszystkie te działania są obsługiwane przez bibliotekę, więc nie musisz pisać kodu przeznaczonego na konkretne urządzenie.

Celem aplikacji CameraX jest pomyślne zainicjowanie sesji kamery. Oznacza to, że w zależności od możliwości urządzenia CameraX zmniejsza rozdzielczość i formaty obrazu. Do naruszenia zabezpieczeń może dojść, ponieważ:

  • Urządzenie nie obsługuje żądanej rozdzielczości.
  • W urządzeniu występują problemy ze zgodnością, np. starsze urządzenia wymagają określonych rozwiązań do prawidłowego działania.
  • Na niektórych urządzeniach pewne formaty są dostępne tylko przy określonych współczynnikach proporcji.
  • W przypadku kodowania JPEG lub wideo urządzenie preferuje tryb „najbliższy mod16”. Więcej informacji: 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 obrót kamery jest ustawiany na podstawie obrotu domyślnego wyświetlacza podczas tworzenia danego przypadku użycia. W tym domyślnym przypadku Aparat X generuje wyniki, które pozwalają aplikacji dopasować się do tego, czego oczekujesz na podglądzie. Możesz zmienić obrót na wartość niestandardową, aby zapewnić obsługę urządzeń z wieloma wyświetlaczami, przekazując bieżącą orientację wyświetlania podczas konfigurowania obiektów przypadku użycia lub dynamicznie po ich utworzeniu.

Aplikacja może ustawić rotację docelową 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 użycia zdjęcia lub analizy trzeba zdawać sobie sprawę z bieżącej orientacji urządzenia. Może być na przykład potrzebna świadomość obrotu, by twarze były prawidłowo zorientowane na potrzeby wykrywania twarzy, a zdjęcia zostały ustawione w orientacji poziomej lub pionowej.

Dane zrobionych obrazów mogą być przechowywane bez informacji o rotacji. Dane Exif zawierają informacje o rotacji, dzięki czemu aplikacje galerii mogą po zapisaniu wyświetlić obraz we właściwej orientacji.

Aby wyświetlić dane podglądu w prawidłowej orientacji, możesz utworzyć przekształcenia, korzystając z wyjściowych metadanych z narzędzia Preview.PreviewOutput().

Przeanalizuj przykładowy kod poniżej, aby dowiedzieć się, jak ustawić rotację w przypadku zdarzenia orientacji:

Kotlin

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

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

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

Java

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

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

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

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

W zależności od 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: dostarczane są dane wyjściowe metadanych, aby możliwa była rotacja rozdzielczości docelowej 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.

Przytnij jako prostokąt

Domyślnie prostokąt przycinania jest pełnym prostokątem bufora. Możesz go dostosować za pomocą ViewPort i UseCaseGroup. Dzięki grupowaniu przypadków użycia i ustawianiu widocznego obszaru Aparat X gwarantuje, że wszystkie przypadki użycia w grupie wskazują na ten sam obszar czujnika kamery.

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 pole bufora widoczne 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 polecenia 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 poprzednim przykładzie dane, które aplikacja uzyskuje ze źródeł ImageAnalysis i ImageCapture, są zgodne z tym, co widzi użytkownik w narzędziu PreviewView, przy założeniu, że typ skali PreviewView jest ustawiony na domyślny FILL_CENTER. Po zastosowaniu prostokąta przycinania i obrócenia do bufora wyjściowego obraz we wszystkich przypadkach użycia jest taki sam, choć może mieć różną rozdzielczość. Więcej informacji o stosowaniu informacji o przekształceniu znajdziesz w artykule o transformowaniu danych wyjściowych.

Wybór aparatu

CameraX automatycznie wybiera aparat najodpowiedniejszy do zastosowań i zastosowań aplikacji. Jeśli chcesz użyć innego urządzenia niż wybrane, masz kilka możliwości:

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)

Wybierz kilka kamer jednocześnie

Począwszy od CameraX 1.3, można również wybrać wiele aparatów jednocześnie. Możesz na przykład powiązać przedni i tylny aparat, aby robić zdjęcia lub nagrywać filmy jednocześnie z obu perspektyw.

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 pozwolić, by aparat X ustawiał rozdzielczość obrazu na podstawie jego możliwości, poziomu sprzętu, przypadku użycia i przekazanych formatów obrazu. Możesz też ustawić określoną rozdzielczość docelową lub format obrazu w przypadkach, które obsługują tę konfigurację.

Automatyczna rozdzielczość

CameraX może automatycznie określać najlepsze ustawienia rozdzielczości na podstawie przypadków użycia określonych w zasadzie cameraProcessProvider.bindToLifecycle(). Jeśli to możliwe, określ wszystkie przypadki użycia, które muszą być uruchomione jednocześnie w ramach 1 sesji w ramach pojedynczego wywołania bindToLifecycle(). AparatX określa rozdzielczości na podstawie zbioru przypadków użycia określonych na podstawie obsługiwanego poziomu sprzętu oraz wersji na danym urządzeniu (jeśli urządzenie przekracza dostępne konfiguracje strumieni lub nie spełnia ich). Dzięki temu aplikacja będzie działać na wielu różnych urządzeniach, a ścieżki kodu dla poszczególnych urządzeń zostaną zminimalizowane.

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

Przypadki użycia mają konfigurowalny współczynnik proporcji, co pozwala aplikacji określić odpowiedni format na podstawie projektu UI. Materiał wyjściowy z aparatu CameraX jest dopasowywany do żądanych formatów obrazu możliwie dokładnie tak, jak obsługuje urządzenie. Jeśli nie jest obsługiwana rozdzielczość dokładnej, wybierana jest ta, która spełnia najwięcej warunków. Aplikacja określa sposób wyświetlania kamery w aplikacji, a CameraX wybiera najlepsze ustawienia rozdzielczości aparatu na różnych urządzeniach.

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

  • W przypadku zastosowania określ docelową rozdzielczość 4:3 lub 16:9
  • Określ niestandardową rozdzielczość, w której AparatX próbuje znaleźć najbliższe dopasowanie
  • Określ współczynnik proporcji kadrowania na potrzeby obrazu ImageCapture

Aparat X automatycznie wybiera rozdzielczość wewnętrznej powierzchni aparatu Camera2. W poniższej tabeli znajdziesz rozwiązania:

Przypadek użycia Rozdzielczość powierzchni wewnętrznej Rozdzielczość danych wyjściowych
Podgląd Współczynnik proporcji: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej. Podane metadane umożliwiają przycinanie, skalowanie i obracanie widoku danych 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, czyli najlepszy rozmiar do rozdzielczości ekranu urządzenia, lub 1080p (1920 x 1080), zależnie od tego, która wartość jest mniejsza.
Analiza obrazu Format obrazu: rozdzielczość, która najlepiej pasuje do ustawienia docelowego. Rozdzielczość powierzchni wewnętrznej.
Rozdzielczość domyślna: domyślne ustawienie rozdzielczości docelowej to 640 x 480. Jeśli dostosujesz zarówno docelową rozdzielczość, jak i odpowiedni format obrazu, uzyskasz najlepszą obsługiwaną rozdzielczość.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie YUV_420_888, pobierana z StreamConfigurationMap.getOutputSizes(). Domyślna rozdzielczość jest ustawiona na 640 x 480, więc jeśli chcesz uzyskać rozdzielczość większą niż 640 x 480, użyj setTargetResolution() i setTargetAspectRatio(), aby uzyskać najbliższą z obsługiwanych rozdzielczości.
Robienie zdjęć Format obrazu: format obrazu, który najlepiej pasuje do ustawienia. Rozdzielczość powierzchni wewnętrznej.
Rozdzielczość domyślna: najwyższa dostępna rozdzielczość lub najwyższa rozdzielczość preferowana na urządzeniu, która jest zgodna ze współczynnikiem proporcji narzędzia ImageCapture.
Maksymalna rozdzielczość: maksymalna rozdzielczość wyjściowa aparatu w formacie JPEG. Aby to zrobić, użyj zadania StreamConfigurationMap.getOutputSizes().

Określ rozdzielczość

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

Po obróceniu obsługiwanych rozmiarów zgodnie z rotacją docelową określ rozdzielczość Size w ramce współrzędnych. Na przykład urządzenie z naturalną orientacją pionową w naturalnej rotacji celu i żądające obrazu pionowego może wybrać rozdzielczość 480 x 640, a to samo urządzenie z obróconą o 90 stopni i kierowaniem poziomym może mieć wartość 640 x 480.

Docelowa rozdzielczość ma na celu ustalenie minimalnej granicy rozdzielczości obrazu. Rzeczywista rozdzielczość obrazu to najbliższa dostępna rozdzielczość, która nie jest mniejsza od docelowej (określonej przez 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łówną potrzebą jest dostosowanie formatu obrazu, określ tylko setTargetAspectRatio, a AparatX określi konkretną rozdzielczość odpowiednią do 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 są obsługiwane przez bieżące urządzenie, zobacz StreamConfigurationMap.getOutputSizes(int).

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

Steruj wyjściem kamery

Oprócz możliwości konfigurowania wyjścia z kamery zgodnie z potrzebami danego przypadku użycia CameraX ma również implementowane następujące interfejsy do obsługi operacji kamery typowych dla wszystkich powiązanych przypadków użycia:

  • CameraControl umożliwia konfigurowanie typowych funkcji aparatu.
  • CameraInfo umożliwia wysyłanie zapytań o stany tych popularnych funkcji kamery.

Następujące funkcje aparatu są obsługiwane przez funkcję CameraControl:

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

Pobieranie instancji CameraControl i CameraInfo

Pobierz instancje CameraControl i CameraInfo przy użyciu obiektu Camera zwróconego przez ProcessCameraProvider.bindToLifecycle(). Oto przykład:

Kotlin

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

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

Java

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

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

Na przykład możesz przesłać powiększenie i inne operacje CameraControl po wywołaniu bindToLifecycle(). Gdy zatrzymasz lub zniszczysz działanie służące do powiązania instancji kamery, funkcja CameraControl nie może już wykonywać operacji i zwraca błąd ListenableFuture.

Zoom

CameraControl umożliwia zmianę poziomu powiększenia na dwa sposoby:

  • setZoomRatio() ustawia powiększenie.

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

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

    Zaletą powiększenia liniowego jest to, że skaluje pole widzenia w miarę zmian powiększenia. Dzięki temu idealnie nadaje się do użycia w widoku Slider.

CameraInfo.getZoomState() zwraca LiveData bieżącego stanu powiększenia. Wartość zmienia się po zainicjowaniu kamery albo ustawieniu poziomu powiększenia przy użyciu setZoomRatio() lub setLinearZoom(). Wywołanie jednej z metod powoduje ustawienie wartości zwrotnych ZoomState.getZoomRatio() i 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 zaktualizować obie te wartości bez konieczności wykonywania konwersji.

ListenableFuture zwracany przez oba interfejsy API daje aplikacjom możliwość otrzymywania powiadomień o zrealizowaniu powtarzającego się żądania z określoną wartością powiększenia. Dodatkowo, jeśli ustawisz nową wartość powiększenia, gdy poprzednia operacja jest nadal wykonywana, jej ListenableFuture od razu zakończy się niepowodzeniem.

Latarka

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

CameraInfo.getTorchState() można używać do wysyłania zapytań o bieżący stan pochodnej. Możesz sprawdzić wartość zwracaną przez funkcję CameraInfo.hasFlashUnit(), by ustalić, czy latarka jest dostępna. W przeciwnym razie wywołanie funkcji CameraControl.enableTorch(boolean) powoduje natychmiastowe zakończenie zwróconych działań (ListenableFuture) z nieudanym wynikiem i ustawia stan lampy na TorchState.OFF.

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

Ostrość i pomiar

CameraControl.startFocusAndMetering() aktywuje autofokus i pomiar ekspozycji, ustawiając regiony pomiaru AF/AE/AWB na podstawie danego obiektu FocusMeteringAction. Często używa się go do wdrożenia funkcji „Dotknij, by ustawić ostrość” w wielu aplikacjach aparatu.

Punkt mierniczy

Zacznij od utworzenia MeteringPoint z użyciem MeteringPointFactory.createPoint(float x, float y, float size). MeteringPoint reprezentuje pojedynczy punkt kamery Surface. Dane są przechowywane w znormalizowanej postaci, więc można je łatwo przekonwertować na współrzędne czujnika w celu określenia regionów AF/AE/AWB.

Rozmiar elementu MeteringPoint mieści się w zakresie od 0 do 1, a rozmiar domyślny to 0,15f. Gdy wywołujesz MeteringPointFactory.createPoint(float x, float y, float size), CameraX tworzy prostokątny obszar wyśrodkowany na (x, y) dla podanego size.

Ten kod pokazuje, jak utworzyć MeteringPoint:

Kotlin

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

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

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

startFocusAndMetering i FocusMeteringAction

Aby wywołać metodę startFocusAndMetering(), aplikacje muszą utworzyć FocusMeteringAction, który składa się z co najmniej 1 elementu MeteringPoints z opcjonalnymi kombinacjami trybu pomiaru z: FLAG_AF, FLAG_AE i 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 pokazano w poprzednim kodzie, startFocusAndMetering() obejmuje FocusMeteringAction, który składa się z 1 MeteringPoint dla regionów pomiaru AF/AE/AWB i drugiego MeteringPoint dla 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 na żądanie zapisu.

Ponieważ nie każde urządzenie obsługuje AF/AE/AWB i wiele regionów, AparatX wykonuje FocusMeteringAction z najwyższą starannością. Aparat X wykorzystuje maksymalną liczbę obsługiwanych punktów MeteringPoints w kolejności ich dodawania. Wszystkie punkty MeteringPoint dodane po maksymalnej liczbie punktów są ignorowane. Jeśli na przykład do FocusMeteringAction zostały dodane 3 punkty MeteringPoint na platformie obsługującej tylko 2, używane są tylko 2 pierwsze punkty MeteringPoint. Ostatni element MeteringPoint jest ignorowany przez aparat X.

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ę dla bieżących warunków zdjęcia:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

AparatX udostępnia funkcję Camera.CameraControl.setExposureCompensationIndex(), która umożliwia ustawienie kompensacji ekspozycji jako wartości indeksu.

Wartości indeksu dodatnie rozjaśniają obraz, a ujemne – go przyciemniają. Aplikacje mogą wysyłać zapytania do obsługiwanego zakresu za pomocą parametru CameraInfo.ExposureState.exposureCompensationRange() opisanego w następnej sekcji. Jeśli ta wartość jest obsługiwana, zwracana wartość ListenableFuture jest kończona po włączeniu wartości w żądaniu przechwytywania. Jeśli podany indeks wykracza poza obsługiwany zakres, funkcja setExposureCompensationIndex() sprawia, że zwrócona ListenableFuture kończy się niepowodzeniem.

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ą wartość ExposureState, w tym:

    • Obsługa kontroli kompensacji ekspozycji.
    • Bieżący indeks kompensacji ekspozycji.
    • Zakres indeksu kompensacji ekspozycji.
    • Krok kompensacji ekspozycji używany do obliczania wartości kompensacji ekspozycji.

Na przykład ten kod inicjuje ustawienia ekspozycji SeekBar 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 AparatemX
  • Przykładowy kod

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

    Grupa dyskusyjna dotyczące aparatu Android CameraX