Se la tua app utilizza la versione originale di Camera
("Camera1"), che è stata ritirata dal momento
Android 5.0 (livello API 21),
ti consigliamo vivamente di passare a un'API moderna per fotocamera Android. Offerte Android
CameraX (una fotocamera Jetpack standardizzata e robusta
API) e Camera2 (un'API del framework di basso livello). Per
Nella maggior parte dei casi, ti consigliamo di eseguire la migrazione dell'app a CameraX. Di seguito le ragioni:
- Facilità d'uso: CameraX gestisce i dettagli di basso livello, così puoi: concentrati meno sulla creazione di un'esperienza con la fotocamera da zero per differenziare la tua app.
- CameraX gestisce la frammentazione al posto tuo: CameraX riduce le attività a lungo termine costi di manutenzione e codice specifico del dispositivo, offrendo esperienze di qualità superiore agli utenti. Per saperne di più, dai un'occhiata alla nostra Migliore compatibilità dei dispositivi con CameraX post del blog.
- Funzionalità avanzate: CameraX è stata progettata con attenzione per rendere avanzate una funzionalità semplice da incorporare nella tua app. Ad esempio, puoi facilmente e applicare Bokeh, Ritocco viso, HDR (High Dynamic Range) e luminosità in condizioni di scarsa illuminazione Modalità Acquisizione notturna per le tue foto con Estensioni di CameraX.
- Aggiornabilità: Android rilascia nuove funzionalità e correzioni di bug su CameraX nel corso dell'anno. Eseguendo la migrazione a CameraX, la tua app avrà la versione più recente di Android tecnologia fotocamera a ogni release di CameraX, non solo sulla di release annuali della versione Android.
In questa guida troverai scenari comuni per le applicazioni della fotocamera. Ciascuna uno scenario include un'implementazione di Camera1 e un'implementazione di CameraX per a confronto.
Quando si tratta di migrazione, a volte hai bisogno di una maggiore flessibilità per l'integrazione
con un codebase esistente. Tutto il codice CameraX in questa guida ha un
CameraController
l'implementazione, ideale se vuoi il modo più semplice di utilizzare CameraX, oltre a una
CameraProvider
implementazione, ideale se hai bisogno di maggiore flessibilità. Per aiutarti a decidere quale
quello giusto per te, ecco i vantaggi di ciascuno:
Controller videocamera |
Fornitore fotocamera |
Richiede un piccolo codice di configurazione | Consente un maggiore controllo |
Se permetti a CameraX di gestire un numero maggiore di operazioni di configurazione, le funzionalità di tocco per mettere a fuoco e pizzica per lo zoom funzionano automaticamente |
Poiché lo sviluppatore dell'app gestisce la configurazione, ci sono più opportunità
per personalizzare la configurazione, ad esempio abilitando la rotazione dell'immagine di output
o impostando il formato dell'immagine di output in ImageAnalysis
|
La richiesta di PreviewView per l'anteprima della fotocamera consente
CameraX per offrire una perfetta integrazione end-to-end, come nel nostro ML Kit
integrazione che può mappare le coordinate dei risultati del modello ML (come una faccia
riquadri di delimitazione) direttamente sulle coordinate di anteprima
|
La possibilità di utilizzare una "Piattaforma" personalizzata per l'anteprima della fotocamera consente di maggiore flessibilità, ad esempio l'utilizzo del codice "Surface" esistente che potrebbe essere un input per altre parti della tua app |
Se non riesci a eseguire la migrazione, contattaci sul Gruppo di discussione di CameraX.
Prima della migrazione
Confrontare l'utilizzo di CameraX con Camera1
Anche se il codice può avere un aspetto diverso, i concetti alla base di Camera1 e
CameraX è molto simile. FotocameraX
abbrevia le funzionalità più comuni della fotocamera nei casi d'uso,
Di conseguenza, molte attività affidate allo sviluppatore in Fotocamera1
gestite automaticamente da CameraX. Esistono quattro
UseCase
in CameraX, che puoi usare
da utilizzare per varie attività della videocamera: Preview
,
ImageCapture
,
VideoCapture
e
ImageAnalysis
.
Un esempio di CameraX che gestisce i dettagli di basso livello per gli sviluppatori è la
ViewPort
condiviso tra
UseCase
attivi. In questo modo, tutti i componenti UseCase
vedranno esattamente gli stessi pixel.
In Fotocamera1, devi gestire personalmente questi dettagli e data la variabilità
nelle proporzioni dei vari dispositivi sensori e schermi della videocamera, può essere difficile
assicurati che l'anteprima corrisponda alle foto e ai video acquisiti.
Per fare un altro esempio, CameraX gestisce automaticamente i callback Lifecycle
nella
Lifecycle
istanza, la superi. Ciò significa che CameraX gestisce le
alla videocamera durante l'intero processo
Ciclo di vita dell'attività di Android,
inclusi i seguenti casi: chiusura della fotocamera quando l'app accede
il contesto; rimuovendo l'anteprima della fotocamera quando lo schermo non richiede più
la visualizzazione; e mettendo in pausa l'anteprima della fotocamera quando scatta un'altra attività
la precedenza in primo piano, ad esempio una videochiamata in arrivo.
Infine, CameraX gestisce la rotazione e il ridimensionamento senza necessità di codice aggiuntivo
da parte tua. Nel caso di un Activity
con orientamento sbloccato,
La configurazione di UseCase
viene eseguita ogni volta che il dispositivo viene ruotato, mentre il sistema distrugge
e ricrea l'Activity
in caso di modifiche di orientamento. Questo comporta
UseCases
imposta la rotazione target in modo che corrisponda all'orientamento del display del
predefinito ogni volta.
Scopri di più sulle rotazioni in CameraX.
Prima di entrare nel dettaglio, diamo uno sguardo generale alle
UseCase
e come si rappresenterebbe un'app Fotocamera1. (I concetti di CameraX sono
blu e Fotocamera1
si trovano in
verde.
FotocameraX |
|||
Configurazione CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Anteprima | Acquisizione di immagini | Acquisizione video | Analisi delle immagini |
⁞ | ⁞ | ⁞ | ⁞ |
Gestisci la superficie di anteprima e impostala sulla fotocamera | Imposta PictureCallback e chiama takePicture() sulla fotocamera | Gestione della configurazione della videocamera e di MediaRecorder in un ordine specifico | Codice di analisi personalizzato basato sulla superficie di anteprima |
↑ | ↑ | ↑ | ↑ |
Codice specifico per dispositivo | |||
↑ | |||
Gestione della rotazione e della scalabilità dei dispositivi | |||
↑ | |||
Gestione delle sessioni della videocamera (selezione della videocamera, gestione del ciclo di vita) | |||
Fotocamera1 |
Compatibilità e prestazioni in CameraX
CameraX supporta dispositivi con Android 5.0 (livello API 21) e versioni successive. Questo rappresenta oltre il 98% dei dispositivi Android esistenti. CameraX è progettata per gestire automaticamente le differenze tra i dispositivi, riducendo la necessità di usare codice nella tua app. Inoltre testiamo oltre 150 dispositivi fisici su tutti i dispositivi Android versioni successive alla 5.0 nel nostro CameraX Test Lab. Tu consultare l'elenco completo dispositivi attualmente presenti in Test Lab.
CameraX utilizza un Executor
per
per guidare lo stack di videocamere. Puoi
impostare il proprio esecutore su CameraX
se la tua app ha requisiti specifici per l'organizzazione in thread. Se non viene configurato, CameraX crea
e utilizza un valore interno predefinito ottimizzato per il Executor
. Molte delle API della piattaforma
la fotocameraX creata richiede il blocco della comunicazione tra processi (IPC) con
che a volte può impiegare centinaia di millisecondi per rispondere. Per questo
motivo, CameraX chiama queste API solo dai thread in background, assicurando così che
il thread principale non viene bloccato e la UI rimane fluida.
Scopri di più sui thread.
Se il mercato di destinazione della tua app include dispositivi di fascia bassa, CameraX ti offre
per ridurre i tempi di configurazione
limitatore di videocamera. Poiché il
di connessione ai componenti hardware può richiedere una quantità non banale
soprattutto sui dispositivi di fascia bassa, puoi specificare l'insieme di videocamere
e alle esigenze aziendali. CameraX si connette a queste videocamere solo durante la configurazione. Ad esempio, se
l'applicazione utilizza solo fotocamere posteriori, può impostare questa configurazione
con DEFAULT_BACK_CAMERA
, quindi CameraX evita l'inizializzazione della fotocamera anteriore
videocamere per ridurre la latenza.
Concetti di sviluppo Android
Questa guida presuppone una conoscenza generale dello sviluppo di Android. Oltre di base, ecco un paio di concetti utili prima di comprenderli per analizzare il codice riportato di seguito:
- L'opzione View Binding genera una classe di associazione per
i file di layout XML, consentendoti di semplificare
fare riferimento alle visualizzazioni in Attività,
come nei vari snippet di codice riportati di seguito. Ci sono alcuni
differenze tra associazione della vista e
findViewById()
(il modo precedente per fare riferimento alle viste), ma nel codice seguente dovresti essere in grado di sostituisci le righe di associazione della visualizzazione con una chiamatafindViewById()
simile. - Le coroutine asincrone sono una progettazione contemporanea
aggiunto in Kotlin 1.3 che può essere utilizzato per gestire i metodi CameraX che
restituisce un
ListenableFuture
. Questo è ancora più semplice con il Jetpack Libreria Concurrent a partire dalla versione 1.1.0. Per aggiungere una coroutine asincrona alla tua app:- Aggiungi
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
al file Gradle. - Inserisci qualsiasi codice CameraX che restituisca
ListenableFuture
in unlaunch
bloccare o funzione di sospensione. - Aggiungi un
await()
alla chiamata di funzione che restituisceListenableFuture
. - Per una comprensione più approfondita di come funzionano le coroutine, consulta la Inizia una coroutine.
- Aggiungi
Esegui la migrazione di scenari comuni
Questa sezione spiega come migrare scenari comuni da Camera1 a CameraX.
Ogni scenario riguarda un'implementazione di Camera1, un CameraProvider
di CameraX
e un'implementazione CameraController
di CameraX.
Selezione di una videocamera
Una delle prime cose che potresti offrire nell'applicazione della fotocamera è un per selezionare videocamere diverse.
Fotocamera1
In Camera1, puoi chiamare
Camera.open()
senza parametri
per aprire la prima fotocamera posteriore oppure puoi passare un ID intero per
fotocamera che vuoi aprire. Ecco un esempio di come potrebbe apparire:
// 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, la selezione della fotocamera è gestita dalla classe CameraSelector
. FotocameraX
semplifica il caso comune di utilizzo della videocamera predefinita. Puoi specificare se
vuoi la fotocamera anteriore o la fotocamera posteriore predefinita. Inoltre,
L'oggetto CameraControl
di CameraX ti consente di
impostare il livello di zoom per la tua app, quindi se
la tua app è in esecuzione su un dispositivo che supporta
fotocamere logiche,
all'obiettivo corretto.
Ecco il codice CameraX per utilizzare la fotocamera posteriore predefinita con un
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
Ecco un esempio di selezione della fotocamera anteriore predefinita con CameraProvider
(è possibile utilizzare sia la fotocamera anteriore che quella posteriore con CameraController
o
CameraProvider
):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector to use the default front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Se vuoi controllare quale videocamera selezionare, puoi farlo anche in
FotocameraX se utilizzi CameraProvider
chiamando
getAvailableCameraInfos()
,
che fornisce un oggetto CameraInfo
per controllare determinate proprietà della fotocamera, come
isFocusMeteringSupported()
.
Puoi quindi convertirlo in un CameraSelector
per utilizzarlo come in precedenza
di esempio con il metodo CameraInfo.getCameraSelector()
.
Puoi ottenere ulteriori dettagli su ogni videocamera utilizzando il
Camera2CameraInfo
. Chiama
getCameraCharacteristic()
con una chiave per i dati
della fotocamera che ti interessano. Controlla il
CameraCharacteristics
per un elenco di tutte le chiavi su cui puoi eseguire query.
Di seguito è riportato un esempio di utilizzo di una funzione checkFocalLength()
personalizzata che potresti
definisci te stesso:
// 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()
Visualizzazione di un'anteprima
La maggior parte delle applicazioni della videocamera deve mostrare il feed della videocamera sullo schermo in alcuni punto di accesso. Con Camera1, devi gestire correttamente i callback del ciclo di vita devi anche determinare la rotazione e il ridimensionamento dell'anteprima.
Inoltre, in Fotocamera 1 devi decidere se utilizzare un
TextureView
o un
SurfaceView
come piattaforma di anteprima.
Entrambe le opzioni prevedono dei compromessi e, in entrambi i casi, Camera1 richiede che tu
gestire correttamente la rotazione e la scalabilità. PreviewView
di CameraX, sull'altro
ha implementazioni sottostanti sia per TextureView
che per SurfaceView
.
CameraX decide quale sia l'implementazione migliore in base a fattori quali
il tipo di dispositivo e la versione di Android su cui è in esecuzione l'app. Se
se l'implementazione è compatibile, puoi dichiarare la tua preferenza
PreviewView.ImplementationMode
L'opzione COMPATIBLE
utilizza un TextureView
per l'anteprima, mentre
Il valore PERFORMANCE
utilizza un valore SurfaceView
(se possibile).
Fotocamera1
Per mostrare un'anteprima, devi scrivere il tuo corso Preview
con un
dell'implementazione
android.view.SurfaceHolder.Callback
che viene utilizzata per passare i dati immagine dall'hardware della videocamera
un'applicazione. Prima di poter avviare l'anteprima dell'immagine live, Preview
deve essere passata all'oggetto Camera
.
// Camera1: set up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } override fun surfaceCreated(holder: SurfaceHolder) { // The Surface has been created, now tell the camera // where to draw the preview. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // Take care of releasing the Camera preview in your activity. } override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { // If your preview can change or rotate, take care of those // events here. Make sure to stop the preview before resizing // or reformatting it. if (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried to stop a non-existent preview; nothing to do. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the content of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
CameraX: CameraController
In CameraX, c'è molto meno da gestire per te, lo sviluppatore. Se utilizzi un
CameraController
, devi usare anche PreviewView
. Ciò significa che
Preview
UseCase
è implicito, il che rende la configurazione molto meno semplice:
// 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
Con CameraProvider
di CameraX, non è necessario utilizzare PreviewView
,
semplifica notevolmente la configurazione dell'anteprima su Fotocamera1. Per dimostrazione
ai fini, questo esempio utilizza un PreviewView
, ma puoi scrivere una
SurfaceProvider
per passare a setSurfaceProvider()
in caso di problemi più complessi
e alle esigenze aziendali.
In questo caso, l'elemento Preview
UseCase
non è implicito come avviene per CameraController
,
quindi devi configurarlo:
// 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() } }
Tocca per mettere a fuoco
Quando l'anteprima della fotocamera è sullo schermo, un controllo comune è l'impostazione della messa a fuoco punto quando l'utente tocca l'anteprima.
Fotocamera1
Per implementare il tocco per mettere a fuoco nella Fotocamera1, devi calcolare la messa a fuoco ottimale
Area
per indicare dove Camera
deve tentare di concentrarsi. Questo Area
è
passati in setFocusAreas()
. Inoltre, devi impostare una modalità Niente distrazioni compatibile
Camera
. L'area di messa a fuoco ha effetto solo se la modalità corrente è
FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
o
FOCUS_MODE_CONTINUOUS_PICTURE
.
Ogni Area
è un rettangolo con peso specificato. La ponderazione è un valore compreso tra
1 e 1000 e viene utilizzato per assegnare la priorità all'elemento attivo Areas
se sono impostati più elementi. Questo
esempio utilizza un solo Area
, quindi il valore di ponderazione non è importante. Coordinate di
il rettangolo è compreso tra -1000 e 1000. Il punto in alto a sinistra è (-1000, -1000).
Il punto in basso a destra è (1000, 1000). La direzione è relativa al sensore
orientato, cioè ciò che vede il sensore. La direzione non è influenzata dalla
la rotazione o il mirroring di Camera.setDisplayOrientation()
, quindi devi
convertirà le coordinate dell'evento touch nelle coordinate del sensore.
// 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
ascolta gli eventi touch di PreviewView
per gestire
tocca per mettere a fuoco automaticamente. Puoi attivare e disattivare la funzionalità Tocca per mettere a fuoco con
setTapToFocusEnabled()
,
e verifica il valore con il getter corrispondente
isTapToFocusEnabled()
.
La
getTapToFocusState()
restituisce un oggetto LiveData
per tenere traccia delle modifiche allo stato attivo sul CameraController
.
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
CameraX: CameraProvider
Quando usi un CameraProvider
, è necessaria una configurazione per mettere a fuoco il tocco
funziona. Questo esempio presuppone l'utilizzo di PreviewView
. In caso contrario, è necessario
adatta la logica da applicare al tuo Surface
personalizzato.
Ecco i passaggi da seguire quando utilizzi PreviewView
:
- Configura un rilevatore di gesti per gestire gli eventi di tocco.
- Con l'evento tocco, crea un
MeteringPoint
utilizzandoMeteringPointFactory.createPoint()
. - Con
MeteringPoint
, crea unFocusMeteringAction
. - Con l'oggetto
CameraControl
suCamera
(restituito dabindToLifecycle()
), chiamastartFocusAndMetering()
, superandoFocusMeteringAction
. - (Facoltativo) Rispondi al
FocusMeteringResult
. - Imposta il rilevatore di gesti per rispondere agli eventi tocco in
PreviewView.setOnTouchListener()
.
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector to respond to tap events and call // startFocusAndMetering on CameraControl. If you want to use a // coroutine with await() to check the result of focusing, see the // "Android development concepts" section above. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> // See pinch-to-zooom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Pizzica per eseguire lo zoom
Aumentare e diminuire lo zoom di un'anteprima è un'altra manipolazione diretta e comune delle l'anteprima della fotocamera. Con il numero crescente di fotocamere sui dispositivi, gli utenti si aspetti che venga selezionato automaticamente l'obiettivo con la lunghezza focale migliore come risultato dello zoom.
Fotocamera1
Esistono due modi per eseguire lo zoom con Fotocamera1. Il metodo Camera.startSmoothZoom()
si anima dal livello di zoom corrente al livello di zoom che passi. La
Il metodo Camera.Parameters.setZoom()
passa direttamente al livello di zoom superato
in. Prima di utilizzare uno di questi servizi, chiama isSmoothZoomSupported()
oppure
isZoomSupported()
, rispettivamente, per garantire che i relativi metodi di zoom necessari
sono disponibili sulla tua Fotocamera.
Per implementare Pizzica per eseguire lo zoom, questo esempio utilizza setZoom()
perché il tocco
nella pagina di anteprima attiva continuamente gli eventi quando il pizzico
quindi aggiorna il livello di zoom immediatamente ogni volta. La
La classe ZoomTouchListener
è definita di seguito e deve essere impostata come callback
al listener di tocchi
della superficie di anteprima.
// 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
Come per il tocco per mettere a fuoco, CameraController
ascolta il tocco di PreviewView
per gestire automaticamente lo zoom con due dita. Puoi attivare e disattivare
pizzica per eseguire lo zoom con
setPinchToZoomEnabled()
,
e verifica il valore con il getter corrispondente
isPinchToZoomEnabled()
.
La
getZoomState()
restituisce un oggetto LiveData
per il monitoraggio delle modifiche apportate al
ZoomState
il
CameraController
.
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
CameraX: CameraProvider
Per utilizzare la funzionalità Pizzica per eseguire lo zoom con CameraProvider
, è necessaria una configurazione. Se
non utilizzi PreviewView
, devi adattare la logica da applicare alle
Surface
personalizzato.
Ecco i passaggi da seguire quando utilizzi PreviewView
:
- Configura un rilevatore di gesti della bilancia per gestire gli eventi di pizzicatura.
- Recupera
ZoomState
dall'oggettoCamera.CameraInfo
, doveCamera
viene restituita quando chiamibindToLifecycle()
. - Se
ZoomState
ha un valorezoomRatio
, salvalo come zoom attuale rapporto. Se suZoomState
non è presentezoomRatio
, usa l'impostazione predefinita della videocamera velocità di zoom (1,0). - Prendi il prodotto del rapporto di zoom corrente con
scaleFactor
a determinare il nuovo rapporto di zoom e passarlo aCameraControl.setZoomRatio()
. - Imposta il rilevatore di gesti per rispondere agli eventi tocco in
PreviewView.setOnTouchListener()
.
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector to respond to pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zooom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Scattare una foto
In questa sezione viene spiegato come attivare l'acquisizione di foto, se devi farlo su una della pressione del pulsante di scatto, dopo che è trascorso un timer o per qualsiasi altro evento della tua scegliere.
Fotocamera1
In Fotocamera1, devi innanzitutto definire
Camera.PictureCallback
per gestire i dati delle immagini quando richiesto. Ecco un esempio semplice
PictureCallback
per la gestione dei dati immagine JPEG:
// Camera1: define a Camera.PictureCallback to handle JPEG data. private val picture = Camera.PictureCallback { data, _ -> val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run { Log.d(TAG, "error creating media file, check storage permissions") return@PictureCallback } try { val fos = FileOutputStream(pictureFile) fos.write(data) fos.close() } catch (e: FileNotFoundException) { Log.d(TAG, "file not found", e) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
Quindi, ogni volta che vuoi scattare una foto, chiama il metodo takePicture()
.
sulla tua istanza Camera
. Questo metodo takePicture()
ha tre diverse
per tipi di dati diversi. Il primo parametro riguarda un
ShutterCallback
(che non è definita in questo esempio). Il secondo parametro è
di PictureCallback
per gestire i dati non elaborati (non compressi) della fotocamera. Il terzo
è quello utilizzato in questo esempio, poiché è un PictureCallback
da gestire
Dati immagine JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
CameraController
di CameraX mantiene la semplicità di Camera1 per le immagini
acquisisca con l'implementazione di un metodo takePicture()
proprio. In questo caso, definisci
per configurare una voce MediaStore
e scattare una foto da salvare lì.
// 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
Scattare una foto con CameraProvider
funziona quasi allo stesso modo che con
CameraController
, ma devi prima creare e associare un ImageCapture
UseCase
per avere un oggetto su cui chiamare takePicture()
:
// CameraX: create and bind an ImageCapture UseCase. // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
Quando vuoi scattare una foto, puoi chiamare il numero
ImageCapture.takePicture()
. Visualizza il codice CameraController
in questa sezione
per un esempio completo della funzione 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( ... ) }
Registrazione di un video
Registrare un video è molto più complicato degli scenari esaminati fino ad ora. Ogni parte del processo deve essere impostata correttamente, solitamente in un in un determinato ordine. Inoltre, potresti dover verificare che il video e l'audio siano sincronizzare o gestire altre incoerenze tra i dispositivi.
Come vedrai, CameraX gestisce di nuovo gran parte di questa complessità per te.
Fotocamera1
L'acquisizione video con Fotocamera1 richiede un'attenta gestione di Camera
e
MediaRecorder
e i metodi devono
essere chiamato in un determinato ordine. Devi seguire questo ordine per
che l'applicazione funzioni correttamente:
- Apri la fotocamera.
- Preparare e avviare un'anteprima (se la tua app mostra il video che viene registrato, come in genere accade).
- Sblocca la fotocamera per utilizzarla da
MediaRecorder
chiamando il numeroCamera.unlock()
. - Configura la registrazione chiamando questi metodi su
MediaRecorder
:- Connetti la tua istanza
Camera
asetCamera(camera)
. - Chiama il numero
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Chiama il numero
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Chiama il numero
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
per impostare la qualità. ConsultaCamcorderProfile
per tutti le opzioni di qualità. - Chiama il numero
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - Se la tua app ha un'anteprima del video, chiama
setPreviewDisplay(preview?.holder?.surface)
. - Chiama il numero
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - Chiama il numero
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - Chiama il numero
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - Chiama
prepare()
per finalizzare la configurazione diMediaRecorder
.
- Connetti la tua istanza
- Per avviare la registrazione, chiama il numero
MediaRecorder.start()
. - Per interrompere la registrazione, richiama questi metodi. Anche in questo caso, segui questo ordine esatto:
- Chiama il numero
MediaRecorder.stop()
. - Se vuoi, rimuovi l'attuale configurazione di
MediaRecorder
chiamandoMediaRecorder.reset()
. - Chiama il numero
MediaRecorder.release()
. - Blocca la videocamera in modo che le sessioni future di
MediaRecorder
possano utilizzarla entro chiamata al numeroCamera.lock()
.
- Chiama il numero
- Per interrompere l'anteprima, chiama
Camera.stopPreview()
. - Infine, per rilasciare
Camera
in modo che altri processi possano utilizzarlo, richiamaCamera.release()
.
Ecco tutti questi passaggi combinati:
// 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
Con CameraController
di CameraX, puoi attivare/disattivare ImageCapture
,
VideoCapture
e ImageAnalysis
UseCase
in modo indipendente,
purché l'elenco di casi d'uso possa essere utilizzato contemporaneamente.
I UseCase
di ImageCapture
e ImageAnalysis
sono abilitati per impostazione predefinita e
ecco perché non hai dovuto chiamare setEnabledUseCases()
per scattare una foto.
Per usare CameraController
per la registrazione video, devi prima usare
setEnabledUseCases()
per consentire l'accesso a VideoCapture
UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Quando vuoi iniziare a registrare video, puoi chiamare il
CameraController.startRecording()
personalizzata. Questa funzione consente di salvare il video registrato su un File
, come puoi vedere
nell'esempio riportato di seguito. Inoltre, devi superare un Executor
e un corso
che implementa
OnVideoSavedCallback
per gestire i callback di successo ed errore. Quando la registrazione dovrebbe terminare, chiama
CameraController.stopRecording()
Nota: se utilizzi CameraX 1.3.0-alpha02 o versioni successive, è disponibile un'ulteriore
Parametro AudioConfig
che ti consente di attivare o disattivare la registrazione audio sul tuo video. Per attivare
registrazione audio, devi assicurarti di disporre delle autorizzazioni di accesso al microfono.
Inoltre, il metodo stopRecording()
viene rimosso nella versione 1.3.0-alpha02 e
startRecording()
restituisce un oggetto Recording
che può essere usato per la messa in pausa,
il ripristino e l'interruzione della registrazione video.
// 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
Se utilizzi un CameraProvider
, devi creare un VideoCapture
UseCase
e trasmettiamo un oggetto Recorder
. Su Recorder.Builder
, puoi
impostare la qualità video e, facoltativamente, una
FallbackStrategy
, che
gestisce i casi in cui un dispositivo non sia in grado di soddisfare le specifiche di qualità desiderate. Poi
associare l'istanza VideoCapture
a CameraProvider
con l'altra
UseCase
sec.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
A questo punto, è possibile accedere a Recorder
sul videoCapture.output
proprietà. L'Recorder
può avviare registrazioni video salvate in un File
,
ParcelFileDescriptor
o MediaStore
. In questo esempio viene utilizzato MediaStore
.
Su Recorder
, sono disponibili diversi metodi per effettuare una chiamata per prepararlo. Chiama
prepareRecording()
per impostare le opzioni di output MediaStore
. Se la tua app ha
per usare il microfono del dispositivo, chiama anche withAudioEnabled()
.
Quindi, chiama start()
per iniziare a registrare, passando in un contesto e una
Listener di eventi Consumer<VideoRecordEvent>
per gestire gli eventi di registrazione video. Se
riuscito, il valore Recording
restituito può essere utilizzato per mettere in pausa, riprendere o interrompere
registrazione in tempo reale.
// 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 } } } } }
Risorse aggiuntive
Abbiamo diverse app CameraX complete nel nostro Repository GitHub di esempi di videocamere. Questi esempi mostrano come gli scenari in questa guida si inseriscono in una panoramica App per Android.
Se desideri ulteriore assistenza per la migrazione a CameraX o hai domande in merito alla suite di API Android Camera, contattaci sul Discussione su CameraX Gruppo.