Uwaga: ta strona dotyczy pakietu Camera2. Jeśli Twoja aplikacja nie wymaga określonych funkcji niskiego poziomu z Camera2, zalecamy używanie CameraX. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.
Jedno urządzenie z Androidem może mieć kilka aparatów. Każda kamera to CameraDevice, a CameraDevice może jednocześnie przesyłać więcej niż 1 strumień.
Jednym z powodów jest to, że jeden strumień, czyli sekwencyjne klatki z aparatu pochodzące z CameraDevice, jest zoptymalizowany pod kątem konkretnego zadania, takiego jak wyświetlanie wizjera, podczas gdy inne mogą być używane do robienia zdjęć lub nagrywania filmów. Strumienie działają jak równoległe potoki, które przetwarzają nieprzetworzone klatki z kamery, po jednej klatce na raz:
Przetwarzanie równoległe sugeruje, że mogą występować ograniczenia wydajności w zależności od dostępnej mocy obliczeniowej procesora, GPU lub innego procesora. Jeśli potok nie nadąża za przychodzącymi klatkami, zaczyna je odrzucać.
Każdy potok ma własny format wyjściowy. Przychodzące nieprzetworzone dane są automatycznie przekształcane w odpowiedni format wyjściowy przez niejawną logikę powiązaną z każdym potokiem. Wartość CameraDevice używana w przykładowych kodach na tej stronie jest niespecyficzna, więc najpierw musisz wyliczyć wszystkie dostępne kamery.
Możesz użyć CameraDevice, aby utworzyć CameraCaptureSession, który jest powiązany z tym CameraDevice. CameraDevice musi otrzymać konfigurację klatki dla każdej nieprzetworzonej klatki za pomocą CameraCaptureSession. Konfiguracja określa atrybuty aparatu, takie jak autofokus, przysłona, efekty i ekspozycja. Ze względu na ograniczenia sprzętowe w danym momencie w czujniku aparatu aktywna jest tylko jedna konfiguracja, która jest nazywana konfiguracją aktywną.
Jednak przypadki użycia strumienia ulepszają i rozszerzają poprzednie sposoby korzystania z CameraDevice do sesji przechwytywania strumienia, co pozwala zoptymalizować strumień z kamery pod kątem konkretnego przypadku użycia. Może na przykład wydłużyć czas pracy na baterii podczas optymalizacji rozmów wideo.
CameraCaptureSession opisuje wszystkie możliwe potoki powiązane z CameraDevice. Po utworzeniu sesji nie można dodawać ani usuwać potoków.
CameraCaptureSession utrzymuje kolejkę CaptureRequest, które stają się aktywną konfiguracją.
CaptureRequest dodaje konfigurację do kolejki i wybiera co najmniej 1 lub wszystkie dostępne potoki, które mają otrzymać ramkę z CameraDevice. W trakcie sesji przechwytywania możesz wysłać wiele żądań przechwytywania. Każde żądanie może zmienić aktywną konfigurację i zestaw potoków wyjściowych, które otrzymują nieprzetworzony obraz.
Korzystanie z przypadków użycia strumienia w celu zwiększenia skuteczności
Przypadki użycia strumienia to sposób na zwiększenie wydajności sesji przechwytywania w Camera2. Dzięki temu urządzenie ma więcej informacji do dostrajania parametrów, co zapewnia lepsze działanie aparatu w konkretnym zadaniu.
Dzięki temu urządzenie z aparatem może optymalizować potoki sprzętowe i programowe aparatu na podstawie scenariuszy użytkownika dla każdego strumienia. Więcej informacji o przypadkach użycia strumienia znajdziesz w artykule setStreamUseCase.
Przypadki użycia strumienia pozwalają określić, jak dany strumień z kamery jest używany, w bardziej szczegółowy sposób, oprócz ustawienia szablonu w CameraDevice.createCaptureRequest(). Dzięki temu sprzęt aparatu może optymalizować parametry, takie jak dostrajanie, tryb czujnika czy ustawienia czujnika aparatu, na podstawie kompromisów dotyczących jakości lub opóźnienia, które są odpowiednie w przypadku konkretnych zastosowań.
Przykłady zastosowania strumieni:
DEFAULT: obejmuje wszystkie istniejące zachowania aplikacji. Jest to równoważne z nieustawieniem żadnego przypadku użycia strumienia.PREVIEW: zalecany do analizy obrazu w wizjerze lub w aplikacji.STILL_CAPTURE: zoptymalizowany pod kątem wysokiej jakości i wysokiej rozdzielczości, nie oczekuje się utrzymania liczby klatek na sekundę jak w podglądzie.VIDEO_RECORD: zoptymalizowany pod kątem nagrywania filmów w wysokiej jakości, w tym wysokiej jakości stabilizacji obrazu, jeśli jest obsługiwana przez urządzenie i włączona przez aplikację. Ta opcja może powodować znaczne opóźnienie klatek wyjściowych w stosunku do czasu rzeczywistego, aby umożliwić stabilizację lub inne przetwarzanie o najwyższej jakości.VIDEO_CALL: zalecany w przypadku długotrwałego korzystania z aparatu, gdy problemem jest zużycie energii.PREVIEW_VIDEO_STILL: zalecany w przypadku aplikacji społecznościowych lub zastosowań związanych z pojedynczym strumieniem. To strumień wielofunkcyjny.VENDOR_START: używane w przypadkach użycia zdefiniowanych przez producenta OEM.
Tworzenie sesji CameraCaptureSession
Aby utworzyć sesję kamery, podaj co najmniej 1 bufor wyjściowy, do którego aplikacja może zapisywać klatki wyjściowe. Każdy bufor reprezentuje potok. Musisz to zrobić przed rozpoczęciem korzystania z aparatu, aby platforma mogła skonfigurować wewnętrzne potoki urządzenia i przydzielić bufory pamięci do wysyłania klatek do wymaganych miejsc docelowych.
Poniższy fragment kodu pokazuje, jak przygotować sesję aparatu z 2 buforami wyjściowymi, z których jeden należy do SurfaceView, a drugi do ImageReader. Dodanie przypadku użycia strumienia PREVIEW do previewSurface i przypadku użycia strumienia STILL_CAPTURE do imReaderSurface pozwala sprzętowi urządzenia na jeszcze lepszą optymalizację tych strumieni.
// Retrieve the target surfaces, which might be coming from a number of places: // 1. SurfaceView, if you want to display the image directly to the user // 2. ImageReader, if you want to read each frame or perform frame-by-frame // analysis // 3. OpenGL Texture or TextureView, although discouraged for maintainability // reasons // 4. RenderScript.Allocation, if you want to do parallel processing val surfaceView = findViewById<SurfaceView>( // ... ) val imageReader = ImageReader.newInstance( // ... ) // Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated(). val previewSurface = surfaceView.holder.surface val imReaderSurface = imageReader.surface val targets = listOf(previewSurface, imReaderSurface) // Create a capture session using the predefined targets; this also involves // defining the session state callback to be notified of when the session is // ready. // Setup Stream Use Case while setting up your Output Configuration. @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun configureSession(device: CameraDevice, targets: List<Surface>) { val configs = mutableListOf<OutputConfiguration>() val streamUseCase = CameraMetadata .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL targets.forEach { val config = OutputConfiguration(it) config.streamUseCase = streamUseCase.toLong() configs.add(config) } // ... device.createCaptureSession(session) }
Na tym etapie nie masz zdefiniowanej aktywnej konfiguracji kamery. Po skonfigurowaniu sesji możesz utworzyć i wysłać żądania przechwytywania, aby to zrobić.
Przekształcenie stosowane do danych wejściowych podczas ich zapisywania w buforze jest określane przez typ każdego celu, który musi być obiektem Surface. Platforma Androida wie, jak przekonwertować obraz RAW w aktywnej konfiguracji na format odpowiedni dla każdego celu. Konwersja jest kontrolowana przez format pikseli i rozmiar konkretnego elementu Surface.
Platforma stara się działać jak najlepiej, ale niektóre kombinacje konfiguracji Surface mogą nie działać, co może powodować problemy, takie jak nieutworzenie sesji, błąd środowiska wykonawczego podczas wysyłania żądania lub spadek wydajności. Platforma zapewnia gwarancje dotyczące konkretnych kombinacji urządzenia, platformy i parametrów żądania. Więcej informacji znajdziesz w dokumentacji createCaptureSession().
Pojedyncze żądania CaptureRequest
Konfiguracja używana w przypadku każdej klatki jest kodowana w CaptureRequest, który jest wysyłany do aparatu. Aby utworzyć żądanie przechwytywania, możesz użyć jednego ze wstępnie zdefiniowanych szablonów lub TEMPLATE_MANUAL, aby mieć pełną kontrolę. Gdy wybierzesz szablon, musisz podać co najmniej 1 bufor wyjściowy, który będzie używany z żądaniem. Możesz używać tylko buforów, które zostały już zdefiniowane w sesji przechwytywania, której chcesz użyć.
Żądania przechwytywania używają wzorca budowniczego i umożliwiają deweloperom ustawianie wielu różnych opcji, takich jak automatyczna ekspozycja, autofokus i przysłona obiektywu. Zanim ustawisz pole, upewnij się, że dana opcja jest dostępna na urządzeniu, wywołując metodę CameraCharacteristics.getAvailableCaptureRequestKeys(), i że wybrana wartość jest obsługiwana, sprawdzając odpowiednią charakterystykę aparatu, np. dostępne tryby automatycznej ekspozycji.
Aby utworzyć żądanie przechwytywania dla SurfaceView przy użyciu szablonu przeznaczonego do podglądu bez żadnych modyfikacji, użyj CameraDevice.TEMPLATE_PREVIEW:
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequest.addTarget(previewSurface)
Po zdefiniowaniu żądania przechwytywania możesz je teraz wysłać do sesji aparatu:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // The first null argument corresponds to the capture callback, which you // provide if you want to retrieve frame metadata or keep track of failed capture // requests that can indicate dropped frames; the second null argument // corresponds to the Handler used by the asynchronous callback, which falls // back to the current thread's looper if null. session.capture(captureRequest.build(), null, null)
Gdy klatka wyjściowa zostanie umieszczona w określonym buforze, zostanie wywołane wywołanie zwrotne przechwytywania. W wielu przypadkach dodatkowe wywołania zwrotne, takie jak ImageReader.OnImageAvailableListener, są aktywowane, gdy ramka, którą zawierają, jest przetwarzana. W tym momencie możesz pobrać dane obrazu z określonego bufora.
Powtarzanie żądań CaptureRequest
Żądania dotyczące pojedynczego aparatu są proste, ale nie są zbyt przydatne do wyświetlania podglądu na żywo lub filmu. W takim przypadku musisz otrzymać ciągły strumień klatek, a nie tylko jedną klatkę. Poniższy fragment kodu pokazuje, jak dodać do sesji powtarzające się żądanie:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // This keeps sending the capture request as frequently as possible until // the session is torn down or session.stopRepeating() is called. session.setRepeatingRequest(captureRequest.build(), null, null)
Powtarzające się żądanie przechwytywania powoduje, że aparat stale robi zdjęcia, korzystając z ustawień podanych w obiekcie CaptureRequest. Interfejs Camera2 API umożliwia też użytkownikom nagrywanie filmów za pomocą aparatu przez wysyłanie powtarzających się żądań CaptureRequests, jak pokazano w tym repozytorium przykładowych żądań Camera2 na GitHubie. Może też renderować filmy w zwolnionym tempie, nagrywając film w wysokiej szybkości (w zwolnionym tempie) za pomocą powtarzającej się serii CaptureRequests, jak pokazano w przykładowej aplikacji do nagrywania filmów w zwolnionym tempie Camera2 na GitHubie.
Przeplatanie żądań CaptureRequest
Aby wysłać drugie żądanie przechwytywania, gdy aktywne jest powtarzające się żądanie przechwytywania, np. aby wyświetlić wizjer i umożliwić użytkownikom zrobienie zdjęcia, nie musisz zatrzymywać trwającego powtarzającego się żądania. Zamiast tego wysyłasz niepowtarzające się żądanie przechwytywania, podczas gdy żądanie powtarzające się nadal działa.
Każdy używany bufor wyjściowy musi być skonfigurowany jako część sesji aparatu podczas jej tworzenia. Żądania powtarzające się mają niższy priorytet niż żądania pojedynczej klatki lub serii, co umożliwia działanie tego przykładu:
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback // Create the repeating request and dispatch it. val repeatingRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW ) repeatingRequest.addTarget(previewSurface) session.setRepeatingRequest(repeatingRequest.build(), null, null) // Some time later... // Create the single request and dispatch it. // NOTE: This can disrupt the ongoing repeating request momentarily. val singleRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE ) singleRequest.addTarget(imReaderSurface) session.capture(singleRequest.build(), null, null)
To podejście ma jednak wadę: nie wiesz dokładnie, kiedy następuje pojedyncze żądanie. Jeśli na poniższym rysunku A to powtarzające się żądanie przechwytywania, a B to żądanie przechwytywania pojedynczej klatki, sesja przetwarza kolejkę żądań w ten sposób:
Nie ma gwarancji dotyczących opóźnienia między ostatnim powtarzającym się żądaniem z A przed aktywacją żądania B a następnym użyciem A, więc mogą wystąpić pominięte klatki. Możesz podjąć pewne działania, aby rozwiązać ten problem:
Dodaj cele wyjściowe z żądania A do żądania B. Dzięki temu, gdy klatka B jest gotowa, jest kopiowana do elementów docelowych danych wyjściowych A. Jest to na przykład niezbędne podczas robienia zrzutów ekranu z filmów, aby zachować stałą liczbę klatek. W poprzednim kodzie dodajesz
singleRequest.addTarget(previewSurface)przed utworzeniem żądania.Użyj kombinacji szablonów zaprojektowanych do pracy w tym konkretnym scenariuszu, np. z zerowym opóźnieniem migawki.