Camera1 zu CameraX migrieren

Wenn Ihre App die ursprüngliche Camera-Klasse („Camera1“) verwendet, die seit Android 5.0 (API-Level 21) eingestellt wurde, sollten Sie dringend auf eine moderne Android-Kamera-API umstellen. Android bietet CameraX (eine standardisierte, robuste Jetpack-Kamera-API) und Camera2 (eine Low-Level-Framework-API). In den meisten Fällen empfehlen wir, Ihre App zu CameraX zu migrieren. Aus folgenden Gründen:

  • Einfache Bedienung:CameraX übernimmt die Details auf niedriger Ebene, sodass Sie sich weniger auf die Erstellung einer Kamerafunktion von Grund auf konzentrieren und mehr auf die Differenzierung Ihrer App konzentrieren können.
  • CameraX übernimmt die Fragmentierung für Sie:CameraX reduziert langfristige Wartungskosten und gerätespezifischen Code und bietet Nutzern eine bessere Qualität. Weitere Informationen finden Sie in unserem Blogpost Bessere Gerätekompatibilität mit CameraX.
  • Erweiterte Funktionen:CameraX wurde sorgfältig entwickelt, damit sich erweiterte Funktionen ganz einfach in Ihre App einbinden lassen. Mit den CameraX-Erweiterungen können Sie beispielsweise ganz einfach Bokeh, Gesichtsretusche, HDR (High Dynamic Range) und den Nachtaufnahmemodus mit Aufhellung bei wenig Licht auf Ihre Fotos anwenden.
  • Aktualisierungsmöglichkeiten:Android veröffentlicht das ganze Jahr über neue Funktionen und Fehlerkorrekturen für CameraX. Wenn Sie zu CameraX migrieren, erhält Ihre App nicht nur bei den jährlichen Android-Versionen, sondern bei jedem CameraX-Release die neueste Android-Kameratechnologie.

In diesem Leitfaden finden Sie häufige Szenarien für Kameraanwendungen. Jedes Szenario enthält eine Camera1- und eine CameraX-Implementierung zum Vergleich.

Bei der Migration ist manchmal zusätzliche Flexibilität erforderlich, um die Integration in eine vorhandene Codebasis zu ermöglichen. Der gesamte CameraX-Code in diesem Leitfaden hat eine CameraController-Implementierung, die sich am besten eignet, wenn Sie CameraX auf einfache Weise verwenden möchten. Außerdem gibt es eine CameraProvider-Implementierung, die sich am besten eignet, wenn Sie mehr Flexibilität benötigen. Hier sind die Vorteile der einzelnen Optionen, damit Sie leichter entscheiden können, welche für Sie am besten geeignet ist:

CameraController

CameraProvider

Erfordert nur wenig Einrichtungscode Sie haben mehr Kontrolle
Wenn Sie CameraX mehr der Einrichtung zuweisen, funktionieren Funktionen wie „Zum Fokussieren antippen“ und „Zum Zoomen zusammenziehen“ automatisch. Da die Einrichtung vom App-Entwickler durchgeführt wird, gibt es mehr Möglichkeiten, die Konfiguration anzupassen, z. B. die Ausgabebilddrehung 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 der ML-Modellergebnisse (z. B. Gesichtsbounding-Boxen) direkt auf die Vorschaukoordinaten abgebildet werden können. Die Möglichkeit, eine benutzerdefinierte „Surface“ für die Kameravorschau zu verwenden, bietet mehr Flexibilität. So können Sie beispielsweise Ihren vorhandenen „Surface“-Code verwenden, der als Eingabe für andere Teile Ihrer App dienen kann.

Wenn Sie bei der Migration auf Probleme stoßen, wenden Sie sich an uns in der CameraX-Diskussionsgruppe.

Vor der Migration

CameraX-Nutzung mit Camera1 vergleichen

Der Code sieht zwar möglicherweise anders aus, die zugrunde liegenden Konzepte in Camera1 und CameraX sind jedoch sehr ähnlich. CameraX fasst gängige Kamerafunktionen in Anwendungsfällen zusammen. Daher werden viele Aufgaben, die in Camera1 dem Entwickler überlassen wurden, von CameraX automatisch ausgeführt. In CameraX gibt es vier UseCase, die Sie für verschiedene Kameraaufgaben verwenden können: Preview, ImageCapture, VideoCapture und ImageAnalysis.

Ein Beispiel dafür, wie CameraX Details auf niedriger Ebene für Entwickler verarbeitet, ist die ViewPort, die für aktive UseCases freigegeben wird. So sehen alle UseCases genau dieselben Pixel. In Camera1 müssen Sie diese Details selbst verwalten. Aufgrund der unterschiedlichen Seitenverhältnisse der Kamerasensoren und Bildschirme der Geräte kann es schwierig sein, dafür zu sorgen, dass die Vorschau den aufgenommenen Fotos und Videos entspricht.

Ein weiteres Beispiel: CameraX verarbeitet Lifecycle-Callbacks automatisch für die übergebene Lifecycle-Instanz. Das bedeutet, dass CameraX die Verbindung Ihrer App zur Kamera während des gesamten Android-Aktivitätszyklus verwaltet. Dazu gehören auch die folgenden Fälle: Die Kamera wird geschlossen, wenn Ihre App in den Hintergrund wechselt, die Kameravorschau wird entfernt, wenn sie nicht mehr auf dem Display angezeigt werden muss, und die Kameravorschau wird pausiert, wenn eine andere Aktivität im Vordergrund Vorrang hat, z. B. ein eingehender Videoanruf.

Schließlich übernimmt CameraX die Drehung und Skalierung, ohne dass Sie zusätzlichen Code schreiben müssen. Bei einem Activity mit entsperrter Ausrichtung wird die UseCase-Einrichtung jedes Mal durchgeführt, wenn das Gerät gedreht wird, da das System den UseCase bei Änderungen der Ausrichtung zerstört und neu erstellt.Activity Dadurch wird die Zieldrehung der UseCases jedes Mal standardmäßig an die Ausrichtung des Displays angepasst. Weitere Informationen zu Drehungen in CameraX

Bevor wir uns mit den Details befassen, sehen wir uns die UseCases von CameraX und die Beziehung zu einer Camera1-App an. CameraX-Konzepte sind blau und Camera1-Konzepte sind grün.

CameraX

Konfiguration von CameraController / CameraProvider
Vorschau ImageCapture VideoCapture ImageAnalysis
Vorschaufläche verwalten und für die Kamera festlegen PictureCallback festlegen und takePicture() auf Kamera aufrufen Kamera- und MediaRecorder-Konfiguration in einer bestimmten Reihenfolge verwalten Benutzerdefinierter Analysecode, der auf der Vorschauoberfläche basiert
Gerätespezifischer Code
Geräterotation und Skalierungsverwaltung
Kamerasitzungsverwaltung (Kameraauswahl, Lebenszyklusverwaltung)

Kamera1

Kompatibilität und Leistung in CameraX

CameraX wird auf Geräten mit Android 5.0 (API-Level 21) und höher unterstützt. Das entspricht über 98% aller Android-Geräte. CameraX ist so konzipiert, dass Unterschiede zwischen Geräten automatisch verarbeitet werden, sodass weniger gerätespezifischer Code in Ihrer App erforderlich ist. 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, die sich derzeit im Testlab befinden, finden Sie hier.

CameraX verwendet einen Executor, um den Kamerastack zu steuern. Wenn Ihre App bestimmte Anforderungen an die Threads hat, können Sie einen eigenen Executor für CameraX festlegen. Wenn nicht festgelegt, erstellt und verwendet CameraX eine optimierte interne Standard-Executor. Viele der Plattform-APIs, auf denen CameraX basiert, erfordern die Blockierung der Inter-Process-Kommunikation (IPC) mit Hardware, die manchmal Hunderte von Millisekunden für eine Antwort benötigt. 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 Ihrer App auch Low-End-Geräte umfasst, können Sie mit CameraX die Einrichtungszeit mit einem Kamerabegrenzer verkürzen. Da die Verbindung zu Hardwarekomponenten insbesondere auf Low-End-Geräten viel Zeit in Anspruch nehmen kann, können Sie angeben, welche Kameras für Ihre App benötigt werden. CameraX verbindet sich nur während der Einrichtung mit diesen Kameras. Wenn die Anwendung beispielsweise nur Rückkameras verwendet, kann diese Konfiguration mit DEFAULT_BACK_CAMERA festgelegt werden. CameraX initialisiert dann keine Frontkameras, um die Latenz zu verringern.

Konzepte der Android-Entwicklung

In diesem Leitfaden wird davon ausgegangen, dass Sie mit der Android-Entwicklung vertraut sind. Neben den Grundlagen sind hier noch einige Konzepte, die Sie kennen sollten, bevor Sie sich den Code unten ansehen:

  • Die Bindung generiert eine Bindungsklasse für Ihre XML-Layoutdateien, sodass Sie ganz einfach in Aktivitäten auf Ihre Ansichten verweisen können, wie in den folgenden Code-Snippets gezeigt. Es gibt einige Unterschiede zwischen der View-Bindung und findViewById() (der bisherigen Methode zum Verweisen auf Ansichten). Im folgenden Code sollten Sie jedoch in der Lage sein, die Zeilen der View-Bindung durch einen ähnlichen findViewById()-Aufruf zu ersetzen.
  • Asynchrone Coroutinen sind ein Designmuster für die Parallelverarbeitung, das in Kotlin 1.3 hinzugefügt wurde. Es kann zum Bearbeiten von CameraX-Methoden verwendet werden, die eine ListenableFuture zurückgeben. Das wird mit der Jetpack-Bibliothek Concurrent ab Version 1.1.0 vereinfacht. So fügen Sie Ihrer App eine asynchrone Coroutine hinzu:
    1. Fügen Sie implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") Ihrer Gradle-Datei hinzu.
    2. Platzieren Sie jeglichen CameraX-Code, der eine ListenableFuture zurückgibt, in einem launch-Block oder einer aussetzenden Funktion.
    3. Fügen Sie dem Funktionsaufruf einen await()-Aufruf hinzu, der eine ListenableFuture zurückgibt.
    4. Weitere Informationen zur Funktionsweise von coroutines finden Sie im Leitfaden Eine Coroutine starten.

Gängige 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 Kamera-App sollten Sie in erster Linie die Möglichkeit bieten, verschiedene Kameras auszuwählen.

Kamera1

In Camera1 können Sie entweder Camera.open() ohne Parameter aufrufen, um die erste Rückkamera 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 verwaltet. CameraX erleichtert die häufige Verwendung der Standardkamera. Sie können angeben, ob die Standard-Frontkamera oder die Standard-Rückkamera verwendet werden soll. Außerdem können Sie mit dem CameraControl-Objekt von CameraX ganz einfach die Zoomstufe für Ihre App festlegen. Wenn Ihre App auf einem Gerät ausgeführt wird, das logische Kameras unterstützt, wird das richtige Objektiv verwendet.

Hier ist der CameraX-Code für die Verwendung der Standardrückkamera mit einer 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. Mit CameraController oder CameraProvider kann entweder die Front- oder die Rückkamera 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 "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()
    }
}

Wenn Sie festlegen möchten, welche Kamera ausgewählt wird, ist dies auch in CameraX möglich. Verwenden Sie dazu CameraProvider und rufen Sie getAvailableCameraInfos() auf. Sie erhalten dann ein CameraInfo-Objekt, mit dem Sie bestimmte Kameraeigenschaften wie isFocusMeteringSupported() prüfen können. Sie können ihn dann in einen CameraSelector konvertieren, um ihn wie in den obigen Beispielen mit der CameraInfo.getCameraSelector()-Methode zu verwenden.

Weitere Informationen zu den einzelnen Kameras finden Sie in der Klasse Camera2CameraInfo. Rufen Sie getCameraCharacteristic() mit einem Schlüssel für die gewünschten Kameradaten auf. In der Klasse CameraCharacteristics finden Sie eine Liste aller Schlüssel, nach denen Sie eine Abfrage stellen können.

Hier ein Beispiel für eine benutzerdefinierte 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 Lebenszyklus-Callbacks richtig verwalten und auch die Drehung und Skalierung für die Vorschau festlegen.

Außerdem müssen Sie in Camera1 festlegen, ob Sie eine TextureView oder eine SurfaceView als Vorschaufläche verwenden möchten. Beide Optionen haben Vor- und Nachteile. In beiden Fällen müssen Sie mit Camera1 die Drehung und Skalierung richtig handhaben. PreviewView von CameraX hat dagegen zugrunde liegende Implementierungen sowohl für TextureView als auch für SurfaceView. CameraX entscheidet, welche Implementierung am besten geeignet ist, je nach Faktoren wie dem Gerätetyp und der Android-Version, unter der Ihre App ausgeführt wird. Wenn eine der beiden Implementierungen kompatibel ist, kannst du deine Präferenz mit PreviewView.ImplementationMode angeben. Bei der Option COMPATIBLE wird in der Vorschau ein TextureView verwendet und beim Wert PERFORMANCE ein SurfaceView (sofern möglich).

Kamera1

Wenn Sie eine Vorschau anzeigen möchten, müssen Sie eine eigene Preview-Klasse mit einer Implementierung der Schnittstelle android.view.SurfaceHolder.Callback schreiben, über die Bilddaten von der Kamerahardware an die Anwendung übergeben werden. 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 CameraController verwenden, müssen Sie auch PreviewView verwenden. Das bedeutet, dass die Preview UseCase impliziert ist, 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, aber die Einrichtung der Vorschau wird im Vergleich zu Camera1 erheblich vereinfacht. In diesem Beispiel wird zu Demonstrationszwecken eine PreviewView verwendet. Sie können aber auch eine benutzerdefinierte SurfaceProvider schreiben, die an setSurfaceProvider() übergeben wird, wenn Sie komplexere Anforderungen haben.

Hier ist 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 "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()
    }
}

Zum Fokussieren tippen

Wenn die Kameravorschau auf dem Bildschirm angezeigt wird, wird der Fokus häufig festgelegt, wenn der Nutzer auf die Vorschau tippt.

Kamera1

Wenn Sie die Funktion „Auf Objekt tippen, um den Fokus einzustellen“ in Camera1 implementieren möchten, müssen Sie den optimalen Fokus Area berechnen, um anzugeben, wo die Camera versuchen soll, den Fokus einzustellen. Dieser Area wird an setFocusAreas() übergeben. Außerdem müssen Sie auf der 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.

Jede Area ist ein Rechteck mit einem bestimmten Gewicht. Das Gewicht ist ein Wert zwischen 1 und 1.000 und wird verwendet, um den Fokus Areas zu priorisieren, wenn mehrere festgelegt sind. In diesem Beispiel wird nur eine Area verwendet, sodass der Gewichtswert keine Rolle spielt. Die Koordinaten des Rechtecks liegen zwischen -1.000 und 1.000. Der Punkt links oben hat die Koordinaten (-1.000, -1.000). Der Punkt rechts unten hat die Koordinaten (1.000, 1.000). Die Richtung bezieht sich auf die Ausrichtung des Sensors, also auf das, was der Sensor sieht. Die Richtung wird nicht durch die Drehung oder Spiegelung von Camera.setDisplayOrientation() beeinflusst. Sie müssen also die Touch-Ereignis-Koordinaten 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 überwacht die Touch-Ereignisse von PreviewView, um das automatische Fokussieren durch Tippen zu verarbeiten. Sie können die Funktion „Auf-Pixel-Tippen zum Fokussieren“ mit setTapToFocusEnabled() aktivieren und deaktivieren und den Wert mit dem entsprechenden Getter isTapToFocusEnabled() prüfen.

Die Methode getTapToFocusState() gibt ein LiveData-Objekt zurück, um Änderungen am Fokusstatus des CameraController zu verfolgen.

// 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 „Auf Objekt tippen, um den Fokus darauf zu legen“ funktioniert. In diesem Beispiel wird davon ausgegangen, dass Sie PreviewView verwenden. Andernfalls müssen Sie die Logik so anpassen, dass sie auf Ihre benutzerdefinierte Surface angewendet werden kann.

So funktioniert es mit PreviewView:

  1. Richten Sie einen Touch-Gesten-Erkennungsdienst ein, um Tipp-Ereignisse zu verarbeiten.
  2. Erstellen Sie mit dem Tippen-Ereignis eine MeteringPoint mit MeteringPointFactory.createPoint().
  3. Erstellen Sie mit der MeteringPoint eine FocusMeteringAction.
  4. Rufe mit dem CameraControl-Objekt in deinem Camera (von bindToLifecycle() zurückgegeben) startFocusAndMetering() auf und übergebe FocusMeteringAction.
  5. Optional: Beantworten Sie die FocusMeteringResult.
  6. Legen Sie fest, dass der Gestensensor in PreviewView.setOnTouchListener() auf Touch-Ereignisse reagieren soll.
// 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
}

Mit zwei Fingern auseinander- und zusammenziehen

Das Heranzoomen und Herauszoomen einer Vorschau ist eine weitere gängige direkte Manipulation der Kameravorschau. Mit der zunehmenden Anzahl von Kameras auf Geräten erwarten Nutzer auch, dass beim Zoomen automatisch das Objektiv mit der besten Brennweite ausgewählt wird.

Kamera1

Es gibt zwei Möglichkeiten, mit Camera1 heranzuzoomen. Mit der Methode Camera.startSmoothZoom() wird von der aktuellen Zoomstufe zur von Ihnen übergebenen Zoomstufe animiert. Mit der Methode Camera.Parameters.setZoom() wird direkt zur von Ihnen übergebenen Zoomstufe gesprungen. Bevor Sie eine der beiden Methoden verwenden, drücken Sie isSmoothZoomSupported() bzw. isZoomSupported(), um zu prüfen, ob die entsprechenden Zoommethoden auf Ihrer Kamera verfügbar sind.

In diesem Beispiel wird setZoom() verwendet, um das Zoomen per Zusammenziehen und Spreizen der Finger zu implementieren, da der Touch-Listener auf der Vorschauoberfläche bei der Touch-Geste kontinuierlich Ereignisse auslöst und so die Zoomstufe jedes Mal sofort aktualisiert. Die Klasse ZoomTouchListener wird unten definiert und sollte als Rückruf für den Touch-Listener der Vorschauoberfläche festgelegt werden.

// 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 beim Fokussieren durch Tippen überwacht CameraController die Touch-Ereignisse der Vorschauansicht, um das Zoomen per Zusammenziehen und Spreizen automatisch zu verarbeiten. Sie können das Zoomen per Finger-Zoomen mit setPinchToZoomEnabled() aktivieren und deaktivieren und den Wert mit dem entsprechenden Getter isPinchToZoomEnabled() prüfen.

Die Methode getZoomState() gibt ein LiveData-Objekt zurück, um Änderungen an der ZoomState auf der CameraController zu verfolgen.

// 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 das Zoomen per Zusammenziehen und Spreizen der Finger auf CameraProvider funktioniert, sind einige Einstellungen erforderlich. Wenn Sie PreviewView nicht verwenden, müssen Sie die Logik an Ihre benutzerdefinierte Surface anpassen.

So funktioniert es mit PreviewView:

  1. Richten Sie einen Gesten-Detektor für die Skalierung ein, um Pinch-Ereignisse zu verarbeiten.
  2. Rufe ZoomState vom Camera.CameraInfo-Objekt ab. Die Camera-Instanz wird zurückgegeben, wenn du bindToLifecycle() aufrufst.
  3. Wenn ZoomState den Wert zoomRatio hat, speichern Sie diesen als aktuelles Zoomverhältnis. Wenn unter ZoomState kein zoomRatio angezeigt wird, verwenden Sie die Standardzoomrate der Kamera (1,0).
  4. Multiplizieren Sie das aktuelle Zoomverhältnis mit scaleFactor, um das neue Zoomverhältnis zu ermitteln, und geben Sie es in CameraControl.setZoomRatio() ein.
  5. Legen Sie fest, dass der Gestensensor in PreviewView.setOnTouchListener() auf Touch-Ereignisse reagieren soll.
// 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
}

Fotos aufnehmen

In diesem Abschnitt wird beschrieben, wie Sie die Aufnahme von Fotos auslösen, z. B. durch Drücken der Auslösertaste, nach Ablauf eines Timers oder bei einem anderen Ereignis Ihrer Wahl.

Kamera1

In Camera1 definieren Sie zuerst einen 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 Foto aufnehmen möchten, rufen Sie die Methode takePicture() auf Ihrer Camera-Instanz auf. Diese takePicture()-Methode hat drei verschiedene Parameter für unterschiedliche Datentypen. Der erste Parameter bezieht sich auf eine ShutterCallback, die in diesem Beispiel nicht definiert ist. Der zweite Parameter ist für eine PictureCallback, die die Rohdaten (unkomprimierte Kameradaten) verarbeitet. Der dritte Parameter ist derjenige, der in diesem Beispiel verwendet wird, da es sich um eine PictureCallback zur Verarbeitung von JPEG-Bilddaten handelt.

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

camera?.takePicture(null, null, picture)

CameraX: CameraController

Die CameraController von CameraX behält die Einfachheit von Camera1 für die Bildaufnahme bei, indem eine eigene takePicture()-Methode implementiert wird. Hier definieren Sie eine Funktion, um einen MediaStore-Eintrag zu konfigurieren und ein Foto aufzunehmen, 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 von Fotos mit CameraProvider funktioniert fast genau wie mit CameraController. Sie müssen jedoch zuerst ein ImageCapture-UseCase erstellen und binden, um ein Objekt zu haben, für das takePicture() aufgerufen werden kann:

// 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. Im Code für CameraController in diesem Abschnitt finden Sie ein vollständiges Beispiel für die Funktion takePhoto().

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

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

    ...

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

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 prüfen, ob Video und Audio synchron sind, oder zusätzliche Geräteinkonsistenzen beheben.

Wie Sie sehen, übernimmt CameraX wieder einen Großteil dieser Komplexität für Sie.

Kamera1

Die Videoaufnahme mit Camera1 erfordert eine sorgfältige Verwaltung von Camera und MediaRecorder. Außerdem müssen die Methoden in einer bestimmten Reihenfolge aufgerufen werden. Sie müssen diese Reihenfolge einhalten, damit Ihre Anwendung ordnungsgemäß funktioniert:

  1. Öffnen Sie die Kamera.
  2. Bereiten Sie eine Vorschau vor und starten Sie sie (sofern Ihre App das aufgezeichnete Video anzeigt, was in der Regel der Fall ist).
  3. Entsperren Sie die Kamera für die Nutzung durch MediaRecorder, indem Sie Camera.unlock() anrufen.
  4. Konfigurieren Sie die Aufzeichnung, indem Sie diese Methoden auf MediaRecorder aufrufen:
    1. Verbinden Sie Ihre Camera-Instanz mit setCamera(camera).
    2. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) anrufen.
    3. setVideoSource(MediaRecorder.VideoSource.CAMERA) anrufen.
    4. Rufen Sie setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) auf, um die Qualität festzulegen. Weitere Informationen zu allen Qualitätsoptionen finden Sie unter CamcorderProfile.
    5. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) anrufen.
    6. Wenn Ihre App eine Videovorschau hat, wählen Sie setPreviewDisplay(preview?.holder?.surface).
    7. setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) anrufen.
    8. setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) anrufen.
    9. setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) anrufen.
    10. Rufen Sie prepare() auf, um die Konfiguration Ihrer MediaRecorder abzuschließen.
  5. Um die Aufnahme zu starten, rufen Sie MediaRecorder.start() auf.
  6. Rufen Sie diese Methoden auf, um die Aufzeichnung zu beenden. Gehen Sie dabei genau so vor:
    1. MediaRecorder.stop() anrufen.
    2. Optional können Sie die aktuelle MediaRecorder-Konfiguration entfernen, indem Sie MediaRecorder.reset() aufrufen.
    3. MediaRecorder.release() anrufen.
    4. Sperren Sie die Kamera, damit sie in zukünftigen MediaRecorder-Sitzungen verwendet werden kann, indem Sie Camera.lock() aufrufen.
  7. Drücken Sie Camera.stopPreview(), um die Vorschau zu beenden.
  8. Rufen Sie abschließend Camera.release() auf, um Camera freizugeben, damit andere Prozesse es verwenden können.

Hier sind alle diese Schritte kombiniert:

// 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 der CameraController von CameraX können Sie die ImageCapture-, VideoCapture- und ImageAnalysis-UseCases unabhängig voneinander aktivieren und deaktivieren, sofern die Liste der Anwendungsfälle gleichzeitig verwendet werden kann. Die ImageCapture und ImageAnalysis UseCase sind standardmäßig aktiviert. Deshalb mussten Sie setEnabledUseCases() nicht aufrufen, um ein Foto aufzunehmen.

Wenn Sie eine CameraController für die Videoaufzeichnung verwenden möchten, müssen Sie zuerst mit setEnabledUseCases() die VideoCapture UseCase zulassen.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

Wenn Sie mit der Videoaufzeichnung beginnen möchten, können Sie die Funktion CameraController.startRecording() aufrufen. Mit dieser Funktion kann das aufgenommene Video auf einer File gespeichert werden, wie im Beispiel unten gezeigt. Außerdem müssen Sie einen Executor und eine Klasse übergeben, die OnVideoSavedCallback implementiert, um Erfolg- und Fehler-Callbacks zu verarbeiten. Wenn die Aufzeichnung enden soll, rufen Sie CameraController.stopRecording() auf.

Hinweis:Wenn Sie CameraX 1.3.0-alpha02 oder höher verwenden, gibt es einen zusätzlichen Parameter AudioConfig, mit dem Sie die Audioaufzeichnung für Ihr Video aktivieren oder deaktivieren können. Wenn Sie die Audioaufzeichnung aktivieren möchten, benötigen Sie die Mikrofonberechtigung. Außerdem wurde die Methode stopRecording() in 1.3.0-alpha02 entfernt. 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 CameraProvider verwenden, müssen Sie ein VideoCapture UseCase erstellen und ein Recorder-Objekt übergeben. Auf der Seite Recorder.Builder kannst du die Videoqualität und optional eine FallbackStrategy festlegen. Letztere wird verwendet, wenn ein Gerät die gewünschten Qualitätsspezifikationen nicht erfüllen kann. Binden Sie dann die VideoCapture-Instanz mit Ihren anderen UseCases 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: VideoCapture
private var recording: Recording? = null

...

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

...

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

Jetzt kann über die Recorder auf die videoCapture.output-Property zugegriffen werden. Mit der Recorder können Videoaufzeichnungen gestartet werden, die auf einem File, ParcelFileDescriptor oder MediaStore gespeichert werden. In diesem Beispiel wird MediaStore verwendet.

Für die Vorbereitung der Recorder gibt es mehrere Methoden. Rufen Sie prepareRecording() auf, um die Ausgabeoptionen für MediaStore 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, und übergeben Sie einen Kontext und einen Consumer<VideoRecordEvent>-Ereignis-Listener, um Videoaufzeichnungsereignisse 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
                   }
               }
           }
       }
}

Weitere Informationen

In unserem GitHub-Repository für Kamerabeispiele finden Sie mehrere vollständige CameraX-Apps. Diese Beispiele zeigen, wie die Szenarien in diesem Leitfaden in eine vollwertige Android-App passen.

Wenn Sie weitere Unterstützung bei der Migration zu CameraX benötigen oder Fragen zu den Android Camera APIs haben, wenden Sie sich bitte an uns in der CameraX-Diskussionsgruppe.