Jeśli Twoja aplikacja używa oryginalnej klasy Camera
(„Camera1”), która została wycofana od czasu Androida 5.0 (poziom interfejsu API 21), zdecydowanie zalecamy zaktualizowanie go do nowoczesnego interfejsu API aparatu w Androidzie. Android oferuje CameraX (standardowy, niezawodny interfejs API Jetpack) i Camera2 (niski poziom interfejsu API platformy). W większości przypadków zalecamy przeniesienie aplikacji do CameraX. Oto dlaczego tak się dzieje:
- Łatwość obsługi: aplikacja CameraX zajmuje się detalami niskopoziomowymi, więc nie musisz skupiać się na tworzeniu od podstaw obsługi kamery, a więcej na wyróżnianiu aplikacji.
- Aplikacja CameraX obsługuje fragmentację za Ciebie: aplikacja CameraX obniża długoterminowe koszty utrzymania i zmniejsza kod związany z konkretnym urządzeniem, zapewniając użytkownikom lepsze wrażenia. Więcej informacji na ten temat znajdziesz w poście na blogu na temat lepszej zgodności urządzeń z aparatem CameraX.
- Zaawansowane funkcje: Aparat CameraX został zaprojektowany tak, aby można było w łatwy sposób wdrożyć zaawansowane funkcje w aplikacji. Dzięki rozszerzeniom CameraX możesz łatwo stosować np. Bokeh, retusz twarzy, HDR (High Dynamic Range) oraz naświetlać zdjęcia przy słabym oświetleniu.
- Możliwość aktualizacji: Android w ciągu roku udostępnia nowe funkcje i poprawki błędów w aplikacji CameraX. Dzięki migracji do CameraX Twoja aplikacja będzie korzystać z najnowszej technologii aparatów na Androidzie w każdej wersji CameraX, a nie tylko w kolejnych rocznych wersjach Androida.
W tym przewodniku zamieściliśmy typowe scenariusze korzystania z aparatu. Każdy scenariusz obejmuje implementację aparatu Camera1 i implementację CameraX do porównania.
W przypadku migracji czasami potrzebujesz dodatkowej elastyczności, aby zintegrować ją z istniejącą bazą kodu. Cały kod CameraX w tym przewodniku zawiera implementację CameraController
– to świetne rozwiązanie, jeśli potrzebujesz najprostszego sposobu korzystania z aplikacji CameraX i implementacji CameraProvider
. Aby zdecydować, która opcja jest dla Ciebie najlepsza,
oto zalety każdej z nich:
Kontroler aparatu |
Dostawca aparatu |
Wymaga niewielkiego kodu konfiguracji | Większa kontrola |
Jeśli pozwolisz aplikacji CameraX na przeprowadzanie większej części procesu konfiguracji, funkcje takie jak ustawianie ostrości przez dotknięcie i rozsuwanie palcem, by powiększyć obraz, działają automatycznie |
Konfiguracją zajmuje się to programista aplikacji, dlatego masz więcej możliwości dostosowania konfiguracji, na przykład włączenie rotacji obrazu wyjściowego lub ustawienie formatu obrazu wyjściowego w ImageAnalysis
|
Wymaganie atrybutu PreviewView na potrzeby podglądu aparatu pozwala CameraX zapewniać płynną integrację, na przykład w ramach integracji z pakietem ML Kit, który może mapować współrzędne wyników modelu ML (takie jak ramki ograniczające twarz) bezpośrednio na współrzędne podglądu
|
Możliwość użycia niestandardowej „powierzchni” do wyświetlania podglądu aparatu zapewnia większą elastyczność, na przykład przez wykorzystanie istniejącego kodu „Surface”, który może być danymi wejściowymi do innych części aplikacji. |
Jeśli masz problem z migracją, skontaktuj się z nami na grupie dyskusyjnej CameraX.
Przed migracją
Porównanie wykorzystania aplikacji CameraX i Aparat 1
Kod może wyglądać inaczej, ale podstawowe pojęcia w Aparacie 1 i Aparacie X są bardzo podobne. CameraX uwzględnia w swoich zastosowaniach typowe funkcje kamery, dzięki czemu wiele zadań pozostawionych deweloperowi w Aparacie 1 jest realizowanych automatycznie. W aplikacji CameraX są dostępne 4 obiekty UseCase
, których można używać do różnych zadań związanych z kamerą: Preview
, ImageCapture
, VideoCapture
i ImageAnalysis
.
Jednym z przykładów dla programistów obsługujących niskopoziomowe informacje o Aparacie X jest ViewPort
, który jest udostępniany przez aktywne UseCase
. Dzięki temu wszystkie elementy UseCase
widzą dokładnie te same piksele.
W przypadku Aparatu 1 musisz samodzielnie zarządzać tymi szczegółami, a ze względu na zmienność współczynników proporcji w przypadku czujników i ekranów w aparatach urządzeń uzyskanie dopasowania podglądu do zdjęć i filmów może być trudne.
W innym przykładzie CameraX automatycznie obsługuje wywołania zwrotne Lifecycle
w przekazywanej instancji Lifecycle
. Oznacza to, że CameraX zarządza połączeniem aplikacji z kamerą przez cały cykl życia aktywności na Androidzie. Obejmuje to między innymi: zamknięcie aparatu, gdy aplikacja działa w tle, usunięcie podglądu aparatu, gdy ekran nie wymaga już jego wyświetlania, oraz wstrzymywanie podglądu, gdy inna aktywność ma pierwszeństwo, np. inna rozmowa wideo.
Aparat CameraX obsługuje obroty i skalowanie bez żadnego dodatkowego kodu. W przypadku obiektu Activity
z odblokowaną orientacją konfiguracja UseCase
jest wykonywana przy każdym obróceniu urządzenia, ponieważ system niszczy i odtwarza Activity
po zmianie orientacji. Powoduje to, że ustawienie UseCases
powoduje, że rotacja docelowa jest za każdym razem dopasowywana do domyślnej orientacji wyświetlacza.
Więcej informacji o obrotach w Aparacie X
Zanim przejdziemy do szczegółów, przyjrzyjmy się bliżej UseCase
sprawcom aplikacji CameraX i tym, jak może ona wyglądać w przypadku aplikacji Camera1. (Pojęcia związane z Aparatem X są zapisane w kolorze niebieskim, a koncepcje Camera1 są zielone).
Aparat X |
|||
Konfiguracja kontrolera CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Podgląd | Robienie zdjęć | Nagrywanie filmów | Analiza obrazu |
⁞ | ⁞ | ⁞ | ⁞ |
Zarządzaj powierzchnią podglądu i ustawiaj ją w Aparacie | Ustaw funkcję PictureCallback i wywołaj metodę TakePicture() w aparacie | Zarządzanie konfiguracją aparatu i usługi MediaRecorder w określonej kolejności | Niestandardowy kod analizy utworzony na platformie Surface |
↑ | ↑ | ↑ | ↑ |
Kod urządzenia | |||
↑ | |||
Zarządzanie rotacją i skalowaniem urządzeń | |||
↑ | |||
Zarządzanie sesjami kamery (wybór kamery, zarządzanie cyklem życia) | |||
Aparat 1 |
Zgodność i wydajność w aplikacji CameraX
CameraX obsługuje urządzenia z Androidem 5.0 (poziom interfejsu API 21) lub nowszym. Dotyczy to ponad 98% obecnych urządzeń z Androidem. CameraX została opracowana, aby automatycznie obsługiwać różnice między urządzeniami, co zmniejsza konieczność umieszczania w aplikacji kodu. Dodatkowo od wersji 5.0 w Laboratorium CameraX testujemy ponad 150 urządzeń fizycznych, na wszystkie wersje Androida. Możesz przejrzeć pełną listę urządzeń objętych obecnie Laboratorium.
CameraX używa Executor
do sterowania stosem kamer. Możesz ustawić własnego wykonawcy w Aparacie X, jeśli aplikacja ma określone wymagania dotyczące wątków. Jeśli zasada jest nieskonfigurowana, CameraX tworzy i używa zoptymalizowanego wewnętrznego Executor
. Wiele interfejsów API platformy, na których stworzono CameraX, wymaga blokowania komunikacji międzyprocesowej (IPC) za pomocą sprzętu, którego działanie może czasem trwać setki milisekund. Z tego powodu CameraX wywołuje te interfejsy API tylko z wątków w tle, dzięki czemu wątek główny nie jest blokowany, a interfejs pozostaje płynny.
Więcej informacji o wątkach
Jeśli rynek docelowy Twojej aplikacji obejmuje słabsze urządzenia, CameraX zapewnia skrócenie czasu konfiguracji dzięki ograniczaniu kamery. Ponieważ proces łączenia z komponentami sprzętowymi może trwać dość długo, zwłaszcza na słabszych urządzeniach, możesz określić zestaw kamer, których potrzebuje Twoja aplikacja. CameraX łączy się z tymi kamerami tylko podczas ich konfiguracji. Jeśli na przykład aplikacja używa tylko tylnych aparatów, można ustawić tę konfigurację za pomocą ustawienia DEFAULT_BACK_CAMERA
. Wtedy CameraX nie inicjuje przednich aparatów, by zmniejszyć opóźnienie.
Pojęcia związane z programowaniem Androida
W tym przewodniku przyjęto ogólną wiedzę o programowaniu aplikacji na Androida. Oprócz podstawowych informacji oto kilka koncepcji, które warto poznać przed zagłębieniem się w kod:
- Funkcja View Binding generuje klasę powiązania dla plików układu XML, dzięki czemu możesz łatwo odwoływać się do widoków w działaniach, jak w kilku fragmentach kodu poniżej. Istnieją pewne różnice między powiązaniem widoku a elementem
findViewById()
(wcześniejszym sposobem odwoływania się do widoków), ale w poniższym kodzie powinno być możliwe zastąpienie wierszy powiązania widoku podobnym wywołaniemfindViewById()
. - Korutyny asynchroniczne to wzorzec projektu równoczesności dodany w Kotlin 1.3, który może być używany do obsługi metod CameraX zwracających wartość
ListenableFuture
. Teraz jest to łatwiejsze dzięki bibliotece Concurrent Jetpack od wersji 1.1.0. Aby dodać konfigurację asynchroniczną do aplikacji:- Dodaj
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
do pliku Gradle. - Umieść kod CameraX, który zwraca wartość
ListenableFuture
, w blokulaunch
lub funkcji zawieszenia. - Dodaj do wywołania funkcji
await()
wywołanie funkcji, które zwraca wartośćListenableFuture
. - Dokładniejsze informacje o tym, jak działają współprogramy, znajdziesz w przewodniku Uruchamianie współprogramowania.
- Dodaj
Migracja typowych scenariuszy
W tej sekcji wyjaśniamy, jak przenieść typowe scenariusze z Aparatu 1 do CameraX.
Każdy scenariusz obejmuje: Aparat1, Aparat CameraProvider
i Aparat CameraController
.
Wybieram kamerę
Jedną z pierwszych rzeczy, jakie warto zaoferować w aplikacji aparatu, jest możliwość wyboru różnych kamer.
Aparat 1
W aplikacji Aparat 1 możesz wywołać metodę Camera.open()
bez parametrów, aby otworzyć pierwszy aparat tylny, lub przekazać w postaci liczby całkowitej identyfikator kamery, którą chcesz uruchomić. 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: kontroler kamery
W aplikacji CameraX wybór kamery jest obsługiwany przez klasę CameraSelector
. CameraX to prosty przypadek użycia domyślnej kamery. Możesz wybrać domyślny przedni lub tylny aparat. Dodatkowo obiekt CameraControl
w aplikacji CameraX umożliwia łatwe ustawienie poziomu powiększenia w aplikacji. Jeśli aplikacja działa na urządzeniu, które obsługuje aparaty logiczne, przełączy się ona na odpowiedni obiektyw.
Oto kod aparatu CameraX, który pozwala użyć domyślnego tylnego aparatu na urządzeniu 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 w CameraProvider
(przedni lub tylny aparat może być używany z 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 kontrolować, która kamera jest wybrana, możesz to zrobić również w AparacieX, jeśli używasz CameraProvider
, wywołując getAvailableCameraInfos()
. Otrzymasz obiekt CameraInfo
do sprawdzenia określonych właściwości kamery, np. isFocusMeteringSupported()
.
Następnie możesz przekonwertować go na CameraSelector
, aby używać go jak w powyższych przykładach za pomocą metody CameraInfo.getCameraSelector()
.
Więcej informacji o poszczególnych kamerach możesz uzyskać dzięki klasie Camera2CameraInfo
. Wywołaj getCameraCharacteristic()
i użyj klucza odpowiadającego danym kamery, których potrzebujesz. W klasie CameraCharacteristics
znajdziesz listę wszystkich kluczy, o które możesz wysyłać zapytania.
Oto przykład użycia niestandardowej funkcji checkFocalLength()
, którą można 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świetlam podgląd
W większości aplikacji do obsługi kamery musi być jakiś moment, żeby obraz z kamery pojawił się na ekranie. Korzystając z Aparatu1, musisz prawidłowo zarządzać wywołaniami zwrotnymi cyklu życia, a także określić rotację i skalowanie na potrzeby podglądu.
Dodatkowo w Aparacie 1 musisz określić, czy do wyświetlania podglądu chcesz użyć TextureView
czy SurfaceView
.
Obie opcje mają swoje kompromisy. W obu przypadkach usługa Camera1 wymaga prawidłowej obsługi obrotu i skalowania. Z kolei funkcja PreviewView
firmy CameraX ma implementacje bazowe dla komponentów TextureView
i SurfaceView
.
CameraX decyduje, która implementacja jest najlepsza na podstawie takich czynników jak typ urządzenia i wersja Androida, na której działa aplikacja. Jeśli 1 z nich jest zgodna, możesz zadeklarować swoje preferencje w PreviewView.ImplementationMode
.
Opcja COMPATIBLE
używa TextureView
do wyświetlania podglądu, a wartość PERFORMANCE
używa właściwości SurfaceView
(w miarę możliwości).
Aparat 1
Aby wyświetlić podgląd, musisz napisać własną klasę Preview
z implementacją interfejsu android.view.SurfaceHolder.Callback
, który umożliwia przekazywanie do aplikacji danych obrazu ze sprzętu aparatu. Zanim będzie można rozpocząć podgląd obrazu na żywo, do obiektu Camera
należy przekazać klasę Preview
.
// 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: kontroler kamery
W aplikacji CameraX nie trzeba zajmować się niczym deweloperem. Jeśli używasz właściwości CameraController
, musisz też użyć właściwości PreviewView
. Oznacza to, że właściwość Preview
UseCase
jest domniemana, przez co 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
W przypadku CameraProvider
w Aparacie X nie musisz używać PreviewView
, ale i tak znacznie upraszcza to konfigurowanie podglądu w porównaniu z Aparatem 1. Na potrzeby prezentacji używany jest w tym przykładzie PreviewView
, ale jeśli masz bardziej złożone potrzeby, możesz napisać niestandardową regułę SurfaceProvider
, która będzie przekazywana do funkcji setSurfaceProvider()
.
W tym przypadku Preview
UseCase
nie jest domniemany, 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() } }
Ustawianie ostrości przez dotknięcie
Gdy podgląd z kamery jest widoczny na ekranie, często ustawiany jest punkt ostrości, gdy użytkownik kliknie podgląd.
Aparat 1
Aby wdrożyć ostrość dotykiem w Aparacie 1, musisz obliczyć optymalną ostrość (Area
), aby wskazać, gdzie Camera
ma starać się ustawić ostrość. Ta kolumna Area
jest przekazywana do setFocusAreas()
. Musisz też ustawić zgodny tryb pełnej koncentracji na urządzeniu Camera
. Obszar ostrości działa tylko wtedy, gdy bieżący tryb ostrości to FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
lub FOCUS_MODE_CONTINUOUS_PICTURE
.
Każdy element Area
jest prostokątem o określonej wadze. Waga ma wartość z zakresu od 1 do 1000 i jest używana do określania priorytetów elementu Areas
, jeśli ustawiono wiele. W tym przykładzie użyto tylko jednej właściwości Area
, więc wartość wagi nie ma znaczenia. Współrzędne prostokąta mają zakres od -1000 do 1000. Punkt w lewym górnym rogu to (-1000, -1000).
Prawy dolny punkt to (1000, 1000). Kierunek zależy od orientacji czujnika, czyli tego, co widzi. Obrót ani odbicie lustrzane elementu Camera.setDisplayOrientation()
nie mają wpływu na kierunek, więc musisz przekonwertować współrzędne 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") } } }
CameraX: kontroler kamery
CameraController
nasłuchuje zdarzeń dotyku, które ma PreviewView
, aby automatycznie ustawić ostrość za pomocą dotknięcia. Klikanie, aby ustawić fokus, możesz włączać i wyłączać za pomocą funkcji setTapToFocusEnabled()
oraz sprawdzać wartość przy użyciu odpowiedniej metody pobierania isTapToFocusEnabled()
.
Metoda getTapToFocusState()
zwraca obiekt LiveData
do śledzenia zmian stanu zaznaczenia na 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
Korzystanie z CameraProvider
wymaga pewnej konfiguracji, aby ułatwić sobie pracę z ekranem dotykowym. W tym przykładzie założono, że używasz PreviewView
. Jeśli nie, musisz dostosować tę zasadę, aby zastosować ją do niestandardowego elementu Surface
.
Gdy używasz PreviewView
:
- Skonfiguruj wykrywanie gestów do obsługi zdarzeń kliknięcia.
- Za pomocą zdarzenia kliknięcia utwórz
MeteringPoint
za pomocąMeteringPointFactory.createPoint()
. - Za pomocą
MeteringPoint
utwórzFocusMeteringAction
. - Z obiektem
CameraControl
na urządzeniuCamera
(zwróconym zbindToLifecycle()
) wywołajstartFocusAndMetering()
, przekazującFocusMeteringAction
. - (Opcjonalnie) Odpowiedz na
FocusMeteringResult
. - Skonfiguruj czujnik gestów tak, aby reagował na zdarzenia dotyku w komponencie
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ągnij palce, aby powiększyć
Powiększanie i pomniejszanie podglądu to inna częsta metoda bezpośredniego manipulacji podglądem z aparatu. Ze względu na rosnącą liczbę aparatów dostępnych na urządzeniach użytkownicy oczekują, że w wyniku powiększenia zostanie automatycznie wybrany obiektyw o najlepszej ogniskowej.
Aparat 1
Istnieją 2 sposoby powiększania za pomocą Aparatu1. Metoda Camera.startSmoothZoom()
animuje się od bieżącego poziomu powiększenia do obecnego poziomu powiększenia. Metoda Camera.Parameters.setZoom()
przeskakuje bezpośrednio do przekazanego poziomu powiększenia. Zanim użyjesz którejś z tych opcji, wywołaj odpowiednio metodę isSmoothZoomSupported()
lub isZoomSupported()
, by mieć pewność, że powiązane metody powiększenia są dostępne w kamerze.
Do wdrożenia funkcji ściągania i rozciągania palcem w tym przykładzie używany jest setZoom()
, ponieważ detektor dotyku na powierzchni podglądu stale uruchamia zdarzenia w miarę wykonywania gestu ściągnięcia palcami, więc za każdym razem natychmiast aktualizuje poziom powiększenia. Klasa ZoomTouchListener
jest zdefiniowana poniżej i powinna być ustawiona jako wywołanie zwrotne dla odbiornika dotyku 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()) }
CameraX: kontroler kamery
Podobnie jak w przypadku klikania, aby ustawić ostrość, CameraController
nasłuchuje zdarzeń dotknięcia obiektu PreviewView, aby automatycznie obsługiwać powiększenie przez ściąganie palców. Możesz włączyć lub wyłączyć ściąganie palcami, aby powiększyć, za pomocą funkcji setPinchToZoomEnabled()
, a także sprawdzić wartość za pomocą odpowiedniej metody pobierania 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 przybliżyć obraz przez ściąganie palców na urządzeniu CameraProvider
, musisz przeprowadzić konfigurację. Jeśli nie korzystasz z tabeli PreviewView
, musisz dostosować zasady, aby zastosować ją do niestandardowego elementu Surface
.
Gdy używasz PreviewView
:
- Skonfiguruj wykrywanie gestów skalowania do obsługi zdarzeń ściągnięcia.
- Pobierz
ZoomState
z obiektuCamera.CameraInfo
, który zwraca wystąpienieCamera
podczas wywoływaniabindToLifecycle()
. - Jeśli
ZoomState
ma wartośćzoomRatio
, zapisz ją jako bieżący współczynnik powiększenia. Jeśli na urządzeniuZoomState
nie ma wskaźnikazoomRatio
, użyj domyślnej szybkości powiększenia (1,0). - Pobierz iloczyn bieżącego współczynnika powiększenia z
scaleFactor
, aby określić nowy współczynnik powiększenia, i prześlij go doCameraControl.setZoomRatio()
. - Skonfiguruj czujnik gestów tak, aby reagował na zdarzenia dotyku w komponencie
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 wywołać zdjęcie – niezależnie od tego, czy musisz to zrobić po naciśnięciu przycisku migawki, po upływie minutnika czy też w przypadku jakiegokolwiek innego zdarzenia związanego z wyborem.
Aparat 1
W Aparacie 1 musisz najpierw zdefiniować Camera.PictureCallback
, aby zarządzać danymi zdjęcia, gdy o to poprosi. Oto prosty przykład użycia właściwości 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) } }
Za każdym razem, gdy zechcesz zrobić zdjęcie, wywołaj metodę takePicture()
w swojej instancji Camera
. Ta metoda takePicture()
ma 3 różne parametry dla różnych typów danych. Pierwszy parametr odnosi się do właściwości ShutterCallback
(w tym przykładzie nie zdefiniowano jej). Drugi parametr służy do obsługi nieprzetworzonych (nieskompresowanych) danych kamery przez PictureCallback
. W tym przykładzie jest używany trzeci parametr, ponieważ to parametr PictureCallback
służący do obsługi danych obrazów JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: kontroler kamery
Funkcja CameraController
w Aparacie X zachowuje prostotę obsługi robienia zdjęć za pomocą Aparatu 1, implementując własną metodę takePicture()
. Tutaj zdefiniuj funkcję umożliwiającą skonfigurowanie wpisu MediaStore
i zrobienie zdjęcia do zapisania w tym miejscu.
// 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ą funkcji CameraProvider
działa prawie tak samo jak w przypadku CameraController
, ale musisz najpierw utworzyć i powiązać obiekt ImageCapture
UseCase
, aby otrzymać obiekt, który chcesz wywołać 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, zadzwoń pod numer ImageCapture.takePicture()
. Pełny przykład funkcji takePhoto()
znajdziesz w kodzie CameraController
w tej sekcji.
// 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ż przedstawiane do tej pory scenariusze. Każdy etap procesu musi być prawidłowo skonfigurowany – zwykle w określonej kolejności. Możesz też sprawdzić, czy obraz i dźwięk są zsynchronizowane, lub rozwiązać ewentualne dodatkowe niespójności na urządzeniach.
Jak widzisz, CameraX ponownie w dużej mierze radzi sobie z tym złożonością.
Aparat 1
Nagrywanie filmów za pomocą Aparatu 1 wymaga starannego zarządzania elementami Camera
i MediaRecorder
, a metody muszą być wywoływane w określonej kolejności. Aby aplikacja działała prawidłowo, musisz wykonać te czynności:
- Otwórz aparat.
- Przygotuj i otwórz podgląd (jeśli aplikacja pokazuje nagrywany film – zwykle tak jest).
- Odblokuj aparat, aby
MediaRecorder
mógł go używać, dzwoniąc pod numerCamera.unlock()
. - Skonfiguruj nagranie, wywołując w
MediaRecorder
te metody:- Połącz instancję
Camera
z usługąsetCamera(camera)
. - Będziesz dzwonić pod numer
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Będziesz dzwonić pod numer
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Wywołaj
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 aplikacja udostępnia podgląd filmu, 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)
. - Wywołaj
prepare()
, aby dokończyć konfigurację urządzeniaMediaRecorder
.
- Połącz instancję
- Aby rozpocząć nagrywanie, zadzwoń pod numer
MediaRecorder.start()
. - Aby zatrzymać nagrywanie, wywołaj te metody. Tutaj też powtórz te czynności:
- Będziesz dzwonić pod numer
MediaRecorder.stop()
. - Opcjonalnie usuń bieżącą konfigurację
MediaRecorder
, wywołując metodęMediaRecorder.reset()
. - Będziesz dzwonić pod numer
MediaRecorder.release()
. - Zablokuj kamerę, by mogły jej używać przyszłe sesje
MediaRecorder
, wywołując metodęCamera.lock()
.
- Będziesz dzwonić pod numer
- Aby wyłączyć podgląd, zadzwoń pod numer
Camera.stopPreview()
. - Aby na koniec zwolnić funkcję
Camera
, tak aby mogły jej używać inne procesy, wywołajCamera.release()
.
Oto wszystkie te etapy:
// 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: kontroler kamery
Za pomocą CameraController
w AparacieX możesz przełączać obiekty ImageCapture
, VideoCapture
i ImageAnalysis
UseCase
niezależnie od tego, o ile lista przypadków użycia może być używana równocześnie.
Urządzenia ImageCapture
i ImageAnalysis
UseCase
są domyślnie włączone, dlatego nie musisz dzwonić pod numer setEnabledUseCases()
, żeby zrobić zdjęcie.
Aby używać urządzenia CameraController
do nagrywania wideo, musisz najpierw użyć setEnabledUseCases()
, by zezwolić na UseCase
VideoCapture
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Aby rozpocząć nagrywanie filmu, możesz wywołać funkcję CameraController.startRecording()
. Ta funkcja może zapisać nagrany film w elemencie File
, jak widać w przykładzie poniżej. Dodatkowo musisz przekazać obiekt Executor
i klasę, która implementuje OnVideoSavedCallback
do obsługi wywołań zwrotnych zakończonych powodzeniem i błędów. Po zakończeniu nagrywania wywołaj metodę CameraController.stopRecording()
.
Uwaga: jeśli używasz CameraX w wersji 1.3.0-alpha02 lub nowszej, dostępny jest dodatkowy parametr AudioConfig
, który umożliwia włączanie i wyłączanie nagrywania dźwięku w filmach. Aby włączyć nagrywanie dźwięku, musisz mieć uprawnienia do korzystania z mikrofonu.
Dodatkowo w wersjach 1.3.0-alfa02 metoda stopRecording()
usuwana jest, a 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óry obsługuje przypadki, gdy urządzenie nie spełnia wymagań dotyczących jakości. Następnie powiąż instancję VideoCapture
z CameraProvider
z pozostałymi elementami 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)
Obecnie dostęp do Recorder
jest dostępny w usłudze videoCapture.output
. Recorder
może uruchamiać nagrania wideo zapisane w usłudze File
, ParcelFileDescriptor
lub MediaStore
. W tym przykładzie użyto właściwości MediaStore
.
W Recorder
można wywołać kilka metod, aby go przygotować. Wywołaj prepareRecording()
, aby ustawić opcje wyjściowe elementu MediaStore
. Jeśli aplikacja ma uprawnienia do korzystania z mikrofonu urządzenia, zadzwoń też pod numer withAudioEnabled()
.
Następnie wywołaj start()
, aby rozpocząć nagrywanie, przekazując kontekst i detektor zdarzeń Consumer<VideoRecordEvent>
do obsługi zdarzeń nagrywania wideo. Jeśli operacja się uda, zwróconego Recording
można użyć do wstrzymania, wznowienia lub zatrzymania 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 naszym repozytorium na GitHubie z przykładami aparatu znajdziesz kompletne aplikacje CameraX. Te przykłady pokazują, jak scenariusze zawarte w tym przewodniku pasują do w pełni funkcjonalnej aplikacji na Androida.
Jeśli potrzebujesz dodatkowej pomocy przy migracji do CameraX lub masz pytania związane z interfejsem Android Camera API, skontaktuj się z nami w grupie dyskusyjnej poświęconej CameraX.