Na tej stronie znajdziesz informacje o architekturze CameraX, w tym o jej strukturze, sposobie korzystania z interfejsu API, cyklach życia i łączeniu przypadków użycia.
Struktura CameraX
Za pomocą CameraX możesz komunikować się z aparatem urządzenia za pomocą abstrakcji zwanej przypadkiem użycia. Dostępne są te przypadki użycia:
- Podgląd: akceptuje powierzchnię do wyświetlania podglądu, np.
PreviewView
. - Analiza obrazu: udostępnia bufory dostępne dla procesora na potrzeby analizy, np. uczenia maszynowego.
- Zrób zdjęcie: robi i zapisuje zdjęcie.
- Nagrywanie wideo: nagrywanie wideo i dźwięku za pomocą
VideoCapture
.
Przypadki użycia można łączyć i aktywować jednocześnie. Na przykład aplikacja może umożliwiać użytkownikowi wyświetlanie obrazu z kamery w ramach podglądu, analizować obraz, aby określić, czy osoby na zdjęciu się uśmiechają, oraz robić zdjęcie, gdy tak się stanie.
Model interfejsu API
Aby korzystać z biblioteki, musisz określić te elementy:
- Odpowiedni przypadek użycia z opcjami konfiguracji.
- Co zrobić z danymi wyjściowymi, dołączając odbiorców.
- określony przepływ, np. kiedy włączyć kamery i kiedy generować dane, poprzez powiązanie przypadku użycia z cyklami życia architektury Androida.
Aplikację CameraX możesz napisać na 2 sposoby: CameraController
(najprostszy sposób używania CameraX) lub CameraProvider
(większa elastyczność).
CameraController
Klasa CameraController
udostępnia większość podstawowych funkcji CameraX. Wymaga niewielkiej ilości kodu konfiguracji i automatycznie obsługuje inicjowanie aparatu, zarządzanie przypadkami użycia, obracanie obiektu, ustawianie ostrości przez dotknięcie, powiększanie przez uszczypnięcie i inne funkcje. Klasa konkretna, która rozszerza klasę 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 CameraController
to Preview
, ImageCapture
i ImageAnalysis
. Aby wyłączyć ImageCapture
lub ImageAnalysis
albo włączyć VideoCapture
, użyj metody setEnabledUseCases()
.
Więcej zastosowań CameraController
znajdziesz w przykładzie skanera kodów QR lub w filmie o podstawach CameraController
.
CameraProvider
CameraProvider
jest nadal łatwy w użyciu, ale ponieważ deweloper aplikacji zajmuje się większą częścią konfiguracji, istnieje więcej możliwości dostosowania ustawień, np. włączenia obracania obrazu wyjściowego lub ustawienia formatu obrazu wyjściowego w ImageAnalysis
. Możesz też użyć niestandardowego elementu Surface
do podglądu z kamery, co zapewnia większą elastyczność. W przypadku klasy CameraController musisz użyć elementu PreviewView
. Użycie dotychczasowego kodu Surface
może być przydatne, jeśli jest on już używany w innych częściach aplikacji.
Przypadki użycia konfiguruje się za pomocą metod set()
, a kończy metodą build()
. Każdy obiekt przypadku użycia udostępnia zestaw interfejsów API przeznaczonych do konkretnego przypadku użycia. Na przykład w przypadku użycia związanym z robieniem zdjęć dostępna jest metoda takePicture()
.
Zamiast umieszczać w onResume()
i onPause()
wywołania konkretnych metod rozpoczęcia i zatrzymania, aplikacja określa cykl życia, z którym ma być powiązany aparat, za pomocą cameraProvider.bindToLifecycle()
.
Cykl życia informuje bibliotekę CameraX, kiedy skonfigurować sesję przechwytywania obrazu z kamery, i zapewnia, że stan kamery zmienia się odpowiednio do przejść cyklu życia.
Instrukcje wdrażania w poszczególnych przypadkach użycia znajdziesz w tych artykułach: Implementacja podglądu, Analizowanie obrazów, Przechwytywanie obrazów i Przechwytywanie filmów.
Przypadek użycia podglądu wchodzi w interakcję z Surface
w celu wyświetlenia. Aplikacje
tworzą przypadek użycia z opcjami konfiguracji za pomocą 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 aplikacji przykładowej CameraX.
Cykle życia CameraX
CameraX obserwuje cykl życia, aby określić, kiedy otworzyć aparat, kiedy utworzyć sesję przechwytywania, a kiedy zatrzymać i zamknąć aparat. Interfejsy API przypadków użycia udostępniają wywołania metod i wywołania zwrotne do monitorowania postępów.
Jak wyjaśniono w sekcji Łączenie przypadków użycia, niektóre kombinacje przypadków użycia można powiązać z jednym cyklem życia. Jeśli aplikacja musi obsługiwać przypadki użycia, których nie można łączyć, możesz wykonać jedną z tych czynności:
- Pogrupuj kompatybilne przypadki użycia w kilku fragmentach, a potem przełączaj się między nimi.
- Utwórz niestandardowy komponent cyklu życia i użyj go do ręcznego sterowania cyklem życia aparatu.
Jeśli rozdzielisz właścicieli cyklu życia przypadków użycia widoku i kamery (np. jeśli używasz niestandardowego cyklu życia lub fragmentu retain), musisz się upewnić, że wszystkie przypadki użycia są odłączone od CameraX za pomocą ProcessCameraProvider.unbindAll()
lub przez odłączenie każdego przypadku użycia z osobna. Możesz też powiązać przypadki użycia z cyklem życia, aby CameraX zarządzał otwieraniem i zamykaniem sesji przechwytywania oraz odłączaniem przypadków użycia.
Jeśli wszystkie funkcje kamery odpowiadają cyklowi życia jednego komponentu uwzględniającego cykl życia, takiego jak AppCompatActivity
lub fragment AppCompat
, użycie cyklu życia tego komponentu podczas wiązania wszystkich żądanych przypadków użycia zapewni, że funkcje kamery będą gotowe, gdy komponent uwzględniający cykl życia będzie aktywny, a w innych przypadkach zostaną bezpiecznie usunięte i nie będą zużywać żadnych zasobów.
Niestandardowe obiekty LifecycleOwner
W bardziej zaawansowanych przypadkach możesz utworzyć niestandardowy LifecycleOwner
, aby umożliwić aplikacji wyraźne kontrolowanie cyklu życia sesji CameraX zamiast powiązywania go ze standardowym LifecycleOwner
Androida.
Poniższy przykładowy kod pokazuje, jak utworzyć prosty niestandardowy obiekt LifecycleOwner:
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; } }
Dzięki temu 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 Implementowanie niestandardowego interfejsu LifecycleOwner.
Równoczesne przypadki użycia
Przypadki użycia mogą być uruchamiane równocześnie. Chociaż przypadki użycia można kolejno wiązać z cyklem życia, lepiej jest powiązać wszystkie przypadki użycia za pomocą jednego wywołania funkcji CameraProcessProvider.bindToLifecycle()
. Więcej informacji o sprawdzonych metodach wprowadzania zmian w konfiguracji znajdziesz w artykule Obsługa zmian konfiguracji.
W tym przykładzie kodu aplikacja określa 2 przypadki użycia, które mają zostać utworzone i uruchomione jednocześnie. Określa też cykl życia, który ma być używany w obu przypadkach użycia, aby oba zaczynały się i kończyły 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)); }
CameraX umożliwia jednoczesne korzystanie z jednej instancji każdego z tych interfejsów: Preview
, VideoCapture
, ImageAnalysis
i ImageCapture
. Reklamy
- Każdy przypadek użycia może działać samodzielnie. Na przykład aplikacja może nagrywać wideo bez korzystania z podglądu.
- Gdy rozszerzenia są włączone, gwarantowane jest tylko działanie kombinacji
ImageCapture
iPreview
. W zależności od implementacji OEM może nie być możliwe dodanieImageAnalysis
. Rozszerzeń nie można włączyć w przypadkuVideoCapture
. Szczegóły znajdziesz w dokumentacji referencyjnej rozszerzenia. - W zależności od możliwości aparatu niektóre aparaty mogą obsługiwać kombinację w trybach o niższej rozdzielczości, ale nie w trybach o wyższej rozdzielczości.
- Na urządzeniach z poziomem sprzętowym kamery
FULL
lub niższym połączeniePreview
,VideoCapture
iImageCapture
lubImageAnalysis
może wymusić na CameraX powielenie strumieniaPRIV
kamery dlaPreview
iVideoCapture
. To powielanie, zwane udostępnianiem strumienia, umożliwia jednoczesne korzystanie z tych funkcji, ale wiąże się ze zwiększonymi wymaganiami dotyczącymi przetwarzania. Może to spowodować nieco większe opóźnienia i skrócenie czasu pracy baterii.
Obsługiwany poziom sprzętu można pobrać z Camera2CameraInfo
. Na przykład poniższy kod sprawdza, czy domyślny tylny aparat jest urządzeniem 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\<CameraInfo\> 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, aplikacja będzie też potrzebować uprawnienia WRITE_EXTERNAL_STORAGE
, z wyjątkiem urządzeń z Androidem 10 lub nowszym.
Więcej informacji o konfigurowaniu uprawnień aplikacji znajdziesz w artykule Request App Permissions (Prośba o uprawnienia aplikacji).
Wymagania
CameraX ma te minimalne wymagania dotyczące wersji:
- Android API level 21
- Składniki architektury Androida w wersji 1.1.1
W przypadku działań 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
w projekcie i dodaj repozytorium google()
, jak pokazano poniżej:
Groovy
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 Androida dodaj ten kod:
Groovy
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 do pliku build.gradle
każdego modułu aplikacji te informacje:
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.5.0-beta01" // 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.5.0-beta01" // 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 tych wymagań znajdziesz w sekcji Deklarowanie zależności.
Współdziałanie CameraX z Camera2
CameraX jest oparty na Camera2 i udostępnia sposoby odczytywania, a nawet zapisywania właściwości w implementacji Camera2. Szczegółowe informacje znajdziesz w pakiecie Interop.
Więcej informacji o tym, jak CameraX skonfigurowała właściwości Camera2, znajdziesz w Camera2CameraInfo
, gdzie możesz przeczytać podstawowe CameraCharacteristics
. Możesz też zapisać właściwości Camera2 w jeden z tych 2 sposobów:
Użyj
Camera2CameraControl
, które umożliwia ustawianie właściwości bazowegoCaptureRequest
, takich jak tryb autofokusu.Rozszerzanie
UseCase
CameraX za pomocąCamera2Interop.Extender
. Umożliwia to ustawianie właściwości w obiekcie CaptureRequest, podobnie jak w przypadku elementuCamera2CameraControl
. Daje też dodatkowe możliwości sterowania, takie jak ustawienie przypadku użycia strumienia, aby zoptymalizować kamerę pod kątem danego scenariusza. Więcej informacji znajdziesz w artykule Używanie przypadków użycia strumienia w celu zwiększenia wydajności.
Poniższy przykładowy kod wykorzystuje przypadki użycia strumienia do optymalizacji połączenia wideo.
Użyj Camera2CameraInfo
, aby sprawdzić, czy dostępny jest przypadek użycia strumienia rozmowy wideo. Następnie użyj parametru
Camera2Interop.Extender
, aby określić przypadek użycia strumienia bazowego.
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 CameraX znajdziesz w tych materiałach.
Ćwiczenia z programowania
Przykładowy kod