Przeprowadź migrację z Aparatu 1 do aplikacji CameraX

Jeśli aplikacja używa oryginalnego pakietu Camera klasa („Aparat1”), która jest wycofana od Android 5.0 (poziom API 21), zalecamy przejście na nowoczesny interfejs API aparatu w Androidzie. Oferty Androida CameraX (ustandaryzowany, solidny aparat Jetpack); API) i Camera2 (niskopoziomowy interfejs API). W przypadku atrybutu w większości przypadków zalecamy przeniesienie aplikacji na Aparat X. Przyczyna jest następująca:

  • Łatwość obsługi: Aparat X obsługuje szczegóły, dzięki czemu skup się na tworzeniu pracy kamery od podstaw, a nie na do wyróżnienia Twojej aplikacji.
  • Aparat X obsługuje fragmentację za Ciebie: Aparat X ogranicza długoterminowe koszty konserwacji oraz kod dostosowany do konkretnego urządzenia, w związku z wyższą jakością użytkownikom. Aby dowiedzieć się więcej na ten temat, sprawdź Lepsza zgodność urządzeń z Aparatem X .
  • Zaawansowane funkcje: Aparat X został zaprojektowany tak, aby umożliwiać zaawansowane którą łatwo włączyć w swojej aplikacji. Na przykład można łatwo stosowanie efektu bokeh, retuszu twarzy, HDR (High Dynamic Range) i rozjaśniania przy słabym oświetleniu trybu nocnego na zdjęciach za pomocą Rozszerzenia CameraX.
  • Możliwość aktualizacji: Android udostępnia nowe funkcje i poprawki błędów w aplikacji CameraX. przez cały rok. Dzięki przejściu na Aparat X Twoja aplikacja otrzymuje najnowszą wersję Androida technologia aparatu w każdej wersji CameraX, a nie tylko na rocznych wersji Androida.

W tym przewodniku omawiamy typowe scenariusze korzystania z aplikacji aparatu. Każdy obejmuje implementację Aparatu1 i AparatuX porównanie.

W przypadku migracji czasami potrzebujesz elastycznej elastyczności, z dotychczasową bazą kodu. Cały kod aparatu CameraX w tym przewodniku zawiera CameraController to świetne rozwiązanie, jeśli zależy Ci na najprostszym sposobie korzystania z aplikacji CameraX CameraProvider implementacji – to świetne rozwiązanie, jeśli potrzebujesz większej elastyczności. Aby łatwiej wybrać i odpowiadają następującemu zaletyowi:

Kontroler aparatu

Dostawca kamery

Wymaga niewielkiego kodu konfiguracyjnego Większa kontrola
Umożliwienie mu obsługiwania większej liczby procesów konfiguracji funkcje takie jak ustawianie ostrości przez dotknięcie i powiększanie przez ściągnięcie palców, działają automatycznie. Konfiguracją zajmuje się deweloper aplikacji, więc aby dostosować konfigurację, np. włączyć obrót obrazu wyjściowego lub ustaw format obrazu wyjściowego w ImageAnalysis
Wymaganie PreviewView do podglądu z aparatu pozwala na użycie tego parametru CameraX, oferując płynną, kompleksową integrację, tak jak w przypadku naszego pakietu ML Kit. integracja, która może mapować współrzędne wyników modelu ML (takie jak twarz ramki ograniczające) bezpośrednio na współrzędne podglądu Możliwość użycia niestandardowej „płaszczyzny” do podglądu z kamery pozwala: większą elastyczność, na przykład przez wykorzystanie istniejącego kodu „Surface”, może stanowić źródło danych do innych części aplikacji

Jeśli podczas migracji napotkasz problemy, skontaktuj się z nami na Grupa dyskusyjna CameraX.

Przed migracją

Porównywanie użycia CameraX z Aparatem 1

Choć kod może wyglądać inaczej, podstawowe pojęcia stosowane w Aparacie 1 i Aparaty CameraX są bardzo podobne do siebie. Aparat X przedstawia typowe funkcje aparatu w przypadkach użycia, W rezultacie wiele zadań deweloperskich w Aparacie1 zostało cofniętych. obsługiwane automatycznie przez aparatX. Istnieją cztery UseCase w AparacieX, które możesz do różnych zadań związanych z aparatem: Preview, ImageCapture, VideoCapture ImageAnalysis.

Przykładem obsługi przez deweloperów informacji niskopoziomowych w AparacieX jest ViewPort, który jest wspólny dla aktywnych UseCase. Dzięki temu wszyscy użytkownicy UseCase będą widzieć dokładnie te same piksele. W Aparacie 1 musisz samodzielnie zarządzać tymi szczegółami, biorąc pod uwagę zmienność format obrazu na różnych urządzeniach czujnikami i ekranami aparatu, trudno jest sprawdź, czy podgląd pasuje do zdjęć i filmów.

Inny przykład: AparatX automatycznie obsługuje wywołania zwrotne Lifecycle Lifecycle instancję pozytywnie. Oznacza to, że Aparat X obsługuje połączenia z kamerą przez cały czas Cykl aktywności na Androidzie, w tym w takich przypadkach: zamknięcie aparatu, gdy aplikacja zostanie tle; usuwanie podglądu z aparatu, gdy ekran nie jest już potrzebny jego wyświetlanie; i wstrzymywanie podglądu z kamery, gdy pojawi się inna aktywność pierwszeństwo pierwszego planu, np. przychodzące rozmowy wideo.

Aparat CameraX obsługuje też obracanie i skalowanie bez konieczności pisania dodatkowego kodu. z Twojej strony. W przypadku Activity z odblokowaną orientacją, Konfiguracja UseCase jest wykonywana przy każdym obróceniu urządzenia, ponieważ podczas jego zniszczenia i odtwarza Activity przy zmianie orientacji. Dzięki temu uzyskasz dostęp do UseCases ustawiają rotację docelową tak, aby pasowała do orientacji wyświetlacza o za każdym razem. Więcej informacji o obrotach w Aparacie X

Zanim przejdziemy do szczegółów, obejrzyjmy ogólne spojrzenie na aparat X. UseCase oraz jaki może być ich związek z aplikacją Camera1. (Pojęcia związane z AparatemX są już dostępne niebieski i Aparat 1 koncepcje są w zielony).

Aparat X

Konfiguracja kontrolera CameraController / obiektu CameraProvider
Podgląd Robienie zdjęć Przechwytywanie wideo Analiza obrazu
Zarządzaj powierzchnią podglądu i ustaw ją w Aparacie Ustawianie funkcji PictureCallback i wywoływania TakePicture() w aparacie Zarządzanie konfiguracją aparatu i MediaRecorder w określonej kolejności Kod analizy niestandardowej utworzony na podstawie platformy do wyświetlania podglądu
Kod związany z urządzeniem
Zarządzanie obrotem i skalowaniem urządzeń
Zarządzanie sesją z kamery (wybór kamery, zarządzanie cyklem życia)

Aparat 1

Zgodność i wydajność w AparacieX

CameraX obsługuje urządzenia obsługujące Androida 5.0 (poziom interfejsu API 21) lub nowszym. Ten to ponad 98% istniejących urządzeń z Androidem. Aparat CameraX został stworzony, aby automatycznie rozróżniają urządzenia, co zmniejsza potrzebę stosowania rozwiązań kod w aplikacji. Ponadto testujemy ponad 150 urządzeń fizycznych z Androidem. wersji 5.0 w naszym Laboratorium CameraX. Ty mogą przejrzeć pełną listę urządzeń znajdujących się obecnie w Laboratorium.

Aparat X używa Executor do i zwiększyć możliwości aparatu. Dostępne opcje skonfiguruj własnego wykonawcy w aplikacji CameraX jeśli aplikacja ma określone wymagania dotyczące wątków. Jeśli zasada jest nieskonfigurowana, AparatX tworzy i wykorzystuje zoptymalizowany domyślny domyślny Executor. Wiele interfejsów API platformy dla którego stworzono Aparat X, wymaga zablokowania komunikacji międzyprocesowej (IPC) z sprzęt, którego reakcja może czasem trwać setki milisekund. Do tego celu aparat X wywołuje te interfejsy API tylko z wątków w tle, dzięki czemu wątek główny nie jest zablokowany, a interfejs pozostaje płynny. Więcej informacji o wątkach

Jeśli rynkiem docelowym aplikacji są mniej zaawansowane urządzenia, Aparat X oferuje pozwala skrócić czas konfiguracji ograniczenia aparatu. Ponieważ Podłączenie do komponentów sprzętowych może zająć W przypadku urządzeń niskiej jakości można określić zestaw kamer do Twoich potrzeb. CameraX łączy się z nimi tylko podczas konfiguracji. Na przykład, jeśli Aplikacja używa tylko tylnych aparatów, może ustawić tę konfigurację za pomocą kodu DEFAULT_BACK_CAMERA, a następnie AparatX pozwala uniknąć inicjowania przedniego aparatu kamery, by zmniejszyć opóźnienie.

Pojęcia związane z programowaniem na Androida

W tym przewodniku zakładamy, że masz ogólną wiedzę o programowaniu na Androida. Rozwój sytuacji podstawowe informacje, oto kilka koncepcji, które warto zrozumieć przed przejdź do kodu poniżej:

Przenoszenie typowych scenariuszy

W tej sekcji wyjaśniono, jak przenieść typowe scenariusze z Aparatu 1 do Aparatu X. Każdy scenariusz dotyczy implementacji Aparatu1, CameraProvider oraz implementacja CameraController w AparacieX.

Wybieranie kamery

Jedną z pierwszych rzeczy, które warto zrobić w aplikacji aparatu, jest jak wybrać różne aparaty.

Aparat 1

W Aparacie 1 możesz Camera.open() bez parametrów aby otworzyć pierwszy tylny aparat. Możesz też podać liczbę całkowitą identyfikatora aparat, który chcesz uruchomić. Oto przykład, jak mogłoby to wyglądać:

// Camera1: select a camera from id.

// Note: opening the camera is a non-trivial task, and it shouldn't be
// called from the main thread, unlike CameraX calls, which can be
// on the main thread since CameraX kicks off background threads
// internally as needed.

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        camera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(TAG, "failed to open camera", e)
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    camera?.release()
    camera = null
}

Aparat X: kontroler aparatu

W Aparacie X wybór kamery jest obsługiwany przez klasę CameraSelector. Aparat X to łatwy przypadek używania domyślnego aparatu. Możesz określić, czy wybierz domyślny aparat przedni lub tylny. Ponadto Obiekt CameraControl w AparacieX pozwala łatwo ustaw poziom powiększenia aplikacji, więc jeśli Twoja aplikacja działa na urządzeniu obsługującym kamer logicznych, a potem do odpowiedniego obiektywu.

Oto kod CameraX do używania domyślnego tylnego aparatu CameraController:

// CameraX: select a camera with CameraController

var cameraController = LifecycleCameraController(baseContext)
val selector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraController.cameraSelector = selector

CameraX: CameraProvider,

Oto przykład wyboru domyślnego przedniego aparatu za pomocą funkcji CameraProvider (przedniego lub tylnego aparatu można używać z tymi funkcjami: CameraController lub CameraProvider):

// CameraX: select a camera with CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Set up UseCases (more on UseCases in later scenarios)
    var useCases:Array = ...

    // Set the cameraSelector to use the default front-facing (selfie)
    // camera.
    val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Jeśli chcesz mieć kontrolę nad wyborem kamery, możesz to zrobić na stronie CameraX, jeśli korzystasz z aplikacji CameraProvider, nawiązując połączenie getAvailableCameraInfos(), który daje obiekt CameraInfo do sprawdzania określonych właściwości aparatu, takich jak isFocusMeteringSupported() Następnie możesz go przekonwertować na format CameraSelector, aby używać go jak w przykładzie powyżej. za pomocą metody CameraInfo.getCameraSelector().

Więcej informacji o każdej kamerze możesz uzyskać, używając Camera2CameraInfo zajęcia. Zadzwoń do nas getCameraCharacteristic() i przyznać klucz do odpowiednich danych. Zajrzyj do CameraCharacteristics , gdzie znajduje się lista wszystkich kluczy, o które możesz wysyłać zapytania.

Oto przykład niestandardowej funkcji checkFocalLength(), z której można zdefiniuj siebie:

// CameraX: get a cameraSelector for first camera that matches the criteria
// defined in checkFocalLength().

val cameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val focalLengths = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
            )
        return checkFocalLength(focalLengths)
    }
val cameraSelector = cameraInfo.getCameraSelector()

Wyświetlanie podglądu

Większość aplikacji do obsługi kamery na niektórych urządzeniach musi pokazywać obraz z kamery . W przypadku aplikacji Camera1 trzeba prawidłowo zarządzać wywołaniami zwrotnymi w cyklu życia musisz też ustawić obrót i skalowanie podglądu.

Dodatkowo w Aparacie1 musisz zdecydować, czy chcesz użyć TextureView lub SurfaceView. Obie opcje wiążą się z pewnymi wadami. W obu przypadkach Aparat1 wymaga poprawnie obsługiwać obracanie i skalowanie. Urządzenie PreviewView w aparacie X, z drugiej strony ma bazowe implementacje zarówno dla TextureView, jak i SurfaceView. CameraX decyduje, która implementacja jest najlepsza, na podstawie takich czynników jak: typu urządzenia i wersji Androida, na której działa Twoja aplikacja. Jeśli któryś jest zgodna, możesz zadeklarować swoje preferencje PreviewView.ImplementationMode Opcja COMPATIBLE używa TextureView dla podglądu, a metoda Wartość PERFORMANCE używa SurfaceView (jeśli to możliwe).

Aparat 1

Aby wyświetlić podgląd, musisz utworzyć własne zajęcia w: Preview z atrybutem implementacji android.view.SurfaceHolder.Callback. który służy do przekazywania danych obrazu ze sprzętu aparatu do aplikacji. Przed rozpoczęciem wyświetlania podglądu obrazu na żywo komponent Preview klasa musi być przekazywana do obiektu Camera.

// Camera1: set up a camera preview.

class Preview(
        context: Context,
        private val camera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val holder: SurfaceHolder = holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera
        // where to draw the preview.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "error setting camera preview", e)
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                w: Int, h: Int) {
        // If your preview can change or rotate, take care of those
        // events here. Make sure to stop the preview before resizing
        // or reformatting it.
        if (holder.surface == null) {
            return  // The preview surface does not exist.
        }

        // Stop preview before making changes.
        try {
            camera.stopPreview()
        } catch (e: Exception) {
            // Tried to stop a non-existent preview; nothing to do.
        }

        // Set preview size and make any resize, rotate or
        // reformatting changes here.

        // Start preview with new settings.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "error starting camera preview", e)
            }
        }
    }
}

class CameraActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private var camera: Camera? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create an instance of Camera.
        camera = getCameraInstance()

        preview = camera?.let {
            // Create the Preview view.
            Preview(this, it)
        }

        // Set the Preview view as the content of the activity.
        val cameraPreview: FrameLayout = viewBinding.cameraPreview
        cameraPreview.addView(preview)
    }
}

Aparat X: kontroler aparatu

W Aparacie X możliwości zarządzania są znacznie mniejsze. Jeśli używasz tagu CameraController, musisz też użyć PreviewView. Oznacza to, że Preview UseCase jest domniemana, dlatego konfiguracja jest znacznie mniej pracochłonna:

// CameraX: set up a camera preview with a CameraController.

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create the CameraController and set it on the previewView.
        var cameraController = LifecycleCameraController(baseContext)
        cameraController.bindToLifecycle(this)
        val previewView: PreviewView = viewBinding.cameraPreview
        previewView.controller = cameraController
    }
}

CameraX: CameraProvider,

Dzięki CameraProvider w aparacie X nie musisz używać PreviewView, ale nadal znacznie upraszcza konfigurację podglądu w porównaniu do Aparatu1. Demonstracja W tym przykładzie użyto PreviewView, ale możesz wpisać SurfaceProvider do setSurfaceProvider() w przypadku bardziej złożonych zadań do Twoich potrzeb.

W tym przypadku element UseCase Preview nie jest sugerowany, jak ma to miejsce w przypadku CameraController, dlatego musisz to skonfigurować:

// CameraX: set up a camera preview with a CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Create Preview UseCase.
    val preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(
                viewBinding.viewFinder.surfaceProvider
            )
        }

    // Select default back camera.
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera() in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Dotknij, aby ustawić ostrość

Gdy podgląd z aparatu wyświetla się na ekranie, zwykle wystarczy ustawić ostrość. gdy użytkownik kliknie podgląd.

Aparat 1

Aby zastosować funkcję ostrości przez dotknięcie w Aparacie 1, musisz obliczyć optymalną ostrość Area, aby wskazać, na którym miejscu Camera ma próbować się skupić. To urządzenie Area jest przekazano do: setFocusAreas(). Musisz też ustawić zgodny tryb ostrości na Camera Obszar ostrości działa tylko wtedy, gdy bieżący tryb ostrości jest FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO lub FOCUS_MODE_CONTINUOUS_PICTURE

Każdy element Area to prostokąt o określonej wadze. Waga to wartość pomiędzy 1 i 1000. W przypadku skonfigurowania kilku wartości ten parametr określa priorytety Areas. Ten W przykładzie użyto tylko jednej wartości Area, więc wartość wagi nie ma znaczenia. Współrzędne prostokąta w zakresie od -1000 do 1000. Lewy górny punkt to (-1000, -1000). Prawy dolny punkt to (1000, 1000). Kierunek zależy od czujnika czyli to, co widzi czujnik. Parametr nie ma wpływu na kierunek lub odbicia lustrzanego Camera.setDisplayOrientation(), więc konieczne jest przekształcenie współrzędnych zdarzenia dotknięcia na współrzędne czujnika.

// Camera1: implement tap-to-focus.

class TapToFocusHandler : Camera.AutoFocusCallback {
    private fun handleFocus(event: MotionEvent) {
        val camera = camera ?: return
        val parameters = try {
            camera.getParameters()
        } catch (e: RuntimeException) {
            return
        }

        // Cancel previous auto-focus function, if one was in progress.
        camera.cancelAutoFocus()

        // Create focus Area.
        val rect = calculateFocusAreaCoordinates(event.x, event.y)
        val weight = 1  // This value's not important since there's only 1 Area.
        val focusArea = Camera.Area(rect, weight)

        // Set the focus parameters.
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO)
        parameters.setFocusAreas(listOf(focusArea))

        // Set the parameters back on the camera and initiate auto-focus.
        camera.setParameters(parameters)
        camera.autoFocus(this)
    }

    private fun calculateFocusAreaCoordinates(x: Int, y: Int) {
        // Define the size of the Area to be returned. This value
        // should be optimized for your app.
        val focusAreaSize = 100

        // You must define functions to rotate and scale the x and y values to
        // be values between 0 and 1, where (0, 0) is the upper left-hand side
        // of the preview, and (1, 1) is the lower right-hand side.
        val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000
        val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000

        // Calculate the values for left, top, right, and bottom of the Rect to
        // be returned. If the Rect would extend beyond the allowed values of
        // (-1000, -1000, 1000, 1000), then crop the values to fit inside of
        // that boundary.
        val left = max(normalizedX - (focusAreaSize / 2), -1000)
        val top = max(normalizedY - (focusAreaSize / 2), -1000)
        val right = min(left + focusAreaSize, 1000)
        val bottom = min(top + focusAreaSize, 1000)

        return Rect(left, top, left + focusAreaSize, top + focusAreaSize)
    }

    override fun onAutoFocus(focused: Boolean, camera: Camera) {
        if (!focused) {
            Log.d(TAG, "tap-to-focus failed")
        }
    }
}

Aparat X: kontroler aparatu

CameraController nasłuchuje zdarzeń dotknięcia użytkowniczki PreviewView w celu obsłużenia automatycznie ustawić ostrość przez dotknięcie. Możesz włączyć lub wyłączyć funkcję dotknij, aby ustawić ostrość, używając setTapToFocusEnabled() i sprawdź wartość za pomocą odpowiedniej metody getter isTapToFocusEnabled()

getTapToFocusState() zwraca obiekt LiveData do śledzenia zmian stanu skupienia w CameraController.

// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView,
// with handlers you can define for focused, not focused, and failed states.

val tapToFocusStateObserver = Observer { state ->
    when (state) {
        CameraController.TAP_TO_FOCUS_NOT_STARTED ->
            Log.d(TAG, "tap-to-focus init")
        CameraController.TAP_TO_FOCUS_STARTED ->
            Log.d(TAG, "tap-to-focus started")
        CameraController.TAP_TO_FOCUS_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focus successful)")
        CameraController.TAP_TO_FOCUS_NOT_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focused unsuccessful)")
        CameraController.TAP_TO_FOCUS_FAILED ->
            Log.d(TAG, "tap-to-focus failed")
    }
}

cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)

CameraX: CameraProvider,

Aby ustawić ostrość przez dotknięcie, musisz użyć urządzenia CameraProvider w pracy. W tym przykładzie zakładamy, że używasz właściwości PreviewView. Jeśli nie, musisz wykonać te czynności: dostosować logikę do niestandardowego elementu Surface.

Wykonaj te czynności, jeśli używasz PreviewView:

  1. Skonfiguruj wykrywanie gestów do obsługi zdarzeń dotknięcia.
  2. W przypadku zdarzenia kliknięcia utwórz MeteringPoint w taki sposób: MeteringPointFactory.createPoint()
  3. Za pomocą MeteringPoint utwórz FocusMeteringAction.
  4. Za pomocą obiektu CameraControl na urządzeniu Camera (zwrócono z bindToLifecycle()), wywołaj startFocusAndMetering(), podając wartość FocusMeteringAction.
  5. (Opcjonalnie) Odpowiedz na FocusMeteringResult.
  6. Skonfiguruj detektor gestów, aby reagował na zdarzenia dotyku w PreviewView.setOnTouchListener()
// CameraX: implement tap-to-focus with CameraProvider.

// Define a gesture detector to respond to tap events and call
// startFocusAndMetering on CameraControl. If you want to use a
// coroutine with await() to check the result of focusing, see the
// "Android development concepts" section above.
val gestureDetector = GestureDetectorCompat(context,
    object : SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            val previewView = previewView ?: return
            val camera = camera ?: return
            val meteringPointFactory = previewView.meteringPointFactory
            val focusPoint = meteringPointFactory.createPoint(e.x, e.y)
            val meteringAction = FocusMeteringAction
                .Builder(meteringPoint).build()
            lifecycleScope.launch {
                val focusResult = camera.cameraControl
                    .startFocusAndMetering(meteringAction).await()
                if (!result.isFocusSuccessful()) {
                    Log.d(TAG, "tap-to-focus failed")
                }
            }
        }
    }
)

...

// Set the gestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    // See pinch-to-zooom scenario for scaleGestureDetector definition.
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Ściąganie i powiększanie

Powiększanie i pomniejszanie podglądu to kolejne częste manipulacje podgląd z aparatu. Rosnąca liczba aparatów na urządzeniach sprawia, że użytkownicy obiektyw o najlepszej ogniskowej zostanie automatycznie wybrany w wyniku powiększenia.

Aparat 1

Istnieją dwa sposoby powiększania za pomocą Aparatu1. Metoda Camera.startSmoothZoom() zmienia się od bieżącego poziomu powiększenia do zalecanego poziomu. Metoda Camera.Parameters.setZoom() przechodzi bezpośrednio do uzyskanego poziomu powiększenia cal Zanim użyjesz któregoś z nich, zadzwoń pod numer isSmoothZoomSupported() lub isZoomSupported(), aby odpowiednio korzystać z powiązanych metod powiększenia. dostępne w Aparacie.

Aby zaimplementować powiększenie przez ściągnięcie palców, w tym przykładzie użyto elementu setZoom(), ponieważ detektor na powierzchni podglądu stale uruchamia zdarzenia, gdy ściągnięcie więc automatycznie zmienia poziom powiększenia za każdym razem. Klasa ZoomTouchListener jest zdefiniowana poniżej i powinna być ustawiona jako wywołanie zwrotne do detektora dotykowego obszaru podglądu.

// Camera1: implement pinch-to-zoom.

// Define a scale gesture detector to respond to pinch events and call
// setZoom on Camera.Parameters.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val parameters = try {
                camera.parameters
            } catch (e: RuntimeException) {
                return false
            }

            // In case there is any focus happening, stop it.
            camera.cancelAutoFocus()

            // Set the zoom level on the Camera.Parameters, and set
            // the Parameters back onto the Camera.
            val currentZoom = parameters.zoom
            parameters.setZoom(detector.scaleFactor * currentZoom)
        camera.setParameters(parameters)
            return true
        }
    }
)

// Define a View.OnTouchListener to attach to your preview view.
class ZoomTouchListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean =
        scaleGestureDetector.onTouchEvent(event)
}

// Set a ZoomTouchListener to handle touch events on your preview view
// if zoom is supported by the current camera.
if (camera.getParameters().isZoomSupported()) {
    view.setOnTouchListener(ZoomTouchListener())
}

Aparat X: kontroler aparatu

Podobnie jak używanie funkcji ostrości przez dotknięcie, CameraController słucha dotyku funkcji PreviewView które obsługują automatyczne powiększanie przez ściąganie palców. Możesz włączać i wyłączać ściągnij palce, aby powiększyć setPinchToZoomEnabled() i sprawdź wartość za pomocą odpowiedniej metody getter isPinchToZoomEnabled()

getZoomState() zwraca obiekt LiveData do śledzenia zmian w metodzie ZoomState w CameraController.

// CameraX: track the state of pinch-to-zoom over the Lifecycle of
// a PreviewView, logging the linear zoom ratio.

val pinchToZoomStateObserver = Observer { state ->
    val zoomRatio = state.getZoomRatio()
    Log.d(TAG, "ptz-zoom-ratio $zoomRatio")
}

cameraController.getZoomState().observe(this, pinchToZoomStateObserver)

CameraX: CameraProvider,

Aby korzystać z powiększania przez ściąganie palców w CameraProvider, musisz odpowiednio skonfigurować tę funkcję. Jeśli nie używasz PreviewView, dostosuj zasady, aby zastosować niestandardowy element Surface.

Wykonaj te czynności, jeśli używasz PreviewView:

  1. Skonfiguruj detektor gestów skalowania do obsługi zdarzeń ściągnięcia.
  2. Pobierz ZoomState z obiektu Camera.CameraInfo, gdzie Camera w przypadku wywołania przez Ciebie bindToLifecycle()
  3. Jeśli ZoomState zawiera wartość zoomRatio, zapisz ją jako bieżące powiększenie współczynnik proporcji. Jeśli na urządzeniu ZoomState nie ma parametru zoomRatio, użyj ustawienia domyślnego kamery współczynnik powiększenia (1,0).
  4. Wybierz iloczyn obecnego powiększenia (scaleFactor – określić nowy współczynnik powiększenia i przekazać je do CameraControl.setZoomRatio().
  5. Skonfiguruj detektor gestów, aby reagował na zdarzenia dotyku w PreviewView.setOnTouchListener()
// CameraX: implement pinch-to-zoom with CameraProvider.

// Define a scale gesture detector to respond to pinch events and call
// setZoomRatio on CameraControl.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : SimpleOnGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return
            val zoomState = camera.cameraInfo.zoomState
            val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f
            camera.cameraControl.setZoomRatio(
                detector.scaleFactor * currentZoomRatio
            )
        }
    }
)

...

// Set the scaleGestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        // See pinch-to-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Robienie zdjęcia

W tej sekcji dowiesz się, jak włączyć robienie zdjęć, niezależnie od tego, czy musisz to zrobić na naciśnięcia przycisku migawki, upłynięcia czasu lub jakiegokolwiek innego zdarzenia wyboru.

Aparat 1

W Aparacie 1 najpierw definiujesz Camera.PictureCallback w razie potrzeby zarządzać danymi zdjęć. Oto prosty przykład PictureCallback do obsługi danych obrazu JPEG:

// Camera1: define a Camera.PictureCallback to handle JPEG data.

private val picture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG,
              "error creating media file, check storage permissions")
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "file not found", e)
    } catch (e: IOException) {
        Log.d(TAG, "error accessing file", e)
    }
}

Aby zrobić zdjęcie, wywołaj metodę takePicture() w instancji Camera. Ta metoda takePicture() ma trzy różne dla różnych typów danych. Pierwszy parametr to ShutterCallback (niezdefiniowany w tym przykładzie). Drugi parametr to dla PictureCallback do obsługi nieprzetworzonych (nieskompresowanych) danych kamery. Trzecia jest parametrem użytym w tym przykładzie, ponieważ jest to PictureCallback do obsługi Dane obrazu JPEG.

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

Aparat X: kontroler aparatu

CameraController w Aparatze X zachowuje prostotę obsługi zdjęć w obiekcie Aparat 1 za pomocą własnej metody takePicture(). Tutaj zdefiniuj w celu skonfigurowania wpisu MediaStore i zrobienia tam zdjęcia.

// CameraX: define a function that uses CameraController to take a photo.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun takePhoto() {
   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata.
   val outputOptions = ImageCapture.OutputFileOptions
       .Builder(context.getContentResolver(),
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
       .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken.
   cameraController.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(e: ImageCaptureException) {
               Log.e(TAG, "photo capture failed", e)
           }

           override fun onImageSaved(
               output: ImageCapture.OutputFileResults
           ) {
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

CameraX: CameraProvider,

Robienie zdjęć za pomocą aplikacji CameraProvider działa prawie tak samo jak w przypadku aplikacji CameraController, ale najpierw musisz utworzyć i powiązać ImageCapture UseCase ma obiekt do wywołania takePicture():

// CameraX: create and bind an ImageCapture UseCase.

// Make a reference to the ImageCapture UseCase at a scope that can be accessed
// throughout the camera logic in your app.
private var imageCapture: ImageCapture? = null

...

// Create an ImageCapture instance (can be added with other
// UseCase definitions).
imageCapture = ImageCapture.Builder().build()

...

// Bind UseCases to camera (adding imageCapture along with preview here, but
// preview is not required to use imageCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, imageCapture)

Jeśli chcesz zrobić zdjęcie, możesz zadzwonić pod numer ImageCapture.takePicture() Zobacz kod CameraController w tej sekcji aby zobaczyć pełny przykład funkcji takePhoto().

// CameraX: define a function that uses CameraController to take a photo.

private fun takePhoto() {
    // Get a stable reference of the modifiable ImageCapture UseCase.
    val imageCapture = imageCapture ?: return

    ...

    // Call takePicture on imageCapture instance.
    imageCapture.takePicture(
        ...
    )
}

Nagrywanie filmu

Nagrywanie filmów jest znacznie bardziej skomplikowane niż w przykładach. jak dotąd. Każda część tego procesu musi być odpowiednio skonfigurowana, zwykle w określonym zamówieniu. Może być też konieczne sprawdzenie, czy obraz i dźwięk są synchronizować lub rozwiązywać dodatkowe niespójności na urządzeniach.

Jak się przekonasz, AparatX również sprawdza się w wielu kwestiach.

Aparat 1

Nagrywanie filmów za pomocą Aparatu1 wymaga starannego zarządzania tymi funkcjami: Camera i MediaRecorder, a metody muszą w określonej kolejności. Musisz przestrzegać tego zamówienia przez należy zapewnić prawidłowe działanie aplikacji:

  1. Uruchom aparat.
  2. Przygotuj i uruchom podgląd (jeśli aplikacja wyświetla nagrywany film, co zwykle tak jest).
  3. Odblokuj aparat, aby mógł go używać do MediaRecorder, dzwoniąc pod numer Camera.unlock().
  4. Skonfiguruj nagrywanie, wywołując te metody w aplikacji MediaRecorder:
    1. Połącz instancję Camera z usługą setCamera(camera).
    2. Będziesz dzwonić pod numer setAudioSource(MediaRecorder.AudioSource.CAMCORDER).
    3. Będziesz dzwonić pod numer setVideoSource(MediaRecorder.VideoSource.CAMERA).
    4. Zadzwoń pod numer setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) aby ustawić jakość. Zobacz CamcorderProfile w przypadku całej wybierz opcje jakości.
    5. Będziesz dzwonić pod numer setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()).
    6. Jeśli aplikacja ma podgląd filmu, zadzwoń setPreviewDisplay(preview?.holder?.surface)
    7. Będziesz dzwonić pod numer setOutputFormat(MediaRecorder.OutputFormat.MPEG_4).
    8. Będziesz dzwonić pod numer setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT).
    9. Będziesz dzwonić pod numer setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT).
    10. Wywołaj prepare(), aby dokończyć konfigurację urządzenia MediaRecorder.
  5. Aby rozpocząć nagrywanie, zadzwoń pod numer MediaRecorder.start().
  6. Aby zatrzymać nagrywanie, wywołaj te metody. Jeszcze raz dokładnie w takiej kolejności:
    1. Będziesz dzwonić pod numer MediaRecorder.stop().
    2. Opcjonalnie możesz usunąć bieżącą konfigurację MediaRecorder, wywołując MediaRecorder.reset()
    3. Będziesz dzwonić pod numer MediaRecorder.release().
    4. Zablokuj kamerę, żeby można było z niej korzystać w przyszłych sesjach MediaRecorder Dzwonię pod numer Camera.lock().
  7. Aby wyłączyć podgląd, zadzwoń pod numer Camera.stopPreview().
  8. Na koniec, aby zwolnić Camera, tak aby mogły go używać inne procesy, wywołaj Camera.release()

Oto wszystkie kroki:

// Camera1: set up a MediaRecorder and a function to start and stop video
// recording.

// Make a reference to the MediaRecorder at a scope that can be accessed
// throughout the camera logic in your app.
private var mediaRecorder: MediaRecorder? = null
private var isRecording = false

...

private fun prepareMediaRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    // Unlock and set camera to MediaRecorder.
    camera?.unlock()

    mediaRecorder?.run {
        setCamera(camera)

        // Set the audio and video sources.
        setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        setVideoSource(MediaRecorder.VideoSource.CAMERA)

        // Set a CamcorderProfile (requires API Level 8 or higher).
        setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

        // Set the output file.
        setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

        // Set the preview output.
        setPreviewDisplay(preview?.holder?.surface)

        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)

        // Prepare configured MediaRecorder.
        return try {
            prepare()
            true
        } catch (e: IllegalStateException) {
            Log.d(TAG, "preparing MediaRecorder failed", e)
            releaseMediaRecorder()
            false
        } catch (e: IOException) {
            Log.d(TAG, "setting MediaRecorder file failed", e)
            releaseMediaRecorder()
            false
        }
    }
    return false
}

private fun releaseMediaRecorder() {
    mediaRecorder?.reset()
    mediaRecorder?.release()
    mediaRecorder = null
    camera?.lock()
}

private fun startStopVideo() {
    if (isRecording) {
        // Stop recording and release camera.
        mediaRecorder?.stop()
        releaseMediaRecorder()
        camera?.lock()
        isRecording = false

        // This is a good place to inform user that video recording has stopped.
    } else {
        // Initialize video camera.
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared, now
            // you can start recording.
            mediaRecorder?.start()
            isRecording = true

            // This is a good place to inform the user that recording has
            // started.
        } else {
            // Prepare didn't work, release the camera.
            releaseMediaRecorder()

            // Inform user here.
        }
    }
}

Aparat X: kontroler aparatu

Dzięki CameraController w aparacie X możesz przełączać ImageCapture, VideoCapture i ImageAnalysis UseCase niezależnie, o ile lista przypadków użycia może być używana jednocześnie. Elementy UseCase ImageCapture i ImageAnalysis są domyślnie włączone, co Dlatego nie trzeba dzwonić pod numer setEnabledUseCases(), aby zrobić zdjęcie.

Aby używać urządzenia CameraController do nagrywania filmów, musisz najpierw użyć setEnabledUseCases(), aby zezwolić na VideoCapture UseCase.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

Aby zacząć nagrywać film, możesz zadzwonić do CameraController.startRecording() . Ta funkcja może zapisać nagrany film w elemencie File, jak widać w poniższym przykładzie. Musisz też zdać Executor i zajęcia który stosuje OnVideoSavedCallback. do obsługi udanych i błędnych wywołań zwrotnych. Kiedy powinno się zakończyć nagrywanie, wywołaj połączenie CameraController.stopRecording()

Uwaga: jeśli używasz AparatuX 1.3.0-alfa02 lub nowszego, Parametr AudioConfig , która umożliwia włączenie lub wyłączenie nagrywania dźwięku w filmie. Aby włączyć do nagrywania dźwięku, musisz mieć uprawnienia do korzystania z mikrofonu. Dodatkowo w wersji 1.3.0-alfa02 usunęliśmy metodę stopRecording(). startRecording() zwraca obiekt Recording, którego można użyć do wstrzymywania, wznawiając i zatrzymując nagrywanie.

// CameraX: implement video capture with CameraController.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

// Define a VideoSaveCallback class for handling success and error states.
class VideoSaveCallback : OnVideoSavedCallback {
    override fun onVideoSaved(outputFileResults: OutputFileResults) {
        val msg = "Video capture succeeded: ${outputFileResults.savedUri}"
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
    }

    override fun onError(videoCaptureError: Int, message: String,
                         cause: Throwable?) {
        Log.d(TAG, "error saving video: $message", cause)
    }
}

private fun startStopVideo() {
    if (cameraController.isRecording()) {
        // Stop the current recording session.
        cameraController.stopRecording()
        return
    }

    // Define the File options for saving the video.
    val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
        .format(System.currentTimeMillis())

    val outputFileOptions = OutputFileOptions
        .Builder(File(this.filesDir, name))
        .build()

    // Call startRecording on the CameraController.
    cameraController.startRecording(
        outputFileOptions,
        ContextCompat.getMainExecutor(this),
        VideoSaveCallback()
    )
}

CameraX: CameraProvider,

Jeśli korzystasz z karty CameraProvider, musisz utworzyć VideoCapture UseCase i przekazać obiekt Recorder. Na Recorder.Builder możesz: określ jakość filmu i opcjonalnie FallbackStrategy, czyli zajmuje się przypadkiem, gdy urządzenie nie spełnia wymagań jakościowych. Potem powiąż instancję VideoCapture z instancją CameraProvider ze swoją drugą UseCase s

// CameraX: create and bind a VideoCapture UseCase with CameraProvider.

// Make a reference to the VideoCapture UseCase and Recording at a
// scope that can be accessed throughout the camera logic in your app.
private lateinit var videoCapture: VideoCapture
private var recording: Recording? = null

...

// Create a Recorder instance to set on a VideoCapture instance (can be
// added with other UseCase definitions).
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.FHD))
    .build()
videoCapture = VideoCapture.withOutput(recorder)

...

// Bind UseCases to camera (adding videoCapture along with preview here, but
// preview is not required to use videoCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, videoCapture)

Obecnie aplikacja Recorder jest dostępna w aplikacji videoCapture.output usłudze. Recorder może uruchamiać nagrania wideo zapisane w usłudze File, ParcelFileDescriptor lub MediaStore. W tym przykładzie użyto elementu MediaStore.

Na urządzeniu Recorder możesz je wywołać na kilka sposobów, aby je przygotować. Zadzwoń do nas prepareRecording(), aby ustawić opcje wyjścia MediaStore. Jeśli aplikacja ma uprawnienia do korzystania z mikrofonu urządzenia, wywołaj też też withAudioEnabled(). Następnie wywołaj start(), by rozpocząć nagrywanie, przekazując kontekst i Detektor zdarzeń Consumer<VideoRecordEvent> do obsługi zdarzeń nagrywania wideo. Jeśli zwracany Recording może służyć do wstrzymania, wznowienia lub zatrzymania nagrywanie.

// CameraX: implement video capture with CameraProvider.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun startStopVideo() {
   val videoCapture = this.videoCapture ?: return

   if (recording != null) {
       // Stop the current recording session.
       recording.stop()
       recording = null
       return
   }

   // Create and start a new recording session.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
       .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()

   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .withAudioEnabled()
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(
                           baseContext, msg, Toast.LENGTH_SHORT
                       ).show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "video capture ends with error",
                             recordEvent.error)
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

Dodatkowe materiały

Udostępniamy wiele kompletnych aplikacji CameraX Repozytorium GitHub z przykładami kamer. Dzięki tym przykładom można zrozumieć, jak scenariusze przedstawione w tym przewodniku pasują do pełnego kontekstu: Aplikacja na Androida.

Potrzebujesz dodatkowej pomocy przy migracji do AparatuX lub masz pytania w sprawie pakietu interfejsów API aparatu Android, skontaktuj się z nami na Dyskusja na temat CameraX Grupa.