Sesje i żądania nagrywania z użyciem aparatu

Uwaga: ta strona dotyczy pakietu Aparat2. Jeśli Twoja aplikacja nie wymaga konkretnych, niskopoziomowych funkcji z Aparatu 2, zalecamy używanie AparatuX. Aparaty CameraX i Aparat 2 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 jest CameraDevice, i CameraDevice mogą jednocześnie wysyłać więcej niż 1 strumień.

Jednym z powodów jest to, aby przesyłać jeden strumień, kolejne klatki z kamery z CameraDevice, jest zoptymalizowana pod kątem określonego zadania, np. wyświetlania za pomocą wizjera, a inne mogą być wykorzystywane do robienia zdjęć lub kręcenia filmów. rejestrowania. Strumienie działają jak równoległe potoki przetwarzające nieprzetworzone klatki. ujętych z kamery: jedna klatka po klatce:

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

Przetwarzanie równoległe sugeruje, że mogą występować ograniczenia wydajności w zależności od dostępną moc obliczeniową CPU, GPU lub inny procesor. Jeśli potok nie jest w stanie nadążyć za ramkami przychodzącymi, więc zaczyna je zrzucać.

Każdy potok ma własny format wyjściowy. Nieprzetworzone dane są automatycznie przekształcone w odpowiednie format wyjściowy za pomocą logiki niejawnej powiązane z każdym potokiem. Parametr CameraDevice używany na tej stronie Nie jest to konkretny przykład, więc najpierw wyliczamy wszystkich dostępnych kamer, zanim przejdziesz dalej.

CameraDevice umożliwia utworzenie CameraCaptureSession, która dotyczy tylko tego elementu: CameraDevice. CameraDevice musi otrzymać konfiguracji każdej nieprzetworzonej klatki z wykorzystaniem CameraCaptureSession. określa atrybuty aparatu, takie jak autofokus, przysłona, efekty, i ekspozycji. Ze względu na ograniczenia sprzętowe dopuszczalna jest tylko jedna konfiguracja aktywny czujnik w aparacie w dowolnym momencie. Jest to tzw. active.

Przypadki użycia strumienia jednak stanowią rozszerzenie i rozszerzają dotychczasowe sposoby korzystania z usługi CameraDevice do transmitowania sesji nagrywania, które pozwalają optymalizować dla konkretnego przypadku użycia. Może na przykład poprawić żywotność baterii podczas optymalizacji rozmowy wideo.

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

CaptureRequest dodaje konfigurację do kolejki i wybiera jedną, więcej niż jeden lub wszystkie dostępne potoki, które mogą odbierać ramkę z CameraDevice W całym cyklu życia zdjęcia możesz wysyłać wiele żądań zapisu . Każde żądanie może zmienić aktywną konfigurację i zestaw danych wyjściowych potoków, które odbierają nieprzetworzony obraz.

Przypadki użycia strumienia w celu zwiększenia skuteczności

Przypadki użycia dotyczące strumieniowania to sposób na poprawę wydajności przechwytywania obrazu za pomocą Aparatu 2 sesji. Dostarczają urządzeniu więcej informacji potrzebnych do dostrajania parametrów, co daje więcej możliwości korzystania z kamery do konkretnego zadania.

Ten pozwala aparatowi optymalizować działanie sprzętu i oprogramowania kamery na podstawie scenariusza dotyczącego użytkownika. Więcej informacji o korzystaniu ze strumienia Przypadki: zobacz setStreamUseCase.

Przypadki użycia strumienia pozwalają określić sposób użycia danego strumienia z kamery bardziej szczegółowo poza ustawieniem szablonu CameraDevice.createCaptureRequest() Umożliwia optymalizację sprzętu aparatu takich jak dostrajanie, tryb czujnika czy ustawienia czujnika aparatu, kompromisów związanych z jakością i opóźnieniami, odpowiednio do konkretnych przypadków użycia.

Przykłady użycia strumienia:

  • DEFAULT: obejmuje wszystkie istniejące zachowania aplikacji. Jest to odpowiednik nie dowolnego przypadku użycia strumienia.

  • PREVIEW: zalecany do użycia w wizjerze lub analizie obrazu w aplikacji.

  • STILL_CAPTURE: zoptymalizowana pod kątem wysokiej jakości nagrywania w wysokiej rozdzielczości, nie powinien utrzymać liczbę klatek podobną do podglądu.

  • VIDEO_RECORD: zoptymalizowany pod kątem nagrywania filmó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 spowodować wygenerowanie klatek wyjściowych ze znacznym opóźnieniem w stosunku do czasu rzeczywistego, aby uzyskać najwyższą jakość stabilizacji lub innego rodzaju przetwarzania.

  • VIDEO_CALL: opcja zalecana w przypadku długotrwałego korzystania z kamery w miejscach, gdzie zużycie energii jest ograniczone obawy.

  • PREVIEW_VIDEO_STILL: zalecany w przypadku aplikacji do mediów społecznościowych lub pojedynczych transmisji przypadków. To wielofunkcyjny strumień.

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

Tworzenie sesji CameraCapture

Aby utworzyć sesję kamery, udostępnij jej co najmniej 1 bufor wyjściowy aplikacja może zapisywać ramki wyjściowe. Każdy bufor reprezentuje potok. Musisz zrób to, zanim zaczniesz korzystać z kamery, aby platforma mogła wewnętrznych potoków urządzenia i przydziela bufory pamięci do wysyłania ramek do potrzebnych celów wyjściowych.

Ten fragment kodu pokazuje, jak przygotować sesję kamery z 2 osobami bufory wyjściowe, jeden należący do SurfaceView, a drugi do ImageReader Dodaję przypadek użycia strumienia PREVIEW do previewSurface i STILL_CAPTURE Korzystanie ze strumienia Obudowa do imReaderSurface pozwala sprzętowi urządzenia optymalizować te strumienie dalej.

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

Na tym etapie nie zdefiniowano aktywnej konfiguracji kamery. Po skonfigurowaniu sesji możesz utworzyć i wysłać przechwytywanie o takie potraktowanie.

Przekształcanie danych wejściowych w momencie zapisywania ich w buforze to zależy od typu każdej lokalizacji docelowej. Musi to być Surface Platformy Androida wiedzą, jak konwertuje nieprzetworzony obraz w aktywnej konfiguracji na format odpowiedni dla każdego miejsca docelowego. Konwersja zależy od formatu pikseli i rozmiaru konkretny element Surface.

Staramy się wykorzystać cały system, ale niektóre Surface kombinacje konfiguracji mogą nie działać i powodować problemy takie jak sesja nie zostanie utworzony, powoduje zgłoszenie błędu podczas wysyłania żądania; pogorszenie wydajności. Ten schemat zapewnia gwarancje w odniesieniu do określonych kombinacji parametrów urządzenia, powierzchni i parametrów żądań. Dokumentacja createCaptureSession() zawiera więcej informacji.

Pojedyncze żądania przechwytywania

Konfiguracja używana w przypadku każdej ramki jest zakodowana w CaptureRequest, który jest ciągiem do kamery. Aby utworzyć żądanie zapisu, możesz użyć jednej z wstępnie zdefiniowane szablony, Możesz też użyć TEMPLATE_MANUAL, aby mieć pełną kontrolę. Po wybraniu , musisz podać co najmniej 1 bufor wyjściowy, którego chcesz używać do danej prośby. Możesz używać tylko buforów, które zostały już zdefiniowane podczas przechwytywania której chcesz użyć.

Żądania przechwytywania używają wzorzec kompilacji i umożliwiać programistom określenie wielu różnych , w tym automatyczna ekspozycja, autofokus, oraz przysłona obiektywu. Przed skonfigurowaniem pola upewnij się, że w przypadku na tym urządzeniu, łącząc CameraCharacteristics.getAvailableCaptureRequestKeys() i że żądana wartość jest obsługiwana przez sprawdzenie odpowiedniego aparatu właściwości, takich jak dostępna automatyczna ekspozycja .

Aby utworzyć żądanie zapisu dotyczące obiektu SurfaceView za pomocą szablonu przeznaczone do podglądu bez żadnych modyfikacji, należy używać 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 przechwytywania możesz 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);

Po umieszczeniu ramki wyjściowej w określonym buforze przechwytywanie zostanie przechwycone oddzwanianie W wielu przypadkach dodatkowe wywołania zwrotne, takie jak ImageReader.OnImageAvailableListener, jest wyzwalany po przetworzeniu klatki, którą zawiera. Jest na w którym możesz pobrać dane obrazu z określonego bufora.

Powtórz żądania przechwytywania

Wysyłanie prośby o pojedynczą kamerę jest proste, ale w przypadku wyświetlenia obrazu na żywo ani podglądu, nie są raczej przydatne. W takim przypadku musisz otrzymać ciągłym strumieniem klatek, a nie tylko jedną. Następujący fragment kodu: pokazuje, jak dodać powtarzająca się prośba 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 nagrania powoduje, że aparat stale rejestruje zgodnie z ustawieniami w tym artykule: CaptureRequest. Interfejs API Camera2 Użytkownicy mogą też rejestrować filmy z kamery, wysyłając powtarzanie elementu CaptureRequests jak w tym Przykład z Aparatu2 w repozytorium GitHub. Może też renderować filmy w zwolnionym tempie, rejestrując film w szybkim (zwolnionym tempie) z powtarzającą się serią CaptureRequests co widać w przykładowej aplikacji Camera2 w zwolnionym tempie. w GitHubie.

Przeplot żądań przechwytywania

Aby wysłać drugie żądanie zapisu, gdy powtarzająca się prośba o nagranie jest aktywna, np. aby wyświetlić wizjer i umożliwić użytkownikom zrobienie zdjęcia, nie trzeba zatrzymać powtarzające się żądania. Zamiast tego nagrywasz niepowtarzający się film. .

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

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);

Takie podejście ma jednak wadę: nie wiadomo, kiedy tylko jedno żądanie. Jeśli na wykresie A to powtarzający się element a B to żądanie przechwytywania pojedynczej klatki. W ten sposób sesja przetwarza kolejkę żądań:

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

Nie ma gwarancji czasu oczekiwania między ostatnim cyklicznym żądaniem z A przed aktywacją żądania B i przy następnym użyciu użycia A dlatego możesz zauważyć kilka pominiętych klatek. Są pewne rzeczy, co możesz zrobić, aby zniwelować ten problem:

  • Dodaj docelowe dane wyjściowe z żądania A, by wysłać żądanie B. Dzięki temu, gdy Ramka B jest gotowa i skopiowana do docelowych elementów wyjściowych A. Jest to niezbędne np. przy tworzeniu zrzutów filmu, aby zapewnić stałą liczbę klatek. W poprzednim kodzie dodajesz singleRequest.addTarget(previewSurface).

  • Użyj kombinacji szablonów przeznaczonych do tego konkretnego scenariusza, takich jak zero-opóźnienie.