Jeśli Twoja aplikacja korzysta z pierwotnej klasy Camera
(„Camera1”), która została wycofana w wersji Androida 5.0 (poziom API 21), zdecydowanie zalecamy przejście na nowy interfejs API aparatu na Androida. Android oferuje CameraX (standardowy, niezawodny interfejs API Jetpack do obsługi aparatu) i Camera2 (interfejs API frameworku na niskim poziomie). W większości przypadków zalecamy przeniesienie aplikacji do CameraX. Przyczyna jest następująca:
- Łatwość obsługi: CameraX obsługuje szczegóły niskiego poziomu, dzięki czemu możesz mniej skupiać się na tworzeniu aparatu od podstaw, a bardziej na wyróżnieniu aplikacji.
- CameraX radzi sobie z fragmentacją: CameraX zmniejsza długoterminowe koszty konserwacji i kodu związanego z konkretnym urządzeniem, zapewniając użytkownikom wyższą jakość. Więcej informacji znajdziesz w poście na blogu Zwiększenie zgodności z urządzeniami dzięki CameraX.
- Funkcje zaawansowane: aplikacja CameraX została zaprojektowana tak, aby ułatwić Ci stosowanie zaawansowanych funkcji w aplikacji. Możesz na przykład łatwo stosować efekt bokeh, retusz twarzy, HDR (High Dynamic Range) i tryb nocny do swoich zdjęć za pomocą rozszerzeń CameraX.
- Możliwość aktualizacji: w ciągu roku Android wprowadza nowe funkcje i poprawki błędów w CameraX. Dzięki migracji na CameraX Twoja aplikacja będzie korzystać z najnowszej technologii aparatu Androida w każdej wersji CameraX, a nie tylko w rocznych wersjach Androida.
W tym przewodniku znajdziesz typowe scenariusze dotyczące aplikacji do obsługi aparatu. Każdy scenariusz zawiera implementację Camera1 i CameraX do porównania.
W przypadku migracji czasami potrzebujesz większej elastyczności, aby zintegrować się z istniejącą bazą kodu. Cały kod CameraX w tym przewodniku ma implementację CameraController
, która jest świetna, jeśli chcesz użyć CameraX w najprostszy sposób, a także implementację CameraProvider
, która jest świetna, jeśli potrzebujesz większej elastyczności. Aby ułatwić Ci wybór odpowiedniej opcji, przedstawiamy korzyści płynące z każdej z nich:
CameraController |
CameraProvider |
Wymaga niewielkiej ilości kodu konfiguracyjnego | Większa kontrola |
Przekazanie aplikacji CameraX większej części procesu konfiguracji oznacza, że funkcje takie jak skupianie przez dotknięcie czy powiększanie przez zbliżenie działają automatycznie. |
Ponieważ konfiguracją zajmuje się deweloper aplikacji, masz więcej możliwości dostosowania konfiguracji, na przykład włączenia funkcji obracania obrazu wyjściowego lub ustawienia formatu obrazu wyjściowego w pliku ImageAnalysis .
|
Wymaganie PreviewView w przypadku podglądu aparatu umożliwia CameraX oferowanie płynnej integracji end-to-end, podobnie jak w przypadku integracji z ML Kit, która może mapować współrzędne wyników modelu ML (np. ramki ograniczające twarz) bezpośrednio na współrzędne podglądu.
|
Możliwość używania niestandardowej „powierzchni” do podglądu aparatu daje większą elastyczność, np. możliwość użycia dotychczasowego kodu „powierzchni” jako danych wejściowych w innych częściach aplikacji. |
Jeśli napotkasz problemy podczas migracji, skontaktuj się z nami w grupie dyskusyjnej CameraX.
Zanim przeprowadzisz migrację
Porównanie wykorzystania aplikacji CameraX i Camera1
Chociaż kod może wyglądać inaczej, zastosowane w Camera1 i CameraX koncepcje są bardzo podobne. CameraX umieszcza wspólne funkcje aparatu w ramach konkretnych zastosowań, dzięki czemu wiele zadań, które w aplikacji Camera1 należało do dewelopera, jest wykonywanych automatycznie przez CameraX. W aplikacji CameraX są 4 UseCase
, które możesz stosować do różnych zadań związanych z aparatem: Preview
, ImageCapture
, VideoCapture
i ImageAnalysis
.
Przykładem obsługi przez CameraX szczegółów niskiego poziomu dla deweloperów jest ViewPort
, który jest udostępniany aktywnym UseCase
. Dzięki temu wszystkie UseCase
widzą dokładnie te same piksele.
W aplikacji Camera1 musisz samodzielnie zarządzać tymi szczegółami, a z uwagi na zmienność formatów obrazu na czujnikach i ekranach aparatów na różnych urządzeniach może być trudno zapewnić, aby podgląd pasował do zrobionych zdjęć i filmów.
Innym przykładem jest CameraX, który automatycznie obsługuje wywołania zwrotne Lifecycle
w przekazanej instancji Lifecycle
. Oznacza to, że CameraX obsługuje połączenie aplikacji z kamerą przez cały cykl życia aktywności Androida, w tym w takich przypadkach: gdy aplikacja przechodzi na drugi plan, gdy nie jest już potrzebne wyświetlanie podglądu kamery, oraz gdy inna aktywność ma pierwszeństwo, np. przychodzący połączenie wideo.
Na koniec CameraX obsługuje obracanie i skalowanie bez potrzeby dodawania dodatkowego kodu. W przypadku Activity
z odblokowaną orientacją konfiguracja UseCase
jest wykonywana za każdym razem, gdy urządzenie zostanie obrócone, ponieważ system niszczy i ponownie tworzy Activity
po zmianie orientacji. W rezultacie UseCases
ustawia domyślnie dopasowanie obrotu do orientacji wyświetlacza.
Więcej informacji o obrotach w CameraX
Zanim przejdziemy do szczegółów, przyjrzyjmy się ogólnie interfejsom API CameraXUseCase
i aplikacji Camera1. (Koncepcje dotyczące aparatu X są w kolorze niebieskim, a koncepcje dotyczące aparatu 1 – w kolorze zielonym.)
CameraX |
|||
Konfiguracja CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Podgląd | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
Zarządzanie powierzchnią podglądu i jej ustawieniami w aparacie | Ustaw PictureCallback i wywołaj takePicture() w Aparacie | Zarządzanie konfiguracją aparatu i MediaRecorder w określonej kolejności | niestandardowy kod analizy utworzony na podstawie interfejsu Surface w wersji podglądu; |
↑ | ↑ | ↑ | ↑ |
Kod dla konkretnego urządzenia | |||
↑ | |||
Zarządzanie rotacją i skalowaniem urządzeń | |||
↑ | |||
Zarządzanie sesją kamery (wybór kamery, zarządzanie cyklem życia) | |||
Camera1 |
Zgodność i wydajność w aplikacji CameraX
CameraX obsługuje urządzenia z Androidem 5.0 (poziom interfejsu API 21) lub nowszym. To ponad 98% dotychczasowych urządzeń z Androidem. Biblioteka CameraX została zaprojektowana tak, aby automatycznie obsługiwać różnice między urządzeniami, co zmniejsza potrzebę stosowania w aplikacji kodu specyficznego dla danego urządzenia. Ponadto w naszym Laboratorium testowym CameraX testujemy ponad 150 fizycznych urządzeń z Androidem we wszystkich wersjach od 5.0. Możesz przejrzeć pełną listę urządzeń obecnie używanych w Test Lab.
CameraX używa Executor
do obsługi zestawu kamer. Jeśli Twoja aplikacja ma określone wymagania dotyczące wątków, możesz ustawić własny wykonawca w CameraX. Jeśli nie jest ustawiony, CameraX tworzy i używa domyślnego wewnętrznego Executor
. Wiele interfejsów API platformy, na której opiera się CameraX, wymaga blokowania komunikacji międzyprocesowej (IPC) z urządzeniami, na odpowiedź których czasami trzeba czekać setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko z wątków w tle, co zapewnia, że wątek główny nie jest zablokowany i interfejs użytkownika pozostaje płynny.
Więcej informacji o wątkach
Jeśli na rynku docelowym Twojej aplikacji znajdują się urządzenia niskiej klasy, CameraX umożliwia skrócenie czasu konfiguracji za pomocą ogranicznika kamery. Proces łączenia z urządzeniami może zająć sporo czasu, zwłaszcza na urządzeniach niskiej klasy. Możesz więc określić zestaw kamer, których potrzebuje Twoja aplikacja. Aplikacja CameraX łączy się z tymi kamerami tylko podczas konfiguracji. Jeśli na przykład aplikacja korzysta tylko z tylnego aparatu, może ustawić tę konfigurację za pomocą DEFAULT_BACK_CAMERA
, a następnie CameraX uniknie inicjowania przednich aparatów, aby zmniejszyć opóźnienie.
Koncepcje związane z tworzeniem aplikacji na Androida
W tym przewodniku zakładamy, że znasz podstawy programowania aplikacji na Androida. Oprócz podstaw warto poznać kilka pojęć, które pomogą Ci zrozumieć kod poniżej:
- View Binding generuje klasę wiązania dla plików układu XML, co umożliwia łatwe odwoływanie się do widoków w aktywnościach, jak to zostało pokazane w kilku fragmentach kodu poniżej. Istnieją pewne różnice między view binding a
findViewById()
(poprzedni sposób odniesienia do widoków), ale w poniżej zamieszczonym kodzie możesz zastąpić wiersze view binding podobnym wywołaniemfindViewById()
. - Asynchroniczne coroutines to wzór projektowania współbieżności dodany w Kotlinie 1.3, który można wykorzystać do obsługi metod CameraX zwracających wartość
ListenableFuture
. Ułatwia to biblioteka Concurrent z Jetpacka w wersji 1.1.0. Aby dodać do aplikacji asynchroniczną coroutine:- Dodaj
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
do pliku Gradle. - Umieść kod CameraX, który zwraca
ListenableFuture
, w blokulaunch
lub w funkcji wstrzymywania. - Dodaj do wywołania funkcji, które zwraca wartość
ListenableFuture
, wywołanie funkcjiawait()
. - Aby dowiedzieć się więcej o tym, jak działają coroutine, zapoznaj się z przewodnikiem uruchamiania coroutine.
- Dodaj
Przenoszenie w typowych sytuacjach
Z tej sekcji dowiesz się, jak przenieść typowe scenariusze z Camera1 do CameraX.
Każdy scenariusz obejmuje implementację Camera1, implementację CameraXCameraProvider
i implementację CameraXCameraController
.
Wybieranie kamery
Jedną z pierwszych rzeczy, które warto dodać do aplikacji aparatu, jest możliwość wyboru różnych kamer.
Camera1
W Camera1 możesz wywołać metodę Camera.open()
bez parametrów, aby otworzyć pierwszy tylny aparat, lub możesz przekazać identyfikator całkowity aparatu, który chcesz otworzyć. Oto przykład, jak to może 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 }
CameraX: CameraController
W CameraX wybór aparatu jest obsługiwany przez klasę CameraSelector
. CameraX ułatwia korzystanie z domyślnego aparatu. Możesz określić, czy chcesz użyć domyślnej przedniej kamery czy domyślnej tylnej kamery. Ponadto obiekt CameraControl
w CameraX umożliwia łatwe ustawienie poziomu powiększenia w aplikacji. Jeśli aplikacja działa na urządzeniu obsługującym logiczne aparaty, przełączy się na odpowiedni obiektyw.
Oto kod CameraX do korzystania z domyślnego tylnego aparatu w ramach 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ą właściwości CameraProvider
(dostępne są opcje 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 kontrolować, która kamera jest wybrana, możesz to zrobić w CameraX, jeśli używasz CameraProvider
, wywołując getAvailableCameraInfos()
, co daje Ci obiekt CameraInfo
do sprawdzania niektórych właściwości aparatu, takich jak isFocusMeteringSupported()
.
Następnie możesz przekształcić go w element CameraSelector
, który będzie używany w sposób podobny do pokazanego w powyższych przykładach z metodą CameraInfo.getCameraSelector()
.
Więcej informacji o poszczególnych kamerach znajdziesz w klasie Camera2CameraInfo
. Wywołaj funkcję getCameraCharacteristic()
z kluczem do wybranych danych z kamery. Sprawdź klasę CameraCharacteristics
, aby zobaczyć listę wszystkich kluczy, których możesz używać w zapytaniach.
Oto przykład użycia niestandardowej funkcji checkFocalLength()
, którą możesz zdefiniować samodzielnie:
// 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 musi w jakimś momencie wyświetlić obraz z kamery na ekranie. W przypadku Camera1 musisz prawidłowo zarządzać wywołaniami zwrotnymi cyklu życia, a także określić obrót i powiększenie podglądu.
Dodatkowo w Kamera1 musisz zdecydować, czy chcesz użyć jako powierzchni podglądu TextureView
czy SurfaceView
.
Oba rozwiązania mają swoje wady, a w obu przypadkach Camera1 wymaga prawidłowego obsługiwania obracania i powiększania. Z drugiej strony, interfejs PreviewView
w CameraX ma implementacje zarówno dla TextureView
, jak i SurfaceView
.
CameraX wybiera najlepszą implementację na podstawie takich czynników jak typ urządzenia i wersja Androida, na której działa aplikacja. Jeśli którakolwiek z implementacji jest zgodna, możesz określić preferowany format za pomocą parametru PreviewView.ImplementationMode
.
Opcja COMPATIBLE
używa TextureView
do podglądu, a wartość PERFORMANCE
używa SurfaceView
(jeśli to możliwe).
Camera1
Aby wyświetlić podgląd, musisz napisać własną klasę Preview
z implementacją interfejsu android.view.SurfaceHolder.Callback
, który służy do przekazywania danych obrazu z aparatu do aplikacji. Następnie, zanim rozpoczniesz podgląd obrazu na żywo, klasa Preview
musi zostać przekazana 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) } }
CameraX: CameraController
W CameraX masz znacznie mniej elementów do zarządzania. Jeśli używasz reguły CameraController
, musisz też użyć reguły PreviewView
. Oznacza to, że Preview
UseCase
jest domyślnie ustawiony, co znacznie ułatwia konfigurację:
// 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 funkcji CameraProvider
w CameraX nie musisz używać funkcji PreviewView
, ale znacznie upraszcza to konfigurowanie podglądu w porównaniu z Camera1. W tym przykładzie na potrzeby demonstracji użyto funkcji PreviewView
, ale jeśli masz bardziej złożone potrzeby, możesz napisać niestandardową funkcję SurfaceProvider
i przekazać ją do funkcji setSurfaceProvider()
.
W tym przypadku Preview
UseCase
nie jest domyślnie ustawiony tak jak w przypadku CameraController
, więc musisz go 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 wyostrzyć
Gdy podgląd kamery jest widoczny na ekranie, można ustawić punkt ostrości, gdy użytkownik kliknie podgląd.
Camera1
Aby w aplikacji Camera1 wdrożyć funkcję „dotknij, aby ustawić ostrość”, musisz obliczyć optymalny punkt ostrości Area
, aby wskazać, gdzie Camera
ma się skupić. Ten element Area
jest przekazywany do elementu setFocusAreas()
. Musisz też ustawić zgodny tryb fokusa na urządzeniu Camera
. Obszar skupienia ma zastosowanie tylko wtedy, gdy bieżący tryb skupienia to 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ść z zakresu 1–1000, która służy do ustalania priorytetów Areas
, jeśli ustawiono kilka takich wartości. W tym przykładzie jest używana tylko jedna wartość Area
, więc wartość wagi nie ma znaczenia. Współrzędne prostokąta mieszczą się w zakresie od -1000 do 1000. Punkt w lewym górnym rogu ma współrzędne (-1000, -1000).
Punkt w prawym dolnym rogu ma współrzędne (1000, 1000). Kierunek jest określany względem orientacji czujnika, czyli tego, co widzi czujnik. Kierunek nie jest zależny od obracania ani lustrzanego odbicia Camera.setDisplayOrientation()
, więc musisz przekształcić współrzędne zdarzenia dotykowego w 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") } } }
CameraX: CameraController
CameraController
nasłuchuje zdarzeń dotyku PreviewView
, aby automatycznie obsługiwać funkcję „dotknij, aby wyśrodkować”. Funkcję „dotknij, aby wyśrodkować” można włączyć i wyłączyć za pomocą metody setTapToFocusEnabled()
, a wartość można sprawdzić za pomocą odpowiedniej metody gettera isTapToFocusEnabled()
.
Metoda getTapToFocusState()
zwraca obiekt LiveData
, który służy do śledzenia zmian stanu fokusa na elemencie 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 korzystać z funkcji „dotknij, aby wyśrodkować”, musisz najpierw skonfigurować CameraProvider
. W tym przykładzie założono, że używasz tagu PreviewView
. Jeśli nie, musisz dostosować logikę do zastosowania w przypadku niestandardowego Surface
.
Oto, co należy zrobić, gdy używasz PreviewView
:
- Skonfiguruj detektor gestów, aby obsługiwał zdarzenia dotyku.
- W przypadku zdarzenia dotknięcia utwórz
MeteringPoint
za pomocą funkcjiMeteringPointFactory.createPoint()
. - Utwórz
FocusMeteringAction
za pomocąMeteringPoint
. - Gdy obiekt
CameraControl
jest dostępny w TwoimCamera
(zwrócony zbindToLifecycle()
), wywołaj funkcjęstartFocusAndMetering()
, przekazując jej argumentFocusMeteringAction
. - (Opcjonalnie) Odpowiedz na
FocusMeteringResult
. - W
PreviewView.setOnTouchListener()
ustaw, aby wykrywanie gestów reagowało na zdarzenia dotykowe.
// 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 }
Rozciągnij, aby powiększyć
Powiększanie i pomniejszanie podglądu to kolejna powszechna metoda bezpośredniej manipulacji podglądem kamery. Wraz ze wzrostem liczby aparatów na urządzeniach użytkownicy oczekują, że obiektyw o najlepszej ogniskowej zostanie automatycznie wybrany w wyniku przybliżenia.
Camera1
Zdjęcia można powiększać na 2 sposoby. Metoda Camera.startSmoothZoom()
animuje przejście od bieżącego poziomu powiększenia do podanego przez Ciebie poziomu powiększenia. Metoda Camera.Parameters.setZoom()
przechodzi bezpośrednio do poziomu powiększenia, który podasz. Zanim użyjesz którejkolwiek z nich, zadzwoń odpowiednio do isSmoothZoomSupported()
lub isZoomSupported()
, aby upewnić się, że potrzebne metody powiększenia są dostępne w aparacie.
Aby zaimplementować funkcję powiększania przez zbliżanie, przykład używa setZoom()
, ponieważ w miarę wykonywania przez użytkownika gestu zbliżania na powierzchni podglądu ciągle wywołuje on zdarzenia, które aktualizują poziom powiększenia natychmiast po każdym takim geście. Poniżej zdefiniowano klasę ZoomTouchListener
, która powinna być ustawiona jako wywołanie zwrotne dla interfejsu dotykowego powierzchni 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()) }
CameraX: CameraController
Podobnie jak w przypadku funkcji „dotknij, aby wyostrzyć”, CameraController
reaguje na zdarzenia dotykowe w PreviewView, aby automatycznie obsługiwać powiększanie przez rozciąganie. Możesz włączać i wyłączać funkcję powiększania za pomocą palców za pomocą metody setPinchToZoomEnabled()
, a wartość sprawdzać za pomocą odpowiedniej metody gettera isPinchToZoomEnabled()
.
Metoda getZoomState()
zwraca obiekt LiveData
do śledzenia zmian w 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 funkcja powiększania za pomocą gestów działała w aplikacji CameraProvider
, musisz ją skonfigurować. Jeśli nie używasz funkcji PreviewView
, musisz dostosować logikę do zastosowania w przypadku niestandardowej funkcji Surface
.
Oto, co należy zrobić, gdy używasz PreviewView
:
- Skonfiguruj detektor gestów przesunięcia, aby obsługiwać gesty ściśnięcia.
- Pobierz
ZoomState
z obiektuCamera.CameraInfo
, gdzie zwracany jest obiektCamera
, gdy wywołaszbindToLifecycle()
. - Jeśli element
ZoomState
ma wartośćzoomRatio
, zapisz ją jako bieżący współczynnik powiększenia. Jeśli w elementachZoomState
nie ma wartościzoomRatio
, użyj domyślnego współczynnika powiększenia aparatu (1,0). - Aby określić nowy współczynnik powiększenia, należy pomnożyć bieżący współczynnik powiększenia przez
scaleFactor
i przekazać go doCameraControl.setZoomRatio()
. - W
PreviewView.setOnTouchListener()
ustaw, aby wykrywanie gestów reagowało na zdarzenia dotykowe.
// 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
Z tej sekcji dowiesz się, jak wywołać zrobienie zdjęcia, niezależnie od tego, czy chcesz to zrobić po naciśnięciu przycisku migawki, po upływie czasu lub po innym zdarzeniu.
Camera1
W ramach usługi Camera1 najpierw definiujesz parametr Camera.PictureCallback
, aby zarządzać danymi obrazu, gdy zostanie to poproszone. Oto prosty przykład funkcji 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) } }
Następnie, gdy chcesz zrobić zdjęcie, wywołujesz metodę takePicture()
w obiekcie Camera
. Ta metoda takePicture()
ma 3 różne parametry dla różnych typów danych. Pierwszy parametr to ShutterCallback
(niezdefiniowany w tym przykładzie). Drugi parametr to PictureCallback
, który obsługuje dane w postaci nieskompresowanych danych z kamery. Trzeci parametr jest używany w tym przykładzie, ponieważ jest to PictureCallback
do obsługi danych obrazu JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
Funkcja CameraController
w CameraX zachowuje prostotę Camera1 w zakresie rejestrowania obrazu, ponieważ implementuje własną metodę takePicture()
. Tutaj zdefiniuj funkcję konfigurowania wpisu MediaStore
i zrób zdjęcie, które ma zostać zapisane.
// 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ą CameraProvider
działa prawie tak samo jak w przypadku CameraController
, ale najpierw musisz utworzyć i powiązać ImageCapture
UseCase
, aby mieć obiekt, w którym można wywołać funkcję 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)
Gdy chcesz zrobić zdjęcie, możesz zadzwonić do ImageCapture.takePicture()
. Pełny przykład funkcji takePhoto()
znajdziesz w tym dziale w kodzie CameraController
.
// 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 filmu jest znacznie bardziej skomplikowane niż do tej pory analizowane scenariusze. Każdy etap procesu musi być prawidłowo skonfigurowany, zwykle w określonej kolejności. Może też być konieczne sprawdzenie, czy film i dźwięk są zsynchronizowane, lub rozwiązanie dodatkowych problemów z urządzeniem.
Jak zobaczysz, CameraX ponownie zadba o wiele z tych złożonych kwestii.
Camera1
Nagrywanie filmów za pomocą Camera1 wymaga ostrożnego zarządzania metodami Camera
i MediaRecorder
. Metody te muszą być wywoływane w określonej kolejności. Aby aplikacja działała prawidłowo, musisz postępować zgodnie z tym porządkiem:
- Otwórz aparat.
- Przygotuj i uruchom podgląd (jeśli aplikacja wyświetla nagrywany film, co zwykle ma miejsce).
- Odblokuj kamerę na potrzeby
MediaRecorder
, dzwoniąc na numerCamera.unlock()
. - Skonfiguruj nagrywanie, wywołując te metody na
MediaRecorder
:- Połącz instancję
Camera
zsetCamera(camera)
. - Będziesz dzwonić pod numer
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Będziesz dzwonić pod numer
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Zadzwoń pod numer
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
, aby ustawić jakość. Wszystkie opcje jakości znajdziesz w sekcjiCamcorderProfile
. - Będziesz dzwonić pod numer
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - Jeśli Twoja aplikacja ma podgląd wideo, zadzwoń pod numer
setPreviewDisplay(preview?.holder?.surface)
. - Będziesz dzwonić pod numer
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - Będziesz dzwonić pod numer
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - Będziesz dzwonić pod numer
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - Aby dokończyć konfigurowanie
MediaRecorder
, zadzwoń pod numerprepare()
.
- Połącz instancję
- Aby rozpocząć nagrywanie, zadzwoń pod numer
MediaRecorder.start()
. - Aby zatrzymać nagrywanie, wywołaj te metody. Ponownie postępuj zgodnie z tą samą kolejnością:
- Będziesz dzwonić pod numer
MediaRecorder.stop()
. - Opcjonalnie możesz usunąć bieżącą konfigurację
MediaRecorder
, wywołując funkcjęMediaRecorder.reset()
. - Będziesz dzwonić pod numer
MediaRecorder.release()
. - Zablokuj kamerę, aby w przyszłości sesje
MediaRecorder
mogły z niej korzystać, wywołującCamera.lock()
.
- Będziesz dzwonić pod numer
- Aby zatrzymać podgląd, zadzwoń pod numer
Camera.stopPreview()
. - Aby uwolnić
Camera
, aby inne procesy mogły z niego korzystać, wywołaj funkcjęCamera.release()
.
Oto wszystkie te czynności w połączeniu:
// 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. } } }
CameraX: CameraController
Za pomocą interfejsu CameraController
w ramach CameraX możesz niezależnie przełączać ImageCapture
,
VideoCapture
i ImageAnalysis
UseCase
,
o ile tylko lista przypadków użycia może być używana jednocześnie.
Domyślnie funkcje ImageCapture
i ImageAnalysis
UseCase
są włączone, więc nie musisz dzwonić do setEnabledUseCases()
, aby zrobić zdjęcie.
Aby używać CameraController
do nagrywania filmów, musisz najpierw użyć setEnabledUseCases()
, aby zezwolić VideoCapture
na UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Aby rozpocząć nagrywanie filmu, możesz wywołać funkcję CameraController.startRecording()
. Ta funkcja umożliwia zapisanie nagranego filmu w File
, jak widać na przykładzie poniżej. Dodatkowo musisz przekazać Executor
i klasę, która implementuje OnVideoSavedCallback
, aby obsługiwać wywołania zwrotne powodzenia i błędu. Gdy nagranie ma się zakończyć, zadzwoń pod numer CameraController.stopRecording()
.
Uwaga: jeśli używasz CameraX 1.3.0-alpha02 lub nowszej wersji, dostępny jest dodatkowy parametr AudioConfig
, który umożliwia włączenie lub wyłączenie nagrywania dźwięku w filmie. Aby włączyć nagrywanie dźwięku, musisz mieć uprawnienia do korzystania z mikrofonu.
Dodatkowo w wersji 1.3.0-alpha02 została usunięta metoda stopRecording()
, a metoda startRecording()
zwraca obiekt Recording
, który można wykorzystać do wstrzymywania, wznawiania i zatrzymywania nagrywania filmu.
// 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 używasz CameraProvider
, musisz utworzyć VideoCapture
UseCase
i przekazać obiekt Recorder
. W Recorder.Builder
możesz ustawić jakość filmu i opcjonalnie FallbackStrategy
, która umożliwia wyświetlanie treści w przypadku, gdy urządzenie nie może spełnić wymagań dotyczących jakości. Następnie powiązać instancję VideoCapture
z CameraProvider
za pomocą innych UseCase
.
// 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: VideoCaptureprivate 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)
W tej chwili usługa Recorder
jest dostępna w usłudze videoCapture.output
. Użytkownik Recorder
może nagrywać filmy, które są zapisywane w urządzeniu File
, ParcelFileDescriptor
lub MediaStore
. W tym przykładzie użyto znacznika MediaStore
.
W przypadku Recorder
możesz użyć kilku metod, aby go przygotować. Wybierz opcję prepareRecording()
, aby skonfigurować opcje wyjściowe MediaStore
. Jeśli aplikacja ma uprawnienia do korzystania z mikrofonu urządzenia, zadzwoń też na numer withAudioEnabled()
.
Następnie wywołaj funkcję start()
, aby rozpocząć nagrywanie, przekazując kontekst i słuchacza zdarzeń Consumer<VideoRecordEvent>
, który będzie obsługiwać zdarzenia nagrywania wideo. Jeśli operacja się powiedzie, zwrócona wartość Recording
może służyć do wstrzymywania, wznawiania i zatrzymywania nagrywania.
// 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
W repozytorium GitHub z przykładowymi aplikacjami CameraX znajdziesz kilka kompletnych aplikacji. Te przykłady pokazują, jak scenariusze opisane w tym przewodniku pasują do pełnej aplikacji na Androida.
Jeśli potrzebujesz dodatkowej pomocy przy migracji do CameraX lub masz pytania dotyczące zestawu interfejsów CameraX API na Androida, skontaktuj się z nami w grupie dyskusyjnej CameraX.