Camera1 zu CameraX migrieren

Wenn in deiner App die ursprüngliche Camera-Klasse („Camera1“) verwendet wird, die seit Android 5.0 (API-Level 21) eingestellt wird, empfehlen wir dringend, auf eine moderne Android-Kamera-API zu aktualisieren. Android bietet CameraX (eine standardisierte, robuste Jetpack-Kamera-API) und Camera2 (eine Low-Level-Framework-API) an. In den meisten Fällen empfehlen wir, Ihre App zu CameraX zu migrieren. Ihre Vorteile:

  • Nutzerfreundlichkeit:CameraX kümmert sich um die Low-Level-Details, sodass Sie sich weniger darauf konzentrieren müssen, eine Kamera von Grund auf neu zu entwickeln, sondern sich mehr auf die Unterscheidung Ihrer App konzentrieren müssen.
  • CameraX kümmert sich um die Fragmentierung:CameraX reduziert langfristige Wartungskosten und gerätespezifischen Code und sorgt so für eine bessere Qualität. Weitere Informationen dazu findest du in unserem Blogpost Better Device Compatibility with CameraX.
  • Erweiterte Funktionen:CameraX wurde sorgfältig entwickelt, damit sich erweiterte Funktionen 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 „Nachtaufnahme“ bei wenig Licht auf Ihre Fotos anwenden.
  • Aktualisierungsbarkeit:Android veröffentlicht im Laufe des Jahres neue Funktionen und Fehlerkorrekturen für CameraX. Durch die Migration zu CameraX erhält Ihre App mit jedem CameraX-Release, nicht nur mit den jährlichen Android-Versionen, die neueste Android-Kameratechnologie.

In diesem Leitfaden finden Sie gängige Szenarien für Kameraanwendungen. Jedes Szenario umfasst eine Camera1- und eine CameraX-Implementierung zum Vergleich.

Bei der Migration benötigen Sie manchmal zusätzliche Flexibilität für die Einbindung in eine vorhandene Codebasis. Für den gesamten CameraX-Code in diesem Leitfaden gibt es eine CameraController-Implementierung – ideal, wenn Sie die einfache Verwendung von CameraX wünschen, und auch eine CameraProvider-Implementierung – ideal, wenn Sie mehr Flexibilität benötigen. Damit Sie die für Sie richtige Wahl treffen können, haben wir jeweils die folgenden Vorteile zusammengefasst:

Kamerasteuerung

Kameraanbieter

Wenig Einrichtungscode erforderlich Mehr Kontrolle
Wenn CameraX einen größeren Teil des Einrichtungsvorgangs übernimmt, funktionieren Funktionen wie das Fokussieren durch Tippen und das Zoomen durch Auseinander- und Zusammenziehen der Finger automatisch Da der App-Entwickler die Einrichtung übernimmt, gibt es mehr Möglichkeiten, die Konfiguration anzupassen, z. B. durch Aktivieren der Drehung des Ausgabebildes oder Festlegen des Ausgabebildformats in ImageAnalysis
Durch die Angabe von PreviewView für die Kameravorschau kann CameraX eine nahtlose End-to-End-Integration bieten, wie z. B. bei der ML Kit-Integration, bei der die Ergebniskoordinaten des ML-Modells (wie Begrenzungsrahmen für Gesichter) direkt den Vorschaukoordinaten zugeordnet werden können. Die Möglichkeit, eine benutzerdefinierte Oberfläche für die Kameravorschau zu verwenden, bietet mehr Flexibilität, z. B. die Verwendung Ihres vorhandenen Surface-Codes, der als Eingabe für andere Bereiche Ihrer App verwendet werden könnte

Wenn Sie bei der Migration nicht weiterkommen, können Sie sich über die CameraX-Diskussionsgruppe an uns wenden.

Vor der Migration

Vergleich von CameraX mit Camera1-Nutzung

Auch wenn der Code anders aussieht, sind die Konzepte in Kamera1 und KameraX sehr ähnlich. CameraX abstrahiert gängige Kamerafunktionen mit Anwendungsfällen. Daher werden viele Aufgaben, die dem Entwickler in Camera1 überlassen wurden, automatisch von CameraX ausgeführt. CameraX enthält vier UseCases, die Sie für verschiedene Kameraaufgaben verwenden können: Preview, ImageCapture, VideoCapture und ImageAnalysis.

Ein Beispiel dafür, dass CameraX Low-Level-Details für Entwickler verarbeitet, ist das ViewPort, das von aktiven UseCases gemeinsam genutzt wird. Dadurch sehen alle UseCases genau die gleichen Pixel. In Camera1 musst du diese Details selbst verwalten. Da die Seitenverhältnisse je nach Kamerasensor und -bildschirm der Geräte variieren, kann es schwierig sein, sicherzustellen, dass deine Vorschau mit aufgenommenen Fotos und Videos übereinstimmt.

Ein weiteres Beispiel: CameraX verarbeitet Lifecycle-Callbacks automatisch für die Lifecycle-Instanz, die Sie übergeben. Das bedeutet, dass CameraX die Verbindung Ihrer App mit der Kamera während des gesamten Android-Aktivitätslebenszyklus verarbeitet, einschließlich der folgenden Fälle: Die Kamera wird geschlossen, wenn die App in den Hintergrund versetzt wird, die Kameravorschau wird entfernt, wenn sie nicht mehr auf dem Bildschirm zu sehen sein muss, und die Kameravorschau wird pausiert, wenn eine andere Aktivität Vorrang hat, z. B. ein eingehender Videoanruf.

KameraX übernimmt die Drehung und Skalierung, ohne dass zusätzlicher Code von Ihrer Seite erforderlich ist. Bei einem Activity mit entsperrter Ausrichtung erfolgt die UseCase-Einrichtung jedes Mal, wenn das Gerät gedreht wird, da das System bei einer Änderung der Ausrichtung die Activity löscht und neu erstellt. Dies führt dazu, dass UseCases die Zieldrehung so einstellen, dass sie jedes Mal standardmäßig der Bildschirmausrichtung entspricht. Weitere Informationen zu Drehungen in KameraX

Bevor wir ins Detail gehen, erhalten Sie hier einen allgemeinen Überblick über die UseCases von CameraX und darüber, wie eine Camera1-App zueinander in Beziehung steht. (CameraX-Konzepte sind blau und Kamera1-Konzepte grün.)

KameraX

CameraController-/CameraProvider-Konfiguration
Vorschau Bilderfassung Videoaufzeichnung Bildanalyse
Vorschaufläche verwalten und auf Kamera festlegen PictureCallback festlegen und takePicture() für die Kamera aufrufen Kamera- und MediaRekorder-Konfiguration in einer bestimmten Reihenfolge verwalten Benutzerdefinierter Analysecode, der auf der Vorschauoberfläche basiert
Gerätespezifischer Code
Geräterotation und Skalierungsverwaltung
Verwaltung der Kamerasitzung (Kameraauswahl, Verwaltung des Lebenszyklus)

Kamera1

Kompatibilität und Leistung in CameraX

CameraX unterstützt Geräte ab Android 5.0 (API-Level 21). Das sind über 98% der bestehenden Android-Geräte. CameraX ist darauf ausgelegt, Unterschiede zwischen Geräten automatisch zu verarbeiten, sodass weniger gerätespezifischer Code in Ihrer App erforderlich ist. Außerdem testen wir in unserem CameraX-Test Lab über 150 physische Geräte auf allen Android-Versionen seit Version 5.0. Hier finden Sie die vollständige Liste der Geräte, die sich derzeit im Test Lab befinden.

CameraX verwendet ein Executor, um den Kamerastack zu konfigurieren. Sie können Ihren eigenen Executor auf CameraX festlegen, wenn Ihre Anwendung bestimmte Threading-Anforderungen hat. Wenn die Richtlinie nicht konfiguriert ist, erstellt und verwendet CameraX eine optimierte standardmäßige interne Executor. Viele der Plattform-APIs, auf denen CameraX basiert, erfordern die Blockierung der Interprozesskommunikation (Interprocess Communication, IPC) mit Hardware, deren Reaktion manchmal Hunderte von Millisekunden dauern kann. Aus diesem Grund ruft CameraX diese APIs nur aus Hintergrundthreads auf. Dadurch wird der Hauptthread nicht blockiert und die UI bleibt flexibel. Weitere Informationen zu Threads

Wenn der Zielmarkt für Ihre App Low-End-Geräte umfasst, bietet CameraX eine Möglichkeit, die Einrichtungszeit mit einer Kamerabeschränkung zu verkürzen. Da das Herstellen einer Verbindung zu Hardwarekomponenten insbesondere auf Low-End-Geräten sehr zeitaufwendig sein kann, können Sie die von Ihrer App benötigten Kameras angeben. CameraX stellt nur während der Einrichtung eine Verbindung zu diesen Kameras her. Wenn die Anwendung beispielsweise nur rückseitige Kameras verwendet, kann sie diese Konfiguration mit DEFAULT_BACK_CAMERA festlegen. CameraX verhindert dann, dass Frontkameras initialisiert werden, um die Latenz zu reduzieren.

Konzepte der Android-Entwicklung

In diesem Leitfaden wird vorausgesetzt, dass Sie mit der Android-Entwicklung vertraut sind. Neben den Grundlagen sollten Sie sich noch einige Konzepte ansehen, bevor Sie mit dem Code weitermachen:

  • View Binding generiert eine Bindungsklasse für Ihre XML-Layoutdateien. So können Sie ganz einfach auf Ihre Ansichten in „Aktivitäten“ verweisen, wie es in verschiedenen Code-Snippets unten der Fall ist. Es gibt einige Unterschiede zwischen der Ansichtsbindung und findViewById() (die bisherige Methode, um auf Ansichten zu verweisen). Im folgenden Code sollten Sie die Bindungslinien der Ansicht durch einen ähnlichen findViewById()-Aufruf ersetzen können.
  • Asynchrone Koroutinen sind ein in Kotlin 1.3 hinzugefügtes Designmuster für Nebenläufigkeit. Es kann verwendet werden, um CameraX-Methoden zu verarbeiten, die ein ListenableFuture zurückgeben. Dies wird durch die Jetpack-Bibliothek Concurrent ab Version 1.1.0 vereinfacht. So fügen Sie der Anwendung eine asynchrone Koroutine hinzu:
    1. Fügen Sie Ihrer Gradle-Datei implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") hinzu.
    2. Fügen Sie jeden CameraX-Code, der ein ListenableFuture zurückgibt, in einen launch-Block oder eine ausgesetzte Funktion ein.
    3. Fügen Sie dem Funktionsaufruf, der einen ListenableFuture zurückgibt, einen await()-Aufruf hinzu.
    4. Weitere Informationen zur Funktionsweise von Koroutinen finden Sie im Leitfaden Koroutinen starten.

Gängige Szenarien migrieren

In diesem Abschnitt wird erläutert, 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 deiner Kamera-App kannst du als Erstes eine Möglichkeit anbieten, 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. So könnte das aussehen:

// 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 erfolgt die Kameraauswahl durch die Klasse CameraSelector. CameraX ist in der Regel die Verwendung der Standardkamera ganz einfach. 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 ganz einfach die Zoomstufe für Ihre App festlegen. Wenn Ihre App also auf einem Gerät ausgeführt wird, das logische Kameras unterstützt, wechselt sie zur richtigen Linse.

Hier ist der CameraX-Code zur Verwendung der Standardrü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: Kameraanbieter

Hier ein Beispiel für die Auswahl der Standard-Frontkamera mit CameraProvider (entweder die Front- oder 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 "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 steuern möchten, welche Kamera ausgewählt wird, ist dies auch in CameraX möglich, wenn Sie CameraProvider verwenden. Dazu rufen Sie getAvailableCameraInfos() auf. Dadurch erhalten Sie ein CameraInfo-Objekt, mit dem Sie bestimmte Kameraeigenschaften wie isFocusMeteringSupported() prüfen können. Anschließend können Sie sie in ein CameraSelector-Objekt konvertieren und dann wie in den obigen Beispielen mit der Methode CameraInfo.getCameraSelector() verwenden.

Mit der Klasse Camera2CameraInfo können Sie weitere Details zu den einzelnen Kameras abrufen. 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, die Sie abfragen 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 wird angezeigt

Bei den meisten Kamera-Apps muss der Kamerafeed irgendwann auf dem Bildschirm angezeigt werden. Mit Camera1 müssen Sie die Lebenszyklus-Callbacks korrekt verwalten und außerdem die Drehung und Skalierung der Vorschau bestimmen.

Außerdem musst du in Camera1 festlegen, ob du ein TextureView- oder ein SurfaceView-Objekt als Vorschauoberfläche verwenden möchtest. Beide Optionen haben Nachteile. In jedem Fall müssen Sie bei Camera1 die Drehung und Skalierung korrekt handhaben. Für PreviewView von CameraX liegen dagegen Implementierungen für TextureView und SurfaceView zugrunde. Die Entscheidung, welche Implementierung von CameraX am besten ist, hängt von Faktoren wie dem Gerätetyp und der Android-Version ab, auf der Ihre App ausgeführt wird. Wenn eine der beiden Implementierungen kompatibel ist, können Sie Ihre Präferenz mit PreviewView.ImplementationMode deklarieren. Die Option COMPATIBLE verwendet einen TextureView für die Vorschau und für den PERFORMANCE-Wert einen SurfaceView (wenn möglich).

Kamera1

Zum Anzeigen einer Vorschau müssen Sie Ihre eigene Preview-Klasse mit einer Implementierung der android.view.SurfaceHolder.Callback-Schnittstelle schreiben, mit der Bilddaten von der Kamerahardware an die Anwendung übergeben werden. Bevor Sie dann die Livebildvorschau starten können, muss die Klasse Preview 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. Dies bedeutet, dass Preview UseCase impliziert ist, was die Einrichtung viel weniger Aufwand macht:

// 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: Kameraanbieter

Mit CameraProvider von CameraX müssen Sie PreviewView nicht verwenden. Die Einrichtung der Vorschau über Camera1 wird aber trotzdem erheblich vereinfacht. Zu Demonstrationszwecken wird in diesem Beispiel ein PreviewView verwendet. Sie können jedoch einen benutzerdefinierten SurfaceProvider schreiben, der an setSurfaceProvider() übergeben wird, wenn Ihre Anforderungen komplexer sind.

Hier ist die Preview-UseCase nicht impliziert, wie es bei CameraController der Fall ist. Daher müssen Sie sie 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, besteht eine gängige Steuerung darin, den Fokuspunkt festzulegen, wenn der Nutzer auf die Vorschau tippt.

Kamera1

Wenn Sie die Funktion „Zum Fokussieren antippen“ in Kamera1 verwenden möchten, müssen Sie den optimalen Fokus Area berechnen, um anzugeben, wo Camera fokussieren soll. Dieses Area wird an setFocusAreas() übergeben. Außerdem musst du einen kompatiblen Fokusmodus auf Camera festlegen. Fokusbereich ist nur wirksam, wenn der aktuelle Fokusmodus FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO oder FOCUS_MODE_CONTINUOUS_PICTURE ist.

Jedes Area-Element ist ein Rechteck mit einer angegebenen Gewichtung. Die Gewichtung ist ein Wert zwischen 1 und 1.000. Sie wird verwendet, um den Fokus Areas zu priorisieren, wenn mehrere festgelegt sind. In diesem Beispiel wird nur eine Area verwendet, sodass der Gewichtungswert keine Rolle spielt. Die Koordinaten des Rechtecks reichen von -1.000 bis 1.000. Der obere linke Punkt ist (-1000, -1000). Der untere rechte Punkt ist (1.000, 1.000). Die Richtung richtet sich nach der Sensorausrichtung, das heißt, was der Sensor erfasst. Die Richtung wird durch die Drehung oder Spiegelung von Camera.setDisplayOrientation() nicht beeinflusst. Daher müssen Sie die Koordinaten des Berührungsereignisses 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 das Tippen zum Fokussieren automatisch zu verarbeiten. Sie können die Funktion zum Fokussieren durch Tippen 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 auf dem CameraController nachzuverfolgen.

// 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: Kameraanbieter

Bei Verwendung eines CameraProvider sind einige Einrichtungsschritte erforderlich, um das Tippen zum Fokussieren zu nutzen. In diesem Beispiel wird davon ausgegangen, dass Sie PreviewView verwenden. Falls nicht, müssen Sie die Logik so anpassen, dass sie auf Ihr benutzerdefiniertes Surface-Objekt angewendet wird.

Wenn Sie PreviewView verwenden, gehen Sie so vor:

  1. Richte einen Bewegungsdetektor ein, um Tippereignisse zu verarbeiten.
  2. Erstellen Sie mit dem Tippereignis ein MeteringPoint mit MeteringPointFactory.createPoint().
  3. Erstellen Sie mit dem MeteringPoint eine FocusMeteringAction.
  4. Rufen Sie mit dem CameraControl-Objekt auf Ihrer Camera (von bindToLifecycle() zurückgegeben) startFocusAndMetering() auf und übergeben Sie die FocusMeteringAction.
  5. Optional: Antworten Sie auf die FocusMeteringResult.
  6. Sie können festlegen, dass die Bewegungserkennung in PreviewView.setOnTouchListener() auf Berührungsereignisse 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
// "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
}

Zoomen durch Auseinander- und Zusammenziehen

Das Vergrößern und Verkleinern einer Vorschau ist eine weitere gängige direkte Manipulation der Kameravorschau. Immer mehr Kameras auf Geräten erwarten auch, dass durch das Zoomen die Linse mit der besten Brennweite automatisch ausgewählt wird.

Kamera1

Es gibt zwei Möglichkeiten zum Zoomen mit Kamera1. Die Methode Camera.startSmoothZoom() animiert uns von der aktuellen Zoomstufe bis zu der an die übergebenen Zoomstufe. Die Methode Camera.Parameters.setZoom() springt direkt zu der Zoomstufe, die Sie übergeben. Bevor Sie eine dieser Methoden verwenden, müssen Sie isSmoothZoomSupported() bzw. isZoomSupported() aufrufen, um sicherzustellen, dass die benötigten Zoommethoden auf der Kamera verfügbar sind.

Zum Implementieren des Zoomens durch Auseinander- und Zusammenziehen wird in diesem Beispiel setZoom() verwendet, da der Touch-Listener auf der Vorschauoberfläche beim Auseinander- und Zusammenziehen kontinuierlich Ereignisse auslöst, sodass der Zoomfaktor jedes Mal sofort aktualisiert wird. Die Klasse ZoomTouchListener ist unten definiert und sollte als Callback 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 erfasst CameraController die Touch-Ereignisse von PreviewView, um automatisch durch Auseinander- und Zusammenziehen zu zoomen. Sie können das Zoomen durch Auseinander- und Zusammenziehen mit setPinchToZoomEnabled() aktivieren oder deaktivieren und den Wert mit dem entsprechenden Getter isPinchToZoomEnabled() prüfen.

Die Methode getZoomState() gibt ein LiveData-Objekt zurück, um Änderungen an ZoomState für 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: Kameraanbieter

Damit das Zoomen durch Auseinander- und Zusammenziehen der Finger in CameraProvider funktioniert, sind einige Einrichtungsschritte erforderlich. Wenn Sie PreviewView nicht verwenden, müssen Sie die Logik so anpassen, dass sie auf Ihre benutzerdefinierte Surface angewendet wird.

Wenn Sie PreviewView verwenden, gehen Sie so vor:

  1. Richten Sie eine Gestenerkennung für die Skalierung ein, um Auseinander- und Zusammenziehen der Finger zu erkennen.
  2. Rufen Sie die ZoomState aus dem Camera.CameraInfo-Objekt ab, wobei die Camera-Instanz zurückgegeben wird, wenn Sie bindToLifecycle() aufrufen.
  3. Wenn ZoomState einen zoomRatio-Wert hat, speichern Sie diesen als aktuelles Zoomverhältnis. Wenn für ZoomState kein zoomRatio angegeben ist, wird die Standardzoomrate der Kamera (1,0) verwendet.
  4. Übergeben Sie das Produkt des aktuellen Zoomverhältnisses mit scaleFactor, um das neue Zoomverhältnis zu bestimmen, und übergeben Sie dieses an CameraControl.setZoomRatio().
  5. Sie können festlegen, dass die Bewegungserkennung in PreviewView.setOnTouchListener() auf Berührungsereignisse 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-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Foto wird aufgenommen

In diesem Abschnitt wird beschrieben, wie Sie die Fotoaufnahme auslösen, wenn Sie den Auslöser betätigen, einen Timer abgelaufen sind oder ein beliebiges anderes Ereignis auswählen.

Kamera1

In Kamera1 definieren Sie zuerst einen Camera.PictureCallback, um die Bilddaten bei Anfrage zu verwalten. Hier ist 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() auf der Instanz Camera auf. Diese Methode takePicture() hat drei verschiedene Parameter für unterschiedliche Datentypen. Der erste Parameter gilt für einen ShutterCallback (der in diesem Beispiel nicht definiert ist). Der zweite Parameter ist für ein PictureCallback zur Verarbeitung der (nicht komprimierten) Kamerarohdaten vorgesehen. In diesem Beispiel wird der dritte Parameter verwendet, da er ein PictureCallback ist, um JPEG-Bilddaten zu verarbeiten.

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

camera?.takePicture(null, null, picture)

CameraX: CameraController

CameraController von CameraX bietet die einfache Bildaufnahme von Camera1 durch Implementierung einer eigenen Methode takePicture(). Definieren Sie hier eine Funktion, um einen MediaStore-Eintrag zu konfigurieren und ein Foto zum Speichern dort aufzunehmen.

// 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: Kameraanbieter

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 für den Aufruf von takePicture() zu haben:

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

// 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

Das Aufzeichnen eines Videos ist wesentlich komplizierter als die bisher betrachteten Szenarien. Jeder Teil des Prozesses muss ordnungsgemäß eingerichtet werden, in der Regel in einer bestimmten Reihenfolge. Außerdem müssen Sie möglicherweise prüfen, ob Video und Audio synchron sind oder ob es zusätzliche Geräteinkonsistenzen gibt.

Wie Sie sehen, übernimmt CameraX 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. Die Methoden müssen in einer bestimmten Reihenfolge aufgerufen werden. Sie müssen dieser Reihenfolge folgen, damit Ihre Anwendung ordnungsgemäß funktioniert:

  1. Öffne die Kamera.
  2. Bereiten Sie eine Vorschau vor und starten Sie sie (wenn in Ihrer App das aufgezeichnete Video angezeigt wird, was in der Regel der Fall ist).
  3. Entsperre die Kamera zur Verwendung durch MediaRecorder, indem du Camera.unlock() anrufst.
  4. Konfigurieren Sie die Aufzeichnung, indem Sie diese Methoden in 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. Unter CamcorderProfile finden Sie alle Qualitätsoptionen.
    5. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) anrufen.
    6. Wenn in deiner App eine Vorschau des Videos verfügbar ist, rufe setPreviewDisplay(preview?.holder?.surface) auf.
    7. setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) anrufen.
    8. setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) anrufen.
    9. setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) anrufen.
    10. Rufe prepare() auf, um die Konfiguration von MediaRecorder abzuschließen.
  5. Um die Aufzeichnung zu starten, ruf MediaRecorder.start() an.
  6. Rufen Sie die folgenden Methoden auf, um die Aufzeichnung zu beenden. Halten Sie sich dabei genau an diese Reihenfolge:
    1. MediaRecorder.stop() anrufen.
    2. Optional können Sie die aktuelle MediaRecorder-Konfiguration durch Aufrufen von MediaRecorder.reset() entfernen.
    3. MediaRecorder.release() anrufen.
    4. Sperre die Kamera, damit sie in zukünftigen MediaRecorder-Sitzungen verwendet werden kann. Dazu musst du Camera.lock() aufrufen.
  7. Um die Vorschau zu beenden, rufen Sie Camera.stopPreview() auf.
  8. Um Camera freizugeben, damit andere Prozesse es verwenden können, rufen Sie Camera.release() auf.

Hier sehen Sie eine Zusammenfassung dieser Schritte:

// 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 UseCase-Werte ImageCapture, VideoCapture und ImageAnalysis unabhängig voneinander umschalten, solange die Liste der Anwendungsfälle gleichzeitig verwendet werden kann. Die UseCases für ImageCapture und ImageAnalysis sind standardmäßig aktiviert. Deshalb musste setEnabledUseCases() nicht aufgerufen werden, um ein Foto aufzunehmen.

Wenn du ein CameraController für die Videoaufzeichnung verwenden möchtest, musst du zuerst setEnabledUseCases() verwenden, um VideoCapture UseCase zuzulassen.

// 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 aufgezeichnete Video in einem File gespeichert werden, wie im folgenden Beispiel gezeigt. Außerdem müssen Sie einen Executor und eine Klasse übergeben, die OnVideoSavedCallback implementiert, um Erfolgs- und Fehler-Callbacks zu verarbeiten. Wenn die Aufzeichnung beendet werden soll, rufen Sie CameraController.stopRecording() auf.

Hinweis:Wenn du CameraX 1.3.0-alpha02 oder höher verwendest, gibt es den zusätzlichen Parameter AudioConfig, mit dem du die Audioaufnahme für dein Video aktivieren oder deaktivieren kannst. Zum Aktivieren der Audioaufnahme benötigen Sie die Mikrofonberechtigungen. Außerdem wurde die Methode stopRecording() in 1.3.0-alpha02 entfernt und startRecording() gibt ein Recording-Objekt zurück, das zum Anhalten, Fortsetzen und Anhalten der Videoaufzeichnung verwendet 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: Kameraanbieter

Wenn Sie ein CameraProvider verwenden, müssen Sie ein VideoCapture-UseCase erstellen und ein Recorder-Objekt übergeben. Auf dem Recorder.Builder kannst du die Videoqualität und optional einen FallbackStrategy festlegen, der Fälle verarbeitet, in denen ein Gerät nicht den gewünschten Qualitätsvorgaben entspricht. Binden Sie dann die VideoCapture-Instanz mit den anderen UseCase-Instanzen an 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)

An dieser Stelle kann im Attribut videoCapture.output auf Recorder zugegriffen werden. Recorder kann Videoaufzeichnungen starten, die in einem File-, ParcelFileDescriptor- oder MediaStore-Objekt gespeichert sind. In diesem Beispiel wird MediaStore verwendet.

Für den Recorder gibt es mehrere Methoden, um ihn vorzubereiten. Rufen Sie prepareRecording() auf, um die Ausgabeoptionen für MediaStore festzulegen. Wenn deine App die Berechtigung zur Verwendung des Mikrofons des Geräts hat, rufe auch withAudioEnabled() auf. Rufen Sie dann start() auf, um mit der Aufzeichnung zu beginnen. Übergeben Sie dabei einen Kontext und einen Consumer<VideoRecordEvent>-Ereignis-Listener zur Verarbeitung von Videoaufzeichnungsereignissen. Bei Erfolg kann die zurückgegebene Recording verwendet werden, um die Aufzeichnung anzuhalten, 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-Anwendungen. Diese Beispiele zeigen, wie die Szenarien in diesem Leitfaden in eine vollwertige Android-App passen.

Wenn Sie zusätzliche Unterstützung bei der Migration zu CameraX wünschen oder Fragen zur Suite der Android Camera APIs haben, kontaktieren Sie uns bitte in der CameraX-Diskussionsgruppe.