Architektura nagrywania filmów w aplikacji CameraX

System przechwytywania zazwyczaj rejestruje strumienie wideo i audio, kompresuje je, miksuje, a następnie zapisuje wynikowy strumień na dysku.

schemat koncepcyjny systemu nagrywania wideo i dźwięku
Rysunek 1. Schemat koncepcyjny systemu nagrywania obrazu i dźwięku.

W aplikacji CameraX rozwiązanie do nagrywania filmów to przypadek użycia VideoCapture:

schemat koncepcyjny pokazujący, jak kamera X obsługuje przypadek użycia do nagrywania filmów.
Rysunek 2. Schemat koncepcyjny pokazujący, jak CameraX obsługuje przypadek użycia VideoCapture.

Jak widać na Rysunku 2, nagrywanie filmów za pomocą aparatu CameraX obejmuje kilka komponentów architektonicznych wysokiego poziomu:

  • SurfaceProvider jako źródło filmu.
  • AudioSource, aby wskazać źródło dźwięku.
  • Dwa kodery do kodowania i kompresji obrazu/dźwięku.
  • Mikser multimediów do łączenia dwóch strumieni.
  • Oszczędzanie plików do zapisania wyniku.

Interfejs VideoCapture API wyodrębnia złożony mechanizm przechwytywania i udostępnia aplikacjom interfejs API o wiele prostszym i czytelniejszym charakterze.

Omówienie interfejsu VideoCapture API

VideoCapture to przypadek użycia CameraX, który działa samodzielnie lub w połączeniu z innymi przypadkami użycia. Konkretne obsługiwane kombinacje zależą od możliwości sprzętowych kamery, ale Preview i VideoCapture to prawidłowe połączenie przypadków użycia na wszystkich urządzeniach.

Interfejs VideoCapture API składa się z tych obiektów, które komunikują się z aplikacjami:

  • VideoCapture to klasa przypadku najwyższego poziomu. VideoCapture wiąże obiekt LifecycleOwner z elementem CameraSelector i innym przypadkiem użycia CameraX. Więcej informacji o tych zagadnieniach i zastosowaniach znajdziesz w artykule o architekturze CameraX.
  • Recorder to implementacja Videooutput, która jest ściśle sprzężona z VideoCapture. Recorder służy do nagrywania obrazu i dźwięku. Aplikacja tworzy nagrania z Recorder.
  • PendingRecording konfiguruje nagranie, udostępniając opcje takie jak włączenie dźwięku i ustawienie odbiornika. Aby utworzyć PendingRecording, musisz użyć Recorder. PendingRecording niczego nie rejestruje.
  • Recording wykonuje rzeczywiste nagranie. Aby utworzyć Recording, musisz użyć PendingRecording.

Rysunek 3 przedstawia zależności między tymi obiektami:

schemat pokazujący interakcje, które zachodzą w przypadku użycia zapisu wideo
Rysunek 3. Diagram prezentujący interakcje, które zachodzą w przypadku użycia VideoCapture.

Legenda:

  1. Utwórz Recorder z QualitySelector.
  2. Skonfiguruj Recorder za pomocą jednego z komponentów OutputOptions.
  3. W razie potrzeby włącz dźwięk w narzędziu withAudioEnabled().
  4. Aby rozpocząć nagrywanie, zadzwoń pod numer start() z słuchaczem VideoRecordEvent.
  5. Użyj pause()/resume()/stop() na Recording, aby sterować nagrywaniem.
  6. Odpowiedz w sekcji VideoRecordEvents w detektorze zdarzeń.

Szczegółowa lista interfejsów API znajduje się w pliku current.txt w kodzie źródłowym.

Korzystanie z interfejsu VideoCapture API

Aby zintegrować przypadek użycia usługi CameraX VideoCapture z aplikacją, wykonaj te czynności:

  1. Powiąż plik VideoCapture.
  2. Przygotuj i skonfiguruj nagrywanie.
  3. Uruchamianie i kontrolowanie nagrywania w czasie działania.

Poniżej opisujemy, co możesz zrobić na poszczególnych etapach, by przeprowadzić kompleksową sesję nagrywania.

Powiąż nagrywanie filmów

Aby powiązać przypadek użycia VideoCapure, wykonaj te czynności:

  1. Utwórz obiekt Recorder.
  2. Utwórz obiekt VideoCapture.
  3. Powiąż z elementem Lifecycle.

Interfejs CameraX VideoCapture API jest zgodny z wzorcem konstrukcyjnym. Do tworzenia Recorder aplikacje wykorzystują Recorder.Builder. Rozdzielczość wideo dla Recorder możesz też skonfigurować za pomocą obiektu QualitySelector.

CameraX Recorder obsługuje te wstępnie zdefiniowane Qualities w rozdzielczościach wideo:

  • Quality.UHD – film w rozdzielczości 4K ultra HD (2160p)
  • Quality.FHD – film w rozdzielczości Full HD (1080p)
  • Quality.HD – rozmiar filmu HD (720p)
  • Quality.SD – rozmiar filmu w SD (480p)

Pamiętaj, że aplikacja CameraX może też wybrać inne rozdzielczości, jeśli autoryzuje ją aplikacja.

Dokładny rozmiar obrazu w przypadku każdego wybranego materiału zależy od możliwości kamery i kodera. Więcej informacji znajdziesz w dokumentacji CamcorderProfile.

Aplikacje mogą konfigurować rozdzielczość przez utworzenie QualitySelector. QualitySelector możesz utworzyć, korzystając z jednej z tych metod:

  • Podaj kilka preferowanych rozwiązań za pomocą funkcji fromOrderedList() i dodaj strategię zastępczą, która będzie używana w sytuacji, gdy żadna z nich nie będzie obsługiwana.

    Aplikacja CameraX może określić najlepsze dopasowanie zastępcze na podstawie możliwości wybranej kamery. Więcej informacji znajdziesz w dokumentacji QualitySelector.FallbackStrategy specification Na przykład ten kod wymaga najwyższej obsługiwanej rozdzielczości nagrywania. Jeśli nie będzie można wykonać żadnej z rozdzielczości żądania, autoryzuj aparat Aparat X do wybrania takiej rozdzielczości, która jest najbliższa rozdzielczości w ramach jakości.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Najpierw zapytaj o możliwości aparatu, a potem wybierz jedną z obsługiwanych rozdzielczości za pomocą funkcji QualitySelector::from():

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    Pamiętaj, że zwrócona możliwość z funkcji QualitySelector.getSupportedQualities() będzie działać zarówno w przypadku użycia VideoCapture, jak i kombinacji przypadków użycia VideoCapture i Preview. W przypadku powiązania z przypadkiem użycia ImageCapture lub ImageAnalysis powiązanie CameraX może się nie powieść, gdy wymagana kombinacja nie jest obsługiwana przez żądaną kamerę.

Gdy masz już QualitySelector, aplikacja może utworzyć obiekt VideoCapture i wykonać wiązanie. Zwróć uwagę, że to powiązanie działa tak samo jak w innych przypadkach użycia:

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

Pamiętaj, że bindToLifecycle() zwraca obiekt Camera. W tym przewodniku znajdziesz więcej informacji o kontrolowaniu wyjścia kamery, np. powiększenia i ekspozycji.

Recorder wybiera najbardziej odpowiedni format dla systemu. Najczęstszym kodekiem wideo jest H.264 AVC) w formacie kontenera MPEG-4.

Skonfiguruj i utwórz nagranie

Z Recorder aplikacja może tworzyć obiekty nagrywania służące do nagrywania dźwięku i wideo. Aplikacje tworzą nagrania:

  1. Skonfiguruj OutputOptions za pomocą prepareRecording().
  2. (Opcjonalnie) Włącz nagrywanie dźwięku.
  3. Użyj start(), aby zarejestrować słuchacza VideoRecordEvent i rozpocząć nagrywanie filmu.

Recorder zwraca obiekt Recording podczas wywoływania funkcji start(). Aplikacja może używać tego obiektu Recording do zakończenia przechwytywania lub wykonywania innych działań, takich jak wstrzymywanie lub wznawianie.

Recorder obsługuje 1 obiekt Recording naraz. Nowe rejestrowanie możesz rozpocząć po wywołaniu metody Recording.stop() lub Recording.close() w poprzednim obiekcie Recording.

Przyjrzyjmy się im bardziej szczegółowo. Najpierw aplikacja konfiguruje OutputOptions dla Dyktafonu z Recorder.prepareRecording(). Recorder obsługuje te typy OutputOptions:

  • FileDescriptorOutputOptions do przechwytywania obrazu w FileDescriptor.
  • FileOutputOptions – przechwytywanie w elemencie File.
  • MediaStoreOutputOptions do przechwytywania obrazu w MediaStore.

Wszystkie typy OutputOptions umożliwiają ustawienie maksymalnego rozmiaru pliku w polu setFileSizeLimit(). Inne opcje zależą od konkretnego typu danych wyjściowych, np. ParcelFileDescriptor w polu FileDescriptorOutputOptions.

prepareRecording() zwraca obiekt PendingRecording, który jest obiektem pośrednim służącym do utworzenia odpowiedniego obiektu Recording. PendingRecording jest klasą przejściową, która w większości przypadków powinna być niewidoczna i rzadko jest przechowywana w pamięci podręcznej aplikacji.

Aplikacje mogą dodatkowo konfigurować nagrywanie, na przykład:

  • Włącz dźwięk na ekranie withAudioEnabled().
  • Zarejestruj detektor, aby otrzymywać zdarzenia nagrywania wideo za pomocą start(Executor, Consumer<VideoRecordEvent>).
  • Zezwól na ciągłe nagrywanie nagrania, do którego jest podłączone nagranie wideo, które jest przesyłane do innej kamery za pomocą funkcji PendingRecording.asPersistentRecording().

Aby rozpocząć nagrywanie, zadzwoń pod numer PendingRecording.start(). CameraX zmienia PendingRecording w Recording, umieszcza żądanie nagrywania w kolejce i zwraca nowo utworzony obiekt Recording do aplikacji. Gdy rozpocznie się nagrywanie na odpowiednim urządzeniu kamery, CameraX wyśle zdarzenie VideoRecordEvent.EVENT_TYPE_START.

Ten przykład pokazuje, jak nagrać wideo i dźwięk w pliku MediaStore:

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

Widok z przedniego aparatu jest domyślnie powielany na podglądzie z aparatu, a filmy nagrane za pomocą funkcji VideoCapture domyślnie nie są powielane. W Aparacie 1.3 można teraz tworzyć odbicie lustrzane nagrań wideo, aby zapewnić zgodność obrazu z przedniego aparatu i nagranego obrazu.

Dostępne są trzy opcje trybu MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON oraz MIRROR_MODE_ON_FRONT_ONLY. Aby dopasować obraz do podglądu z aparatu, Google zaleca użycie funkcji MIROR_MODE_ON_FRONT_ONLY, co oznacza, że odbicie lustrzane jest wyłączone dla tylnego aparatu, ale włączone dla przedniego. Więcej informacji o MirrorMode znajdziesz w artykule MirrorMode constants.

Ten fragment kodu pokazuje, jak wywołać usługę VideoCapture.Builder.setMirrorMode() za pomocą MIRROR_MODE_ON_FRONT_ONLY. Więcej informacji: setMirrorMode().

Kotlin


val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java


Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

Sterowanie aktywnym nagraniem

Możesz wstrzymać, wznowić i zatrzymać trwający Recording, korzystając z tych metod:

  • pause, aby wstrzymać bieżące aktywne nagranie.
  • resume(), aby wznowić wstrzymane aktywne nagranie.
  • stop(), aby zakończyć nagrywanie i usunąć wszystkie powiązane obiekty nagrywania.
  • mute(), aby wyciszyć bieżące nagranie lub wyłączyć wyciszenie.

Pamiętaj, że możesz wywołać stop(), by zakończyć nagrywanie Recording niezależnie od tego, czy nagrywanie jest wstrzymane czy aktywne.

Jeśli masz zarejestrowany EventListener w PendingRecording.start(), Recording komunikuje się za pomocą VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS służy do rejestrowania statystyk, takich jak bieżący rozmiar pliku czy zarejestrowany okres.
  • Parametr VideoRecordEvent.EVENT_TYPE_FINALIZE służy do uzyskania wyniku rejestrowania i zawiera informacje takie jak identyfikator URI ostatecznego pliku oraz wszelkie powiązane błędy.

Gdy aplikacja otrzyma sygnał EVENT_TYPE_FINALIZE oznaczający udaną sesję nagrywania, możesz uzyskać dostęp do nagranego filmu z lokalizacji podanej w polu OutputOptions.

Dodatkowe materiały

Więcej informacji o aplikacji CameraX znajdziesz w tych dodatkowych materiałach: