Sesje i żądania nagrywania z użyciem aparatu

Uwaga: ta strona dotyczy pakietu Camera2. Jeśli aplikacja nie wymaga określonych, niskiego poziomu funkcji z Aparatu2, zalecamy użycie CameraX. Zarówno aplikacja CameraX, jak i Aparat 2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.

Każde urządzenie z systemem Android może mieć wiele kamer. Każda kamera to CameraDevice, a CameraDevice może przesyłać jednocześnie więcej niż 1 strumień.

Jednym z powodów jest to, aby jeden strumień, czyli sekwencyjne klatki kamery pochodzące z obiektu CameraDevice, był zoptymalizowany pod kątem określonego zadania, takiego jak wyświetlanie wizjera, a innych można było użyć do zrobienia zdjęcia lub nagrania wideo.Strumienie działają jak równoległe potoki, które przetwarzają nieprzetworzone klatki wychodzące z kamery, jedna po drugiej:

Rysunek 1. Ilustracja z tworzenia aplikacji do uniwersalnego aparatu (Google I/O 2018)

Przetwarzanie równoległe wskazuje, że mogą obowiązywać limity wydajności zależne od dostępnej mocy procesora, GPU lub innego procesora. Jeśli potok nie jest w stanie nadążyć za klatkami przychodzącymi, zaczyna je usuwać.

Każdy potok ma własny format wyjściowy. Nieprzetworzone dane są automatycznie przekształcane na odpowiedni format wyjściowy przez niejawną logikę powiązaną z każdym potokiem. Parametr CameraDevice używany w przykładowym kodzie na tej stronie jest nieokreślony, dlatego zanim przejdziesz dalej, musisz najpierw wyliczyć wszystkie dostępne kamery.

Możesz użyć CameraDevice, aby utworzyć CameraCaptureSession, który jest dostosowany do tego obiektu CameraDevice. Element CameraDevice musi otrzymać konfigurację ramki dla każdej nieprzetworzonej ramki za pomocą CameraCaptureSession. Konfiguracja określa takie atrybuty aparatu jak autofokus, przysłona, efekty i ekspozycja. Ze względu na ograniczenia sprzętowe czujnik w kamerze może korzystać w danym momencie tylko z jednej konfiguracji. Jest to tzw. konfiguracja aktywna.

Przypadki użycia strumienia rozszerzają jednak wcześniejsze sposoby korzystania z usługi CameraDevice do transmitowania sesji nagrywania, co pozwala zoptymalizować transmisję z kamery pod kątem konkretnego przypadku użycia. Może na przykład poprawić czas pracy na baterii przy optymalizacji rozmów wideo.

CameraCaptureSession opisuje wszystkie możliwe potoki powiązane z CameraDevice. Po utworzeniu sesji nie możesz dodawać ani usuwać potoków. CameraCaptureSession przechowuje kolejkę obejmującą CaptureRequest, które stają się aktywną konfiguracją.

CaptureRequest dodaje konfigurację do kolejki i wybiera 1, więcej niż 1 lub wszystkie dostępne potoki, które mają otrzymać ramkę z CameraDevice. W trakcie sesji przechwytywania możesz wysyłać wiele żądań zapisu. Każde żądanie może zmienić aktywną konfigurację i zestaw potoków wyjściowych, które otrzymują nieprzetworzony obraz.

Przypadki użycia strumienia pozwalają zwiększyć wydajność

Przypadki użycia strumienia to sposób na poprawę wydajności sesji nagrywania za pomocą Aparatu 2. Dostarczają one urządzeniu więcej informacji, które pozwalają dopasować parametry dzięki czemu kamera lepiej radzi sobie z określonym zadaniem.

Dzięki temu urządzenie z kamerą może optymalizować potoki sprzętu i oprogramowania kamery na podstawie scenariuszy użytkownika w przypadku każdego strumienia. Więcej informacji o przypadkach użycia strumienia znajdziesz w artykule setStreamUseCase.

Przypadki użycia strumienia pozwalają nie tylko skonfigurować szablon w CameraDevice.createCaptureRequest(), ale też szczegółowo określać sposób wykorzystania konkretnego strumienia z kamery. Dzięki temu sprzęt kamery może optymalizować parametry, takie jak dostrajanie, tryb czujnika lub ustawienia czujnika kamery, na podstawie kompromisów w zakresie jakości lub opóźnienia odpowiednich do określonych przypadków użycia.

Przykładowe zastosowania strumienia:

  • DEFAULT: obejmuje wszystkie istniejące zachowanie aplikacji. Jest to równoważne brakowi ustawienia strumienia.

  • PREVIEW: zalecane w przypadku analizy obrazów w aplikacji za pomocą wizjera.

  • STILL_CAPTURE: optymalizacja pod kątem wysokiej jakości zdjęć w wysokiej rozdzielczości. Nie powinna utrzymywać liczby klatek przypominającej podgląd.

  • VIDEO_RECORD: optymalizacja pod kątem wysokiej jakości nagrywania filmów, w tym stabilizacji obrazu wysokiej jakości, jeśli jest obsługiwana przez urządzenie i włączona przez aplikację. Ta opcja może powodować generowanie klatek wyjściowych ze znacznym opóźnieniem w stosunku do czasu rzeczywistego, co pozwala uzyskać najwyższą jakość stabilizacji lub innego przetwarzania.

  • VIDEO_CALL: zalecany w przypadku długotrwałej pracy kamery, gdy istnieje duże zużycie energii.

  • PREVIEW_VIDEO_STILL: zalecany w przypadku aplikacji mediów społecznościowych lub pojedynczych strumieni. To kanał wielofunkcyjny.

  • VENDOR_START: używana w przypadkach użycia określonych przez OEM.

Utwórz sesję robienia zdjęć z aparatu

Aby utworzyć sesję kamery, udostępnij jej co najmniej 1 bufor wyjściowy, w którym aplikacja może zapisywać klatki wyjściowe. Każdy bufor reprezentuje potok. Musisz to zrobić przed rozpoczęciem korzystania z kamery, aby platforma mogła skonfigurować wewnętrzne potoki urządzenia i przydzielić bufory pamięci do wysyłania ramek do niezbędnych celów wyjściowych.

Ten fragment kodu pokazuje, jak możesz przygotować sesję kamery za pomocą 2 buforów wyników: jednego na potrzeby SurfaceView, a drugiego do ImageReader. Dodanie przykładu użycia strumienia PREVIEW do previewSurface i STILL_CAPTURE przypadku użycia strumienia w imReaderSurface pozwala sprzętowi na dalszą optymalizację tych strumieni.

Kotlin


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

Java


// 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. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// 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
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

W tym momencie nie masz zdefiniowanej aktywnej konfiguracji kamery. Po skonfigurowaniu sesji możesz w tym celu tworzyć i wysyłać żądania przechwytywania.

Przekształcenie stosowane do danych wejściowych podczas zapisywania ich w buforze jest określane przez typ każdego celu, który musi mieć postać Surface. Platforma Android wie, jak przekonwertować nieprzetworzony obraz w aktywnej konfiguracji na format odpowiedni dla każdego środowiska docelowego. Konwersja zależy od formatu piksela i rozmiaru konkretnego elementu Surface.

Platforma staramy się jak najlepiej robić, ale niektóre kombinacje konfiguracji Surface mogą nie działać, co powoduje problemy, np. nieutworzenie sesji, generowanie błędu środowiska wykonawczego podczas wysyłania żądania lub pogorszenie wydajności. Platforma daje gwarancje w przypadku określonych kombinacji parametrów urządzenia, powierzchni i żądania. Więcej informacji znajdziesz w dokumentacji createCaptureSession().

Pojedyncze żądania przechwytywania

Konfiguracja każdej klatki jest zakodowana w elemencie CaptureRequest, który jest wysyłany do kamery. Aby utworzyć żądanie przechwytywania, możesz użyć jednego ze wstępnie zdefiniowanych szablonów lub TEMPLATE_MANUAL, aby mieć pełną kontrolę. Po wybraniu szablonu musisz udostępnić co najmniej 1 bufor danych wyjściowych, których będzie można używać z tym żądaniem. Możesz używać tylko buforów zdefiniowanych już podczas sesji przechwytywania, której zamierzasz użyć.

Żądania przechwytywania wykorzystują wzorzec kreatora i umożliwiają programistom ustawienie wielu różnych opcji, w tym automatycznej ekspozycji, autofokusa i przysłony obiektywu. Przed ustawieniem pola upewnij się, że konkretna opcja jest dostępna na urządzeniu, wywołując metodę CameraCharacteristics.getAvailableCaptureRequestKeys(), oraz sprawdź, czy pożądana wartość jest obsługiwana przez sprawdzenie odpowiednich cech aparatu, np. dostępnych trybów automatycznej ekspozycji.

Aby utworzyć żądanie przechwytywania elementu SurfaceView przy użyciu szablonu przeznaczonego do podglądu bez żadnych modyfikacji, użyj funkcji CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

Po zdefiniowaniu żądania zapisu możesz je wysłać do sesji kamery:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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);

Umieszczenie ramki wyjściowej w określonym buforze uruchamia wywołanie zwrotne przechwytywania. W wielu przypadkach podczas przetwarzania zawartej w niej ramki są wywoływane dodatkowe wywołania zwrotne, np. ImageReader.OnImageAvailableListener. Na tym etapie można pobrać dane obrazu z określonego bufora.

Powtórz żądania przechwytywania

Żądania z jedną kamerą są łatwe do wykonania, ale nie są zbyt przydatne do wyświetlania podglądu na żywo lub filmu. W takim przypadku potrzebujesz ciągłego strumienia klatek, a nie tylko jednego. Poniższy fragment kodu pokazuje, jak dodać powtarzające się żądanie do sesji:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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 zapisu sprawia, że aparat stale robi zdjęcia zgodnie z ustawieniami w przedziale CaptureRequest. Interfejs Camera2 API umożliwia też użytkownikom nagrywanie obrazu z kamery przez wysyłanie powtarzanego żądania CaptureRequests, tak jak w tym przykładowym pliku Camera2 w GitHubie. Może też renderować filmy w zwolnionym tempie, nagrywając filmy w dużej prędkości (zwolnione tempo) z powtarzającą się seriami CaptureRequests, tak jak to pokazano w aplikacji z przykładowym filmem w zwolnionym tempie Camera2 na GitHubie.

Żądania przechwytywania ekranu

Aby wysłać drugą prośbę o zrobienie zdjęcia, gdy ta funkcja jest aktywna, na przykład aby wyświetlić wizjer i umożliwić użytkownikom zrobienie zdjęcia, nie musisz zatrzymywać tego powtarzanego żądania. Zamiast tego wysyłasz jednorazowe żądanie przechwytywania, gdy żądanie jest kontynuowane.

Każdy używany bufor wyjściowy musi zostać skonfigurowany w ramach sesji kamery przy jej tworzeniu. Powtarzające się żądania mają niższy priorytet niż żądania jednoklatkowe lub seryjne, które umożliwiają działanie następującej próbki:

Kotlin

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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().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
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

Jednak takie podejście ma wadę: nie wiadomo, kiedy dokładnie żądanie zostało wysłane. Jeśli A jest powtarzającym się żądaniem przechwytywania, a B jest żądaniem przechwytywania pojedynczej klatki, sesja przetwarza kolejkę żądań w ten sposób:

Rysunek 2. Ilustracja kolejki żądań dla trwającej sesji kamery

Nie ma gwarancji czasu oczekiwania między ostatnim powtarzającym się żądaniem od A przed aktywacją żądania B a następnym użyciem komponentu A, dlatego może się zdarzyć, że niektóre klatki zostaną pominięte. Aby temu zaradzić, możesz wykonać kilka czynności:

  • Dodaj cele wyjściowe z żądania A żądania B. Dzięki temu, gdy ramka B będzie gotowa, zostanie skopiowana do celów wyjściowych A. Jest to niezbędne np. podczas tworzenia zrzutów filmu, aby utrzymać stałą liczbę klatek. W poprzednim kodzie przed skompilowaniem żądania dodano singleRequest.addTarget(previewSurface).

  • Używaj kombinacji szablonów, które sprawdzają się w danym przypadku, np. z brakiem opóźnienia.