Na tej stronie omawiamy architekturę CameraX, w tym jej strukturę, sposoby korzystania z interfejsu API, sposoby pracy z cyklami życia oraz łączenie przypadków użycia.
Struktura CameraX
Za pomocą CameraX możesz komunikować się z kamerą urządzenia w postaci abstrakcji nazywanej przypadkiem użycia. Dostępne są te przypadki użycia:
- Podgląd: akceptuje platformę do wyświetlania podglądu, np.
PreviewView
. - Analiza obrazu: udostępnia bufory dostępne dla procesora do analizy, np. na potrzeby systemów uczących się.
- Robienie zdjęć: robi zdjęcie i zapisuje je.
- Nagrywanie filmów: rejestrowanie obrazu i dźwięku za pomocą aplikacji
VideoCapture
Przypadki użycia mogą być łączone i aktywne jednocześnie. Na przykład aplikacja może umożliwić użytkownikowi wyświetlenie obrazu, który widzi aparat, w ramach przykładu użycia podglądu, mieć możliwość użycia analizy obrazu, która określa, czy osoby na zdjęciu się uśmiechają, i uwzględniać przypadek użycia do robienia zdjęć w celu wykonania zdjęcia od razu.
Model API
Aby korzystać z biblioteki, określ te parametry:
- Pożądany przypadek użycia z opcjami konfiguracji.
- Co zrobić z danymi wyjściowymi, dołączając detektory.
- Zamierzony przepływ, np. kiedy włączyć kamery i kiedy wygenerować dane, przez powiązanie zastosowania z architekturą Androida i cyklami życia.
Aplikację CameraX można napisać na 2 sposoby: za pomocą CameraController
(doskonały, jeśli zależy Ci na najprostszym sposobie korzystania z CameraX) lub CameraProvider
(doskonały, jeśli potrzebujesz większej elastyczności).
Kontroler aparatu
CameraController
zapewnia większość głównych funkcji aparatu CameraX w ramach jednej klasy. Nie wymaga pisania dużych ilości kodu, a oprócz tego automatycznie obsługuje m.in. inicjowanie kamery, zarządzanie przypadkami użycia, obracanie celu, ustawianie ostrości przez dotknięcie, powiększanie i rozsuwanie palcami. Klasa betonowa, która rozszerza CameraController
, to LifecycleCameraController
.
Kotlin
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
Domyślne UseCase
dla elementu CameraController
to Preview
, ImageCapture
i ImageAnalysis
. Aby wyłączyć ImageCapture
lub ImageAnalysis
albo włączyć VideoCapture
, użyj metody setEnabledUseCases()
.
Informacje o innych zastosowaniach CameraController
znajdziesz w przykładach ze skanera kodów QR i w filmie o podstawowych funkcjach CameraController
.
Dostawca aparatu
Interfejs CameraProvider
jest nadal łatwy w użyciu, ale ponieważ w większości konfiguracji zajmuje się deweloper aplikacji, masz więcej możliwości dostosowania konfiguracji, np. włączenie rotacji obrazu wyjściowego lub ustawienie formatu obrazu wyjściowego w usłudze ImageAnalysis
. Możesz też użyć niestandardowego elementu Surface
na potrzeby podglądu aparatu, co zapewnia większą elastyczność, natomiast w przypadku kontrolera CameraController musisz użyć elementu PreviewView
. Użycie istniejącego kodu Surface
może być przydatne, jeśli jest już źródłem danych wejściowych do innych części aplikacji.
Konfigurujesz przypadki użycia za pomocą metod set()
i finalizujesz je za pomocą metody build()
. Każdy obiekt przypadku użycia udostępnia zestaw interfejsów API związanych z danym przypadkiem użycia. Na przykład użycie funkcji przechwytywania obrazu obejmuje wywołanie metody takePicture()
.
Zamiast używać konkretnych wywołań metody uruchamiania i zatrzymywania w onResume()
i onPause()
, aplikacja określa cykl życia, z którym ma zostać powiązana kamera, używając parametru cameraProvider.bindToLifecycle()
.
Ten cykl życia informuje Aparat X o tym, kiedy skonfigurować sesję nagrywania z kamery, i dba o to, aby stan kamery zmieniał się odpowiednio do przejść w cyklu życia.
Kroki implementacji w poszczególnych przypadkach użycia znajdziesz w artykułach Wdrażanie podglądu, Analiza obrazów, Robienie zdjęć i Przechwytywanie filmów.
Przypadek użycia podglądu wchodzi w interakcję z elementem Surface
na potrzeby wyświetlania. Aplikacje tworzą przypadki użycia z opcjami konfiguracji przy użyciu tego kodu:
Kotlin
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
Więcej przykładowego kodu znajdziesz w oficjalnej przykładowej aplikacji CameraX.
Cykle życia aparatu CameraX
CameraX obserwuje cykl życia, aby określić, kiedy otworzyć aparat, utworzyć sesję robienia zdjęć oraz kiedy należy zatrzymać i wyłączyć aparat. Interfejsy API przypadków użycia udostępniają wywołania metod i wywołania zwrotne służące do monitorowania postępu.
Jak wyjaśniliśmy w sekcji Łączenie przypadków użycia, niektóre kombinacje przypadków użycia można powiązać z jednym cyklem życia. Jeśli Twoja aplikacja musi obsługiwać przypadki użycia, których nie można łączyć, możesz wykonać jedną z tych czynności:
- Pogrupuj zgodne przypadki użycia w więcej niż 1 fragment, a potem przełączaj się między fragmentami
- Utwórz niestandardowy komponent cyklu życia i używaj go do ręcznego kontrolowania cyklu życia kamery
Jeśli rozłączysz właścicieli cyklu życia widoku i aparatu (np. jeśli używasz niestandardowego cyklu życia lub fragmentu zachowywania), musisz zadbać o to, aby wszystkie przypadki użycia były niepowiązane z Aparatem X. W tym celu użyj ProcessCameraProvider.unbindAll()
lub usuń powiązanie każdego przypadku użycia z osobna. Ewentualnie, gdy powiążesz przypadki użycia z cyklem życia, możesz zezwolić aplikacji CameraX na zarządzanie otwieraniem i zamykaniem sesji przechwytywania oraz usuwanie powiązań z tymi przypadkiami.
Jeśli wszystkie funkcje aparatu odpowiadają cyklowi życia pojedynczego komponentu identyfikującego cykl życia, np. AppCompatActivity
lub fragmentu AppCompat
, zastosowanie cyklu życia tego komponentu do powiązania we wszystkich pożądanych przypadkach użycia sprawi, że funkcje kamery będą gotowe, gdy komponent śledzący cykl życia będzie aktywny i bezpiecznie zutylizowany – w przeciwnym razie nie będzie używać żadnych zasobów.
Niestandardowy właściciele cyklu życia
W zaawansowanych przypadkach możesz utworzyć niestandardowy LifecycleOwner
, aby umożliwić aplikacji jawne sterowanie cyklem życia sesji CameraX, zamiast łączyć ją ze standardowym Androidem LifecycleOwner
.
Poniższy przykładowy kod ilustruje, jak utworzyć prosty niestandardowy identyfikator cyklu życia właściciela:
Kotlin
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Za pomocą tego komponentu LifecycleOwner
aplikacja może umieszczać przejścia stanu w wybranych punktach kodu. Więcej informacji o wdrażaniu tej funkcji w aplikacji znajdziesz w artykule Wdrażanie elementu niestandardowego cyklu życia właściciela.
Równoczesne przypadki użycia
Przypadki użycia mogą działać równocześnie. Przypadki użycia można powiązać sekwencyjnie z cyklem życia, ale lepiej jest powiązać wszystkie przypadki użycia w jednym wywołaniu do CameraProcessProvider.bindToLifecycle()
. Więcej informacji o sprawdzonych metodach wprowadzania zmian w konfiguracji znajdziesz w artykule Obsługa zmian konfiguracji.
W poniższym przykładowym kodzie aplikacja określa 2 przypadki użycia, które mają zostać utworzone i uruchomione jednocześnie. Określa też cykl życia do użycia w obu przypadkach, aby każdy z nich uruchamiał się i kończył zgodnie z cyklem życia.
Kotlin
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Java
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
Te kombinacje konfiguracji będą obsługiwane (gdy wymagany jest podgląd lub Zrób zdjęcie wideo, ale nie oba jednocześnie):
Podgląd lub przechwytywanie wideo | Robienie zdjęć | Analiza | opisy; |
---|---|---|---|
Udostępnij użytkownikowi podgląd lub nagraj film, zrób zdjęcie i przeanalizuj strumień obrazów. | |||
Zrób zdjęcie i przeanalizuj strumień obrazów. | |||
Udostępnij użytkownikowi podgląd lub nagraj film i zrób zdjęcie. | |||
Udostępnij użytkownikowi podgląd lub nagraj film i przeanalizuj strumień obrazów. |
Gdy wymagane jest zarówno podgląd, jak i przechwytywanie wideo, warunkowo obsługiwane są te kombinacje przypadków użycia:
Podgląd | Nagrywanie filmów | Robienie zdjęć | Analiza | Wymagania specjalne |
---|---|---|---|---|
Gwarancja na wszystkie kamery | ||||
Kamera: LIMITED (lub lepsza). | ||||
Kamera LEVEL_3 (lub lepsza). |
Reklamy
- Każdy przypadek użycia może działać we własnym zakresie. Na przykład aplikacja może nagrywać film bez korzystania z podglądu.
- Gdy rozszerzenia są włączone, działa tylko kombinacja
ImageCapture
iPreview
. W zależności od wdrożenia OEM nie można również dodać atrybutuImageAnalysis
. Nie można włączyć rozszerzeń w przypadku użyciaVideoCapture
. Więcej informacji znajdziesz w dokumentacji dotyczącej rozszerzeń. - W zależności od możliwości kamery niektóre z nich mogą obsługiwać tę samą kombinację w trybach o niższej rozdzielczości, ale nie w wyższych.
Obsługiwany poziom sprzętu można pobrać z Camera2CameraInfo
. Ten kod pozwala na przykład sprawdzić, czy domyślnym tylnym aparatem jest LEVEL_3
:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
Uprawnienia
Twoja aplikacja będzie potrzebować uprawnienia CAMERA
. Aby zapisywać obrazy w plikach, musisz też mieć uprawnienie WRITE_EXTERNAL_STORAGE
(z wyjątkiem urządzeń z Androidem 10 lub nowszym).
Więcej informacji o konfigurowaniu uprawnień aplikacji znajdziesz w artykule Wysyłanie prośby o uprawnienia aplikacji.
Wymagania
Aplikacja CameraX ma następujące minimalne wymagania dotyczące wersji:
- Poziom 21 interfejsu Android API
- Komponenty w architekturze Androida 1.1.1
W przypadku aktywności uwzględniających cykl życia użyj FragmentActivity
lub AppCompatActivity
.
Deklarowanie zależności
Aby dodać zależność od CameraX, musisz dodać do projektu repozytorium Google Maven.
Otwórz plik settings.gradle
swojego projektu i dodaj repozytorium google()
w sposób pokazany poniżej:
Odlotowy
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Kotlin
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Na końcu bloku dotyczącego Androida dodaj:
Odlotowy
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Kotlin
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Dodaj te elementy do pliku build.gradle
aplikacji w każdym module:
Odlotowy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.4.0-alpha05" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.4.0-alpha05" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
Więcej informacji o konfigurowaniu aplikacji pod kątem zgodności z tymi wymaganiami znajdziesz w sekcji Deklarowanie zależności.
Interoperacyjność aplikacji CameraX z Aparatem2
Aplikacja CameraX została opracowana w oparciu o Aparat2, a Aparat CameraX zapewnia sposoby odczytywania i zapisywania właściwości w ramach implementacji Aparatu2. Szczegółowe informacje znajdziesz w pakiecie Interrop.
Więcej informacji o tym, jak aplikacja CameraX skonfigurowała właściwości Aparatu2, znajdziesz w opisie Camera2CameraInfo
, aby zapoznać się z podstawowymi informacjami o aparacie CameraCharacteristics
. Możesz też zapisać podstawowe właściwości Camera2 w jednej z tych 2 ścieżek:
Użyj funkcji
Camera2CameraControl
, która pozwala określić właściwościCaptureRequest
, np. tryb autofokusa.Rozszerz Aparat X
UseCase
za pomocąCamera2Interop.Extender
. Dzięki temu możesz skonfigurować właściwości żądania CaptureRequest, tak jakCamera2CameraControl
. Daje też dostęp do dodatkowych ustawień, np. umożliwia skonfigurowanie kamery pod kątem danego scenariusza. Więcej informacji znajdziesz w artykule Przypadki użycia strumienia w celu zwiększenia wydajności.
Poniższy przykładowy kod wykorzystuje przypadki użycia strumienia do optymalizacji pod kątem rozmowy wideo.
Parametr Camera2CameraInfo
pozwala sprawdzić, czy strumień rozmowy wideo jest dostępny. Następnie użyj właściwości Camera2Interop.Extender
, aby określić przypadek użycia strumienia.
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Dodatkowe materiały
Więcej informacji o aplikacji CameraX znajdziesz w tych dodatkowych materiałach.
Ćwiczenia z programowania
Przykładowy kod