Wenn Ihre App die ursprüngliche Klasse Camera („Camera1“) verwendet, die seit Android 5.0 (API-Level 21) als veraltet gilt, empfehlen wir dringend, auf eine moderne Android-Kamera-API umzustellen. Android bietet CameraX (eine standardisierte, robuste Jetpack-Kamera-API) und Camera2 (eine Framework-API auf niedriger Ebene).
In den meisten Fällen empfehlen wir, Ihre App zu CameraX zu migrieren. Das kann folgende Gründe haben:
- Einfache Bedienung:CameraX kümmert sich um die Details auf niedriger Ebene, sodass Sie sich weniger auf die Entwicklung einer Kamerafunktion von Grund auf und mehr auf die Differenzierung Ihrer App konzentrieren können.
- CameraX kümmert sich um die Fragmentierung: CameraX reduziert langfristige Wartungskosten und gerätespezifischen Code und bietet Nutzern eine höhere Qualität. Weitere Informationen dazu finden Sie in unserem Blogpost Better Device Compatibility with CameraX.
- Erweiterte Funktionen:CameraX wurde sorgfältig entwickelt, um erweiterte Funktionen einfach in Ihre App einzubinden. Mit CameraX-Erweiterungen können Sie beispielsweise ganz einfach Bokeh, Gesichtsretusche, HDR (High Dynamic Range) und den Nachtmodus mit Aufhellung bei wenig Licht auf Ihre Fotos anwenden.
- Aktualisierbarkeit:Android veröffentlicht das ganze Jahr über neue Funktionen und Fehlerkorrekturen für CameraX. Durch die Migration zu CameraX erhält Ihre App mit jeder CameraX-Version die neueste Android-Kameratechnologie, nicht nur mit den jährlichen Android-Versionen.
In diesem Leitfaden finden Sie häufige Szenarien für Kameraanwendungen. Jedes Szenario enthält eine Camera1-Implementierung und eine CameraX-Implementierung zum Vergleich.
Bei der Migration benötigen Sie manchmal zusätzliche Flexibilität, um eine Integration in eine vorhandene Codebasis vorzunehmen. Der gesamte CameraX-Code in diesem Leitfaden hat eine CameraController-Implementierung, die sich gut eignet, wenn Sie CameraX auf unkomplizierte Weise verwenden möchten, und auch eine CameraProvider-Implementierung, die sich gut eignet, wenn Sie mehr Flexibilität benötigen. Hier finden Sie die Vorteile der einzelnen Optionen, damit Sie entscheiden können, welche für Sie am besten geeignet ist:
CameraController |
CameraProvider |
| Nur wenig Einrichtungscode erforderlich | Mehr Kontrolle |
| Wenn CameraX einen größeren Teil des Einrichtungsprozesses übernimmt, funktionieren Funktionen wie „Fokus durch Tippen“ und „Zoomen durch Auf- und Zuziehen“ automatisch. |
Da der App-Entwickler die Einrichtung übernimmt, gibt es mehr Möglichkeiten, die Konfiguration anzupassen, z. B. die Drehung des Ausgabebilds zu aktivieren oder das Ausgabebildformat in ImageAnalysis festzulegen.
|
Wenn PreviewView für die Kameravorschau erforderlich ist, kann CameraX eine nahtlose End-to-End-Integration bieten, wie bei unserer ML Kit-Integration, bei der die Koordinaten des ML-Modellergebnisses (z. B. Begrenzungsrahmen für Gesichter) direkt den Vorschaukoordinaten zugeordnet werden können.
|
Die Möglichkeit, eine benutzerdefinierte „Surface“ für die Kameravorschau zu verwenden, bietet mehr Flexibilität, z. B. die Verwendung Ihres vorhandenen „Surface“-Codes, der eine Eingabe für andere Teile Ihrer App sein kann. |
Wenn Sie bei der Migration nicht weiterkommen, wenden Sie sich an uns.
Vor der Migration
CameraX mit Camera1 vergleichen
Der Code mag anders aussehen, aber die zugrunde liegenden Konzepte in Camera1 und CameraX sind sehr ähnlich. CameraX abstrahiert gängige Kamerafunktionen in Anwendungsfälle. Daher werden viele Aufgaben, die in Camera1 dem Entwickler überlassen wurden, automatisch von CameraX übernommen. CameraX bietet vier UseCases, die Sie für verschiedene Kameraaufgaben verwenden können: Preview, ImageCapture, VideoCapture und ImageAnalysis.
Ein Beispiel dafür, wie CameraX Low-Level-Details für Entwickler verarbeitet, ist die ViewPort, die von aktiven UseCases gemeinsam genutzt wird. So wird sichergestellt, dass alle UseCase genau dieselben Pixel sehen. In Camera1 müssen Sie diese Details selbst verwalten. Aufgrund der unterschiedlichen Seitenverhältnisse auf verschiedenen Geräten ist es schwierig, die Vorschau an die aufgenommenen Medien anzupassen.
Ein weiteres Beispiel: CameraX verarbeitet Lifecycle-Callbacks automatisch in der von Ihnen bereitgestellten Lifecycle-Instanz. Durch diese Architektur übernimmt CameraX die Verbindung Ihrer App zur Kamera während des gesamten Lebenszyklus der Android-Aktivität, einschließlich der folgenden Fälle: Schließen der Kamera, wenn Ihre App in den Hintergrund wechselt; Entfernen der Kameravorschau, wenn sie nicht mehr auf dem Bildschirm angezeigt werden muss; und Pausieren der Kameravorschau, wenn eine andere Aktivität Vorrang hat, z. B. ein eingehender Videoanruf.
Außerdem übernimmt CameraX die Drehung und Skalierung, ohne dass Sie zusätzlichen Code schreiben müssen. Bei einem Activity mit einer entsperrten Ausrichtung wird die UseCase-Einrichtung jedes Mal durchgeführt, wenn das Gerät gedreht wird, da das System das Activity bei Änderungen der Ausrichtung zerstört und neu erstellt. Dadurch wird die Zielrotation standardmäßig jedes Mal an die Ausrichtung des Displays angepasst.UseCases Weitere Informationen zu Rotationen in CameraX
Bevor wir uns die Details ansehen, hier ein Überblick über die UseCases von CameraX und wie eine Camera1-App damit zusammenhängt. (CameraX-Konzepte sind blau und Camera1-Konzepte sind grün.)
CameraX |
|||
| CameraController / CameraProvider-Konfiguration | |||
| ↓ | ↓ | ↓ | ↓ |
| Vorschau | ImageCapture | VideoCapture | ImageAnalysis |
| ⁞ | ⁞ | ⁞ | ⁞ |
| Vorschauoberfläche verwalten und auf Kamera einstellen | „PictureCallback“ festlegen und „takePicture()“ für die Kamera aufrufen | Kamera- und MediaRecorder-Konfiguration in einer bestimmten Reihenfolge verwalten | Benutzerdefinierter Analysecode, der auf der Vorschauoberfläche basiert |
| ↑ | ↑ | ↑ | ↑ |
| Gerätespezifischer Code | |||
| ↑ | |||
| Verwaltung von Geräteausrichtung und ‑skalierung | |||
| ↑ | |||
| Kamerasitzungsverwaltung (Kameraauswahl, Lebenszyklusverwaltung) | |||
Kamera1 |
|||
Kompatibilität und Leistung in CameraX
CameraX unterstützt Geräte mit Android 5.0 (API-Level 21) und höher. Das entspricht über 98% der vorhandenen Android-Geräte. CameraX wurde entwickelt, um Unterschiede zwischen Geräten automatisch zu berücksichtigen. Dadurch wird der Bedarf an gerätespezifischem Code in Ihrer App reduziert. Außerdem testen wir in unserem CameraX Test Lab über 150 physische Geräte mit allen Android-Versionen ab 5.0. Eine vollständige Liste der Geräte im Test Lab finden Sie hier.
CameraX verwendet ein Executor, um den Kamerastack zu steuern. Sie können einen eigenen Executor in CameraX festlegen, wenn Ihre App bestimmte Anforderungen an die Threading-Ausführung hat.
Wenn nicht festgelegt, erstellt und verwendet CameraX eine optimierte interne Executor.
Viele der Plattform-APIs, auf denen CameraX basiert, erfordern blockierende Interprocess Communication (IPC) mit Hardware, deren Antwort manchmal Hunderte von Millisekunden dauern kann. Aus diesem Grund ruft CameraX diese APIs nur über Hintergrundthreads auf. So wird sichergestellt, dass der Hauptthread nicht blockiert wird und die Benutzeroberfläche flüssig bleibt. Weitere Informationen zu Threads
Wenn der Zielmarkt für Ihre App Low-End-Geräte umfasst, bietet CameraX eine Möglichkeit, die Einrichtungszeit mit einem Kamerabegrenzer zu verkürzen. Da das Herstellen einer Verbindung zu Hardwarekomponenten einige Zeit in Anspruch nehmen kann, insbesondere auf Low-End-Geräten, können Sie die Kameras angeben, die Ihre App benötigt. CameraX stellt nur während der Einrichtung eine Verbindung zu diesen Kameras her. Wenn die Anwendung beispielsweise nur nach hinten gerichtete Kameras verwendet, kann sie diese Konfiguration mit DEFAULT_BACK_CAMERA festlegen. CameraX initialisiert dann keine nach vorn gerichteten Kameras, um die Latenz zu verringern.
Android-Entwicklungskonzepte
In dieser Anleitung wird davon ausgegangen, dass Sie mit der Android-Entwicklung vertraut sind. Neben den Grundlagen gibt es noch einige Konzepte, die Sie kennen sollten, bevor Sie sich den folgenden Code ansehen:
- Mit View Binding wird eine Bindungsklasse für Ihre XML-Layoutdateien generiert, mit der Sie in Aktivitäten auf Ihre Ansichten verweisen können, wie in mehreren nachfolgenden Code-Snippets gezeigt. Es gibt einige Unterschiede zwischen der Ansichtsbindung und
findViewById()(der bisherigen Methode zum Referenzieren von Ansichten). Im folgenden Code sollten Sie die Zeilen für die Ansichtsbindung jedoch durch einen ähnlichenfindViewById()-Aufruf ersetzen können. - Asynchrone Coroutinen sind ein Muster für die Nebenläufigkeit, das in Kotlin 1.3 eingeführt wurde und mit dem CameraX-Methoden verarbeitet werden können, die ein
ListenableFuturezurückgeben. Mit der Jetpack-Bibliothek Concurrent ab Version 1.1.0 wird dies erleichtert. So fügen Sie Ihrer App eine asynchrone Coroutine hinzu:- Fügen Sie Ihrer Gradle-Datei
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")hinzu. - Setzen Sie jeden CameraX-Code, der ein
ListenableFuturezurückgibt, in einenlaunch-Block oder eine suspend-Funktion. - Fügen Sie dem Funktionsaufruf, der ein
ListenableFuturezurückgibt, einenawait()-Aufruf hinzu. - Weitere Informationen zur Funktionsweise von Coroutinen finden Sie in der Anleitung Coroutinen starten.
- Fügen Sie Ihrer Gradle-Datei
Häufige Szenarien migrieren
In diesem Abschnitt wird beschrieben, wie Sie häufige Szenarien von Camera1 zu CameraX migrieren.
Jedes Szenario umfasst eine Camera1-Implementierung, eine CameraX-CameraProvider-Implementierung und eine CameraX-CameraController-Implementierung.
Kamera auswählen
In Ihrer Kameraanwendung sollten Sie als Erstes die Möglichkeit bieten, verschiedene Kameras auszuwählen.
Kamera1
In Camera1 können Sie entweder Camera.open() ohne Parameter aufrufen, um die erste rückseitige Kamera zu öffnen, oder eine Ganzzahl-ID für die Kamera übergeben, die Sie öffnen möchten. Hier ein Beispiel:
// 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
In CameraX wird die Kameraauswahl von der Klasse CameraSelector übernommen. CameraX vereinfacht den häufigen Fall der Verwendung der Standardkamera. Sie können angeben, ob Sie die Standard-Frontkamera oder die Standard-Rückkamera verwenden möchten. Außerdem können Sie mit dem CameraControl-Objekt von CameraX die Zoomstufe für Ihre App festlegen. Wenn Ihre App also auf einem Gerät ausgeführt wird, das logische Kameras unterstützt, wird automatisch zum richtigen Objektiv gewechselt.
Hier ist der CameraX-Code für die Verwendung der Standard-Rückkamera mit einem 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
Hier ein Beispiel für die Auswahl der Standard-Frontkamera mit CameraProvider (entweder die Front- oder die Rückkamera kann mit CameraController oder CameraProvider verwendet werden):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the preceding "Android development concepts" // section. 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() } }
Wenn Sie steuern möchten, welche Kamera ausgewählt wird, ist dies auch in CameraX möglich. Verwenden Sie dazu CameraProvider, indem Sie getAvailableCameraInfos() aufrufen. Dadurch erhalten Sie ein CameraInfo-Objekt, mit dem Sie bestimmte Kameraeigenschaften wie isFocusMeteringSupported() prüfen können. Anschließend können Sie es mit der Methode CameraInfo.getCameraSelector() in ein CameraSelector konvertieren, wie in den vorherigen Beispielen gezeigt.
Weitere Informationen zu den einzelnen Kameras erhalten Sie mit der Klasse Camera2CameraInfo. Rufen Sie getCameraCharacteristic() mit einem Schlüssel für die gewünschten Kameradaten auf. Eine Liste aller Schlüssel, nach denen Sie suchen können, finden Sie in der Klasse CameraCharacteristics.
Hier ist ein Beispiel mit einer benutzerdefinierten checkFocalLength()-Funktion, die Sie selbst definieren können:
// 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()
Vorschau anzeigen
Bei den meisten Kameraanwendungen muss der Kamerafeed irgendwann auf dem Bildschirm angezeigt werden. Bei Camera1 müssen Sie die Lifecycle-Callbacks richtig verwalten und auch die Drehung und Skalierung für die Vorschau festlegen.
Außerdem müssen Sie in Camera1 entscheiden, ob Sie eine TextureView oder eine SurfaceView als Vorschauoberfläche verwenden möchten. Beide Optionen haben Nachteile. In beiden Fällen müssen Sie die Drehung und Skalierung korrekt verarbeiten. Die PreviewView von CameraX hingegen hat zugrunde liegende Implementierungen für TextureView und SurfaceView. CameraX entscheidet anhand von Faktoren wie dem Gerätetyp und der Android-Version, auf der Ihre App ausgeführt wird, welche Implementierung am besten geeignet ist. Wenn beide Implementierungen kompatibel sind, können Sie Ihre bevorzugte Implementierung mit PreviewView.ImplementationMode angeben. Bei der Option COMPATIBLE wird für die Vorschau ein TextureView verwendet und beim Wert PERFORMANCE ein SurfaceView (sofern möglich).
Kamera1
Um eine Vorschau anzuzeigen, müssen Sie eine eigene Preview-Klasse mit einer Implementierung der android.view.SurfaceHolder.Callback-Schnittstelle schreiben, die verwendet wird, um Bilddaten von der Kamerahardware an die Anwendung zu übergeben.
Bevor Sie die Live-Bildvorschau starten können, muss die Preview-Klasse an das Camera-Objekt übergeben werden.
// 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
Mit CameraX müssen Sie als Entwickler viel weniger verwalten. Wenn Sie eine CameraController verwenden, müssen Sie auch PreviewView verwenden. Das bedeutet, dass die Preview UseCase impliziert wird, was die Einrichtung erheblich vereinfacht:
// 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
Mit CameraProvider von CameraX müssen Sie PreviewView nicht verwenden. Die Einrichtung der Vorschau ist aber trotzdem viel einfacher als bei Camera1. In diesem Beispiel wird zu Demonstrationszwecken ein PreviewView verwendet. Sie können aber auch ein benutzerdefiniertes SurfaceProvider schreiben, das Sie an setSurfaceProvider() übergeben, wenn Sie komplexere Anforderungen haben.
Hier wird Preview UseCase nicht wie bei CameraController impliziert. Sie müssen es also einrichten:
// 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 preceding "Android development concepts" // section. 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() } }
Zum Fokussieren tippen
Wenn die Kameravorschau auf dem Bildschirm angezeigt wird, kann der Nutzer durch Tippen auf die Vorschau den Fokuspunkt festlegen.
Kamera1
Wenn Sie die Funktion „Tippen zum Fokussieren“ in Camera1 implementieren möchten, müssen Sie den optimalen Fokus Area berechnen, um anzugeben, wo die Camera fokussieren soll. Dieser Area wird an setFocusAreas() übergeben. Außerdem müssen Sie auf dem Camera einen kompatiblen Fokusmodus festlegen. Der Fokusbereich hat nur Auswirkungen, wenn der aktuelle Fokusmodus FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO oder FOCUS_MODE_CONTINUOUS_PICTURE ist.
Jedes Area ist ein Rechteck mit einem bestimmten Gewicht. Das Gewicht ist ein Wert zwischen 1 und 1.000. Er wird verwendet, um die Priorität von Fokus-Areas festzulegen, wenn mehrere festgelegt sind. In diesem Beispiel wird nur ein Area verwendet, daher spielt der Gewichtungswert keine Rolle. Die Koordinaten des Rechtecks liegen zwischen -1000 und 1000. Der Punkt oben links hat die Koordinaten (-1000, -1000).
Der Punkt unten rechts hat die Koordinaten (1000, 1000). Die Richtung bezieht sich auf die Ausrichtung des Sensors, also darauf, was der Sensor sieht. Die Richtung wird durch die Drehung oder Spiegelung von Camera.setDisplayOrientation() nicht beeinflusst. Daher müssen Sie die Koordinaten des Touch-Ereignisses in die Sensorkoordinaten umwandeln.
// 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 wartet auf die Touch-Ereignisse von PreviewView, um die automatische Fokussierung durch Tippen zu ermöglichen. Sie können die Funktion „Tippen zum Fokussieren“ mit setTapToFocusEnabled() aktivieren und deaktivieren und den Wert mit dem entsprechenden Getter isTapToFocusEnabled() abrufen.
Die Methode getTapToFocusState() gibt ein LiveData-Objekt zurück, mit dem Änderungen am Fokusstatus für die CameraController verfolgt werden können.
// 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
Wenn Sie ein CameraProvider verwenden, müssen Sie einige Einstellungen vornehmen, damit die Funktion „Fokus durch Tippen“ funktioniert. In diesem Beispiel wird davon ausgegangen, dass Sie PreviewView verwenden. Falls nicht, müssen Sie die Logik an Ihre benutzerdefinierte Surface anpassen.
So gehen Sie vor, wenn Sie PreviewView verwenden:
- Richten Sie einen Gestenerkennungsdienst ein, um Tippereignisse zu verarbeiten.
- Erstellen Sie mit dem Tippereignis ein
MeteringPointmitMeteringPointFactory.createPoint(). - Erstellen Sie mit dem
MeteringPointeinFocusMeteringAction. - Rufen Sie mit dem
CameraControl-Objekt in IhremCamera(zurückgegeben vonbindToLifecycle())startFocusAndMetering()auf und übergeben SieFocusMeteringAction. - Optional: Antworten Sie auf die
FocusMeteringResult. - Legen Sie fest, dass Ihr Gestenerkennungsmodul auf Berührungsereignisse in
PreviewView.setOnTouchListener()reagiert.
// 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 // preceding "Android development concepts" section. 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-zoom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Zwei-Finger-Zoom
Das Ein- und Auszoomen einer Vorschau ist eine weitere gängige direkte Bearbeitung der Kameravorschau. Da die Anzahl der Kameras auf Geräten immer weiter zunimmt, erwarten Nutzer auch, dass beim Zoomen automatisch das Objektiv mit der besten Brennweite ausgewählt wird.
Kamera1
Es gibt zwei Möglichkeiten, mit Camera1 zu zoomen. Mit der Methode Camera.startSmoothZoom() wird die Animation von der aktuellen Zoomstufe zur übergebenen Zoomstufe ausgeführt. Mit der Methode Camera.Parameters.setZoom() wird direkt zur übergebenen Zoomstufe gesprungen. Bevor Sie eine der beiden Funktionen verwenden, rufen Sie isSmoothZoomSupported() bzw. isZoomSupported() auf, um sicherzustellen, dass die benötigten Zoommethoden auf Ihrer Kamera verfügbar sind.
Für die Implementierung von „Aufziehen zum Zoomen“ wird in diesem Beispiel setZoom() verwendet, da der Touch-Listener auf der Vorschauoberfläche während der Pinch-Geste kontinuierlich Ereignisse auslöst und die Zoomstufe daher jedes Mal sofort aktualisiert wird. Die Klasse ZoomTouchListener wird später in diesem Abschnitt definiert. Sie sollten sie als Callback für den Touch-Listener der Vorschauoberfläche festlegen.
// 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
Ähnlich wie bei „Tippen zum Fokussieren“ werden bei CameraController die Touch-Ereignisse von PreviewView abgehört, um das Pinch-to-Zoom automatisch zu verarbeiten. Mit setPinchToZoomEnabled() können Sie die Zoomfunktion per Pinch-Geste aktivieren und deaktivieren. Den Wert können Sie mit dem entsprechenden Getter isPinchToZoomEnabled() abrufen.
Die Methode getZoomState() gibt ein LiveData-Objekt zurück, mit dem Änderungen an der ZoomState auf dem CameraController verfolgt werden können.
// 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
Damit die Zoomfunktion mit zwei Fingern auf CameraProvider funktioniert, ist eine Einrichtung erforderlich. Wenn Sie PreviewView nicht verwenden, müssen Sie die Logik an Ihre benutzerdefinierte Surface anpassen.
So gehen Sie vor, wenn Sie PreviewView verwenden:
- Richten Sie einen Skalierungsgestenerkennungsdienst ein, um Pinch-Ereignisse zu verarbeiten.
- Rufen Sie
ZoomStateaus demCamera.CameraInfo-Objekt ab. DieCamera-Instanz wird zurückgegeben, wenn SiebindToLifecycle()aufrufen. - Wenn
ZoomStateeinenzoomRatio-Wert hat, speichern Sie diesen als aktuelles Zoomverhältnis. Wenn es aufZoomStatekeinzoomRatiogibt, verwende die Standardzoomrate der Kamera (1,0). - Multiplizieren Sie das aktuelle Zoomverhältnis mit
scaleFactor, um das neue Zoomverhältnis zu ermitteln, und übergeben Sie es anCameraControl.setZoomRatio(). - Legen Sie fest, dass Ihr Gestenerkennungsmodul auf Berührungsereignisse in
PreviewView.setOnTouchListener()reagiert.
// 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-zoom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Foto aufnehmen
In diesem Abschnitt wird beschrieben, wie Sie die Aufnahme von Fotos auslösen – entweder durch Drücken des Auslösers, nach Ablauf eines Timers oder bei einem anderen Ereignis Ihrer Wahl.
Kamera1
In Camera1 definieren Sie zuerst eine Camera.PictureCallback, um die Bilddaten zu verwalten, wenn sie angefordert werden. Hier ein einfaches Beispiel für PictureCallback zur Verarbeitung von JPEG-Bilddaten:
// 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) } }
Wenn Sie dann ein Bild aufnehmen möchten, rufen Sie die Methode takePicture() für Ihre Camera-Instanz auf. Diese takePicture()-Methode hat drei verschiedene Parameter für unterschiedliche Datentypen. Der erste Parameter ist für eine ShutterCallback (die in diesem Beispiel nicht definiert ist). Der zweite Parameter ist für ein PictureCallback, um die Rohdaten (unkomprimiert) der Kamera zu verarbeiten. Der dritte Parameter wird in diesem Beispiel verwendet, da es sich um ein PictureCallback zum Verarbeiten von JPEG-Bilddaten handelt.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
Die CameraController-Funktion von CameraX behält die Einfachheit von Camera1 für die Aufnahme von Bildern bei, indem sie eine eigene takePicture()-Methode implementiert. Definieren Sie hier eine Funktion zum Konfigurieren eines MediaStore-Eintrags und zum Aufnehmen eines Fotos, das dort gespeichert werden soll.
// 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
Das Aufnehmen eines Fotos mit CameraProvider funktioniert fast genauso wie mit CameraController. Sie müssen jedoch zuerst ein ImageCapture-UseCase erstellen und binden, um ein Objekt zu haben, für das Sie takePicture() aufrufen können:
// 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)
Wenn Sie dann ein Foto aufnehmen möchten, können Sie ImageCapture.takePicture() aufrufen. In diesem Abschnitt finden Sie ein vollständiges Beispiel für die Funktion takePhoto() im CameraController-Code.
// 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( ... ) }
Video aufzeichnen
Die Aufnahme eines Videos ist deutlich komplizierter als die bisher betrachteten Szenarien. Jeder Teil des Prozesses muss richtig eingerichtet werden, in der Regel in einer bestimmten Reihenfolge. Außerdem musst du möglicherweise überprüfen, ob Video und Audio synchron sind, oder dich mit zusätzlichen Geräteinkonsistenzen auseinandersetzen.
Wie Sie sehen, übernimmt CameraX wieder einen Großteil dieser Komplexität für Sie.
Kamera1
Für die Videoaufnahme mit Camera1 müssen Camera und MediaRecorder sorgfältig verwaltet werden und die Methoden müssen in einer bestimmten Reihenfolge aufgerufen werden. Sie müssen diese Reihenfolge einhalten, damit Ihre Anwendung ordnungsgemäß funktioniert:
- Öffnen Sie die Kamera.
- Bereite eine Vorschau vor und starte sie (falls deine App das aufgenommene Video anzeigt, was in der Regel der Fall ist).
- Entsperre die Kamera für die Verwendung durch
MediaRecorder, indem duCamera.unlock()anrufst. - Konfigurieren Sie die Aufzeichnung, indem Sie diese Methoden für
MediaRecorderaufrufen:- Verbinden Sie Ihre
Camera-Instanz mitsetCamera(camera). - Rufen Sie
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)an. - Rufen Sie
setVideoSource(MediaRecorder.VideoSource.CAMERA)an. - Rufen Sie
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))auf, um die Qualität festzulegen. Alle Qualitätsoptionen finden Sie unterCamcorderProfile. - Rufen Sie
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())an. - Wenn Ihre App eine Vorschau des Videos hat, rufen Sie
setPreviewDisplay(preview?.holder?.surface)auf. - Rufen Sie
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)an. - Rufen Sie
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)an. - Rufen Sie
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)an. - Rufen Sie
prepare()auf, um die Konfiguration vonMediaRecorderabzuschließen.
- Verbinden Sie Ihre
- Rufen Sie
MediaRecorder.start()an, um die Aufnahme zu starten. - Rufen Sie diese Methoden auf, um die Aufnahme zu beenden. Halten Sie sich dabei genau an die folgende Reihenfolge:
- Rufen Sie
MediaRecorder.stop()an. - Optional können Sie die aktuelle
MediaRecorder-Konfiguration entfernen, indem SieMediaRecorder.reset()aufrufen. - Rufen Sie
MediaRecorder.release()an. - Sperren Sie die Kamera, damit sie in zukünftigen
MediaRecorder-Sitzungen durch Aufrufen vonCamera.lock()verwendet werden kann.
- Rufen Sie
- Wenn Sie die Vorschau beenden möchten, rufen Sie
Camera.stopPreview()an. - Rufen Sie schließlich
Camera.release()auf, um dieCamerafreizugeben, damit sie von anderen Prozessen verwendet werden kann.
Hier sind alle Schritte zusammengefasst:
// 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
Mit CameraController von CameraX können Sie die ImageCapture-, VideoCapture- und ImageAnalysis-UseCases unabhängig voneinander ein- und ausschalten, sofern die Liste der UseCases gleichzeitig verwendet werden kann. Die ImageCapture- und ImageAnalysis-UseCases sind standardmäßig aktiviert. Deshalb mussten Sie zum Aufnehmen eines Fotos nicht setEnabledUseCases() aufrufen.
Wenn Sie ein CameraController für die Videoaufzeichnung verwenden möchten, müssen Sie zuerst setEnabledUseCases() verwenden, um dem VideoCapture UseCase zu erlauben.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Wenn Sie die Videoaufzeichnung starten möchten, können Sie die Funktion CameraController.startRecording() aufrufen. Mit dieser Funktion kann das aufgezeichnete Video in einem File gespeichert werden, wie im folgenden Beispiel zu sehen ist.
Außerdem müssen Sie ein Executor und eine Klasse übergeben, die OnVideoSavedCallback implementiert, um Erfolgs- und Fehler-Callbacks zu verarbeiten. Wenn die Aufnahme beendet werden soll, rufen Sie CameraController.stopRecording() an.
Hinweis:Wenn Sie CameraX 1.3.0-alpha02 oder höher verwenden, gibt es einen zusätzlichen Parameter AudioConfig, mit dem Sie die Audioaufnahme für Ihr Video aktivieren oder deaktivieren können. Damit die Audioaufnahme aktiviert werden kann, müssen Sie die Mikrofonberechtigungen erteilen. Außerdem wurde die Methode stopRecording() in Version 1.3.0-alpha02 entfernt und startRecording() gibt ein Recording-Objekt zurück, mit dem die Videoaufzeichnung pausiert, fortgesetzt und beendet werden kann.
// 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
Wenn Sie ein CameraProvider verwenden, müssen Sie ein VideoCapture
UseCase erstellen und ein Recorder-Objekt übergeben. Auf der Recorder.Builder können Sie die Videoqualität und optional eine FallbackStrategy festlegen, die Fälle abdeckt, in denen ein Gerät Ihre gewünschten Qualitätsspezifikationen nicht erfüllen kann. Binden Sie dann die VideoCapture-Instanz mit Ihren anderen UseCase-Geräten an die CameraProvider.
// 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)
An diesem Punkt kann auf die Recorder über die videoCapture.output-Property zugegriffen werden. Die Recorder kann Videoaufnahmen starten, die auf einer File, ParcelFileDescriptor oder MediaStore gespeichert werden. In diesem Beispiel wird MediaStore verwendet.
Auf der Recorder gibt es mehrere Methoden, die aufgerufen werden können, um sie vorzubereiten. Rufen Sie prepareRecording() auf, um die MediaStore-Ausgabeoptionen festzulegen. Wenn Ihre App die Berechtigung hat, das Mikrofon des Geräts zu verwenden, rufen Sie auch withAudioEnabled() auf.
Rufen Sie dann start() auf, um die Aufzeichnung zu starten. Übergeben Sie dazu einen Kontext und einen Consumer<VideoRecordEvent>-Ereignis-Listener, um Ereignisse der Videoaufzeichnung zu verarbeiten. Bei Erfolg kann die zurückgegebene Recording verwendet werden, um die Aufzeichnung zu pausieren, fortzusetzen oder zu beenden.
// 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 } } } } }
Zusätzliche Ressourcen
Wir haben mehrere vollständige CameraX-Apps in unserem GitHub-Repository für Kamerabeispiele. In diesen Beispielen sehen Sie, wie die Szenarien in diesem Leitfaden in eine vollwertige Android-App passen.
Wenn Sie zusätzliche Unterstützung bei der Migration zu CameraX benötigen oder Fragen zu den Android Camera-APIs haben, wenden Sie sich an uns in der CameraX-Diskussionsgruppe.