Se la tua app utilizza la classe Camera
originale ("Fotocamera1"), che è stata ritirata a partire da Android 5.0 (livello API 21), ti consigliamo vivamente di passare a un'API Android Camera moderna. Android offre
FotocameraX (un'API per fotocamera Jetpack standardizzata e robusta)
e Fotocamera2 (un'API framework di basso livello). Nella maggior parte dei casi, 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 concentrarti meno sulla creazione di un'esperienza con la fotocamera da zero e più sulla differenziazione dell'app.
- FotocameraX gestisce la frammentazione al posto tuo: CameraX riduce i costi di manutenzione a lungo termine e il codice specifico per dispositivo, offrendo esperienze di qualità superiore agli utenti. Per ulteriori informazioni, leggi il nostro post del blog Migliore compatibilità dei dispositivi con CameraX.
- Funzionalità avanzate: CameraX è stata progettata con cura per semplificare l'integrazione di funzionalità avanzate nella tua app. Ad esempio, puoi applicare facilmente le funzionalità Bokeh, ritocco viso, HDR (High Dynamic Range) e scarsa luminosità Modalità di acquisizione notturna alle tue foto con le estensioni CameraX.
- Updatabilità: Android rilascia nuove funzionalità e correzioni di bug per CameraX nel corso dell'anno. Se esegui la migrazione a CameraX, la tua app riceve la tecnologia della fotocamera Android più recente a ogni release di CameraX, non solo per le release annuali della versione di Android.
In questa guida troverai scenari comuni relativi alle applicazioni della fotocamera. Ogni scenario include un'implementazione di Camera1 e un'implementazione di CameraX per il confronto.
Quando si tratta di migrazione, a volte è necessaria una maggiore flessibilità per l'integrazione con un codebase esistente. Tutto il codice di CameraX in questa guida include
un'implementazione di
CameraController
, ideale se vuoi il modo più semplice per usare CameraX, e anche un'implementazione di
CameraProvider
, ideale se hai bisogno di maggiore flessibilità. Per aiutarti a scegliere quella più adatta a te, ecco i vantaggi di ognuna:
Controller della videocamera |
Fornitore Fotocamera |
Richiede poco codice di configurazione | Consente un maggiore controllo |
Consentendo a CameraX di gestire una parte maggiore del processo di configurazione, funzionalità come il tocco per mettere a fuoco e il pizzico per lo zoom verranno attivate automaticamente |
Poiché lo sviluppatore dell'app gestisce la configurazione, ci sono più opportunità di personalizzare la configurazione, ad esempio attivare la rotazione dell'immagine di output o impostare il formato dell'immagine di output in ImageAnalysis
|
La richiesta di PreviewView per l'anteprima della fotocamera consente a CameraX di offrire un'integrazione end-to-end senza interruzioni, come nell'integrazione del nostro ML Kit, che può mappare le coordinate dei risultati del modello ML (come i riquadri di delimitazione dei volti) direttamente sulle coordinate di anteprima
|
La possibilità di utilizzare una "Surface" personalizzata per l'anteprima della fotocamera consente una 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 tramite il Gruppo di discussione di CameraX.
Prima di eseguire la migrazione
Confronta l'utilizzo di CameraX con Camera1
Anche se il codice può avere un aspetto diverso, i concetti di base di Camera1 e
FotocameraX sono molto simili. CameraX astrae le funzionalità più comuni della videocamera nei casi d'uso e, di conseguenza, molte attività lasciate allo sviluppatore in Camera1 vengono gestite automaticamente da CameraX. In CameraX sono disponibili quattro elementi UseCase
che puoi utilizzare per diverse attività della fotocamera: Preview
, ImageCapture
, VideoCapture
e ImageAnalysis
.
Un esempio di CameraX che gestisce i dettagli di basso livello per gli sviluppatori è il
ViewPort
condiviso tra
gli UseCase
attivi. In questo modo tutti i UseCase
vedono esattamente gli stessi pixel.
In Fotocamera1 devi gestire autonomamente questi dettagli e, considerata la variabilità delle proporzioni
tra i sensori e gli schermi della fotocamera dei dispositivi, può essere difficile
assicurarti che l'anteprima corrisponda a foto e video acquisiti.
Per fare un altro esempio, CameraX gestisce automaticamente i callback Lifecycle
nell'istanza Lifecycle
in cui la passi. Ciò significa che CameraX gestisce la connessione dell'app alla videocamera durante l'intero ciclo di vita dell'attività Android, inclusi i seguenti casi: chiusura della videocamera quando l'app entra in background, rimozione dell'anteprima della videocamera quando non viene più visualizzata sullo schermo e messa in pausa dell'anteprima della fotocamera quando un'altra attività ha la precedenza, ad esempio una videochiamata in arrivo.
Infine, CameraX gestisce la rotazione e la scalabilità senza richiedere alcun codice aggiuntivo da parte tua. Nel caso di un oggetto Activity
con orientamento sbloccato, la configurazione di UseCase
viene eseguita ogni volta che il dispositivo viene ruotato, poiché il sistema distrugge e ricrea il Activity
quando cambia l'orientamento. In questo modo UseCases
imposta ogni volta la rotazione target in modo che corrisponda all'orientamento del display per impostazione predefinita.
Scopri di più sulle rotazioni in FotocameraX.
Prima di entrare nei dettagli, diamo uno sguardo generale alle funzionalità UseCase
di CameraX e al legame tra un'app Camera1. (I concetti di CameraX sono in blu e quelli di Camera1 sono in verde).
FotocameraX |
|||
CameraController / Configurazione CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
Anteprima | Acquisizione immagine | Acquisizione video | Analisi di immagini |
⁞ | ⁞ | ⁞ | ⁞ |
Gestisci la Surface di anteprima e impostala sulla Fotocamera | Imposta PictureCallback e chiama takePicture() sulla fotocamera | Gestisci la configurazione della videocamera e di MediaRecorder in un ordine specifico | Codice di analisi personalizzato basato sulla Surface di anteprima |
↑ | ↑ | ↑ | ↑ |
Codice specifico per dispositivo | |||
↑ | |||
Gestione della rotazione e della scalabilità dei dispositivi | |||
↑ | |||
Gestione della sessione 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. oltre il 98% dei dispositivi Android esistenti. CameraX è progettata per gestire automaticamente le differenze tra i dispositivi, riducendo la necessità di utilizzare un codice specifico per dispositivo nell'app. Inoltre, nel nostro CameraX Test Lab testiamo oltre 150 dispositivi fisici su tutte le versioni di Android a partire dalla 5.0. Puoi consultare l'elenco completo dei dispositivi attualmente in Test Lab.
CameraX utilizza un elemento Executor
per controllare lo stack di fotocamere. Puoi impostare un tuo esecutore personalizzato su CameraX se la tua app ha requisiti specifici di threading. Se non viene configurato, CameraX crea
e utilizza un'impostazione Executor
interna predefinita ottimizzata. Molte delle API della piattaforma su cui è stata sviluppata CameraX richiedono il blocco della comunicazione tra processi (IPC) con hardware la cui risposta a volte può richiedere centinaia di millisecondi. Per questo motivo, CameraX chiama queste API solo dai thread in background, il che garantisce che il thread principale non sia bloccato e che l'interfaccia utente rimanga scorrevole.
Scopri di più sui thread.
Se il mercato di destinazione della tua app include dispositivi di fascia bassa, CameraX offre un modo per ridurre i tempi di configurazione con un limitatore per fotocamera. Poiché il processo di connessione ai componenti hardware può richiedere un tempo non banale, soprattutto sui dispositivi di fascia bassa, puoi specificare il set di videocamere di cui la tua app ha bisogno. 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
, dopodiché CameraX evita di inizializzare le fotocamere anteriori per ridurre la latenza.
Concetti di sviluppo di Android
Questa guida presuppone una conoscenza generale dello sviluppo di Android. Oltre alle nozioni di base, ecco un paio di concetti che è utile comprendere prima di passare al codice riportato di seguito:
- L'associazione delle viste genera una classe di associazione per i file di layout XML, che ti consente di fare riferimento facilmente alle tue viste in Attività, come avviene in diversi snippet di codice riportati di seguito. Esistono alcune differenze tra l'associazione della vista e
findViewById()
(il metodo precedente per fare riferimento alle viste), ma nel codice seguente dovresti essere in grado di sostituire le righe di associazione della vista con una chiamatafindViewById()
simile. - Le coroutine asincrone sono un modello di progettazione di contemporaneità aggiunto in Kotlin 1.3 che può essere utilizzato per gestire i metodi CameraX che restituiscono un valore
ListenableFuture
. Ciò è reso più semplice con la libreria Jetpack 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 un bloccolaunch
o in una funzione di sospensione. - Aggiungi una chiamata
await()
alla chiamata funzione che restituisceListenableFuture
. - Per una comprensione più approfondita di come funzionano le coroutine, consulta la guida Iniziare una coroutine.
- Aggiungi
Esegui la migrazione degli scenari comuni
Questa sezione spiega come eseguire la migrazione degli scenari comuni da Fotocamera1 a CameraX.
Ogni scenario riguarda un'implementazione di Camera1, CameraProvider
di CameraX e un'implementazione di CameraController
di CameraX.
Selezione di una videocamera
Nell'applicazione della fotocamera, una delle prime opzioni che proponiamo è un modo per selezionare diverse videocamere.
Fotocamera1
In Fotocamera1, puoi chiamare
Camera.open()
senza parametri
per aprire la prima fotocamera posteriore oppure passare un ID intero
per la videocamera che vuoi aprire. Ecco un esempio di come potrebbe essere strutturata:
// 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: controller della videocamera
In CameraX, la selezione della fotocamera viene gestita dalla classe CameraSelector
. CameraX semplifica l'uso comune della fotocamera predefinita. Puoi specificare se vuoi la fotocamera anteriore predefinita o quella posteriore. Inoltre,
l'oggetto CameraControl
di CameraX ti consente di
impostare facilmente il livello di zoom della tua app, quindi se
l'app è in esecuzione su un dispositivo che supporta
le fotocamere logiche, passerà
all'obiettivo corretto.
Ecco il codice CameraX per utilizzare la fotocamera posteriore predefinita con CameraController
:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
FotocameraX: CameraProvider
Ecco un esempio di selezione della fotocamera anteriore predefinita con CameraProvider
(la fotocamera anteriore o posteriore può essere utilizzata 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 è selezionata, puoi farlo anche in
FotocameraX se utilizzi CameraProvider
chiamando
getAvailableCameraInfos()
,
che ti fornisce un oggetto CameraInfo
per controllare determinate proprietà della videocamera, come
isFocusMeteringSupported()
.
Puoi quindi convertirlo in CameraSelector
da utilizzare come negli esempi precedenti con il metodo CameraInfo.getCameraSelector()
.
Puoi ottenere ulteriori dettagli su ogni videocamera utilizzando il corso Camera2CameraInfo
. Chiama il numero
getCameraCharacteristic()
con una chiave per i dati della videocamera che ti interessano. Controlla la classe CameraCharacteristics
per un elenco di tutte le chiavi su cui puoi eseguire query.
Ecco un esempio di utilizzo di una funzione checkFocalLength()
personalizzata che potresti definire personalmente:
// 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 per videocamere deve mostrare il feed della videocamera sullo schermo in un determinato momento. Con Camera1, devi gestire correttamente i callback del ciclo di vita e determinare anche la rotazione e il ridimensionamento dell'anteprima.
Inoltre, in Fotocamera1 devi decidere se utilizzare TextureView
o SurfaceView
come piattaforma di anteprima.
Entrambe le opzioni presentano compromessi e, in entrambi i casi, Fotocamera1 richiede di gestire correttamente la rotazione e la scalabilità. PreviewView
di CameraX, invece, ha implementazioni sottostanti sia per TextureView
che per SurfaceView
.
CameraX decide quale implementazione è migliore in base a fattori quali il tipo di dispositivo e la versione di Android su cui è in esecuzione la tua app. Se una delle due implementazioni è compatibile, puoi dichiarare la tua preferenza con PreviewView.ImplementationMode
.
L'opzione COMPATIBLE
utilizza un valore TextureView
per l'anteprima e il valore PERFORMANCE
utilizza un valore SurfaceView
(se possibile).
Fotocamera1
Per mostrare un'anteprima, devi scrivere la tua classe Preview
con un'implementazione dell'interfaccia android.view.SurfaceHolder.Callback
, che viene utilizzata per trasferire i dati immagine dall'hardware della fotocamera all'applicazione. Quindi, prima di poter avviare l'anteprima dell'immagine live, è necessario passare la classe Preview
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: controller della videocamera
In CameraX c'è molto meno da gestire per gli sviluppatori. Se utilizzi CameraController
, devi utilizzare anche PreviewView
. Ciò significa che è implicito Preview
UseCase
, rendendo molto meno efficiente la configurazione:
// 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 } }
FotocameraX: CameraProvider
Con CameraProvider
di CameraX, non è necessario utilizzare PreviewView
, ma semplifica notevolmente la configurazione dell'anteprima rispetto a Camera1. A fini dimostrativi, questo esempio utilizza un PreviewView
, ma puoi scrivere una SurfaceProvider
personalizzata da passare in setSurfaceProvider()
se hai esigenze più complesse.
In questo caso, il valore di Preview
UseCase
non è implicito come lo è con 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 consiste nell'impostare il punto di messa a fuoco quando l'utente tocca l'anteprima.
Fotocamera1
Per implementare la funzionalità Tocca per mettere a fuoco nella Fotocamera1, devi calcolare la messa a fuoco ottimale Area
per indicare il punto in cui Camera
deve tentare di mettere a fuoco. Questo Area
viene
passato in setFocusAreas()
. Inoltre, devi impostare una modalità di messa a fuoco compatibile su Camera
. L'area di messa a fuoco ha effetto solo se la modalità di messa a fuoco corrente è
FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
o
FOCUS_MODE_CONTINUOUS_PICTURE
.
Ogni Area
è un rettangolo con il peso specificato. La ponderazione è un valore compreso tra 1 e 1000 e viene utilizzata per dare la priorità a Areas
, se sono impostate più opzioni. Questo
esempio utilizza solo un'istruzione Area
, quindi il valore del peso non è importante. Le coordinate del rettangolo sono comprese tra -1000 e 1000. Il punto in alto a sinistra è (-1000, -1000).
Il punto in basso a destra è (1000, 1000). La direzione è relativa all'orientamento
del sensore, ovvero a ciò che vede il sensore. La direzione non è influenzata dalla rotazione o dal mirroring di Camera.setDisplayOrientation()
, quindi devi convertire le coordinate degli eventi 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: controller della videocamera
CameraController
ascolta gli eventi tocco di PreviewView
per gestire
automaticamente il tocco per mettere a fuoco. Puoi attivare e disattivare il tocco per mettere a fuoco con setTapToFocusEnabled()
e controllare il valore con il getter corrispondente isTapToFocusEnabled()
.
Il metodo getTapToFocusState()
restituisce un oggetto LiveData
per il monitoraggio delle modifiche allo stato di impostazione dello stato attivo su 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)
FotocameraX: CameraProvider
Quando si utilizza un CameraProvider
, sono necessarie alcune configurazioni per mettere a fuoco
tocca per mettere a fuoco. Questo esempio presuppone l'utilizzo di PreviewView
. In caso contrario, devi
adattare la logica da applicare al tuo Surface
personalizzato.
Ecco la procedura da seguire per utilizzare PreviewView
:
- Configura un rilevatore di gesti per gestire gli eventi di tocco.
- Con l'evento di tocco, crea un
MeteringPoint
utilizzandoMeteringPointFactory.createPoint()
. - Con il
MeteringPoint
, crea unFocusMeteringAction
. - Con l'oggetto
CameraControl
suCamera
(restituito dabindToLifecycle()
), chiamastartFocusAndMetering()
, passando laFocusMeteringAction
. - (Facoltativo) Rispondi alla
FocusMeteringResult
. - Imposta il rilevatore di gesti per rispondere agli eventi di 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 comune dell'anteprima della fotocamera. Con il numero crescente di fotocamere sui dispositivi, gli utenti si aspettano anche che venga selezionato automaticamente l'obiettivo con la lunghezza focale migliore in seguito allo zoom.
Fotocamera1
Esistono due modi per eseguire lo zoom con Fotocamera1. Il metodo Camera.startSmoothZoom()
consente di passare dal livello di zoom corrente a quello passato. Il metodo Camera.Parameters.setZoom()
passa direttamente al livello di zoom inserito. Prima di utilizzare uno dei due, chiama rispettivamente isSmoothZoomSupported()
o isZoomSupported()
per assicurarti che i relativi metodi di zoom necessari siano disponibili sulla Fotocamera.
Per implementare la funzionalità di pizzicatura per eseguire lo zoom, in questo esempio viene utilizzato setZoom()
perché l'ascoltatore al tocco sulla superficie di anteprima attiva continuamente eventi man mano che si esegue il gesto di pizzicatura, quindi aggiorna immediatamente il livello di zoom ogni volta. La
classe ZoomTouchListener
viene definita di seguito e deve essere impostata come callback
per il listener touch 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: controller della videocamera
Come per il tocco per mettere a fuoco, CameraController
ascolta gli eventi touch di PreviewView per gestire automaticamente lo zoom tramite pizzico. Puoi abilitare e disabilitare la funzionalità Pizzica per eseguire lo zoom con setPinchToZoomEnabled()
e verificare il valore con il getter corrispondente isPinchToZoomEnabled()
.
Il metodo getZoomState()
restituisce un oggetto LiveData
per il monitoraggio delle modifiche a ZoomState
nella 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)
FotocameraX: CameraProvider
Per utilizzare la funzionalità Pizzica per eseguire lo zoom con CameraProvider
, è necessaria una configurazione. Se
non utilizzi PreviewView
, devi adattare la logica in modo da applicarla
alla Surface
personalizzata.
Ecco la procedura da seguire per utilizzare PreviewView
:
- Configura un rilevatore dei gesti di ridimensionamento per gestire gli eventi di pizzicatura.
- Ottieni
ZoomState
dall'oggettoCamera.CameraInfo
, dove viene restituita l'istanzaCamera
quando chiamibindToLifecycle()
. - Se
ZoomState
ha un valorezoomRatio
, salvalo come rapporto di zoom corrente. Se non è presentezoomRatio
suZoomState
, utilizza la velocità di zoom predefinita della videocamera (1,0). - Prendi il prodotto del rapporto di zoom corrente con il
scaleFactor
per determinare il nuovo rapporto di zoom e trasmettilo aCameraControl.setZoomRatio()
. - Imposta il rilevatore di gesti per rispondere agli eventi di 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 }
Scatto di una foto
Questa sezione mostra come attivare l'acquisizione di foto, a prescindere dal fatto che sia necessario premere il pulsante di scatto, dopo che è trascorso un timer o in qualsiasi altro evento di tua scelta.
Fotocamera1
In Fotocamera1, devi prima definire un
Camera.PictureCallback
per gestire i dati relativi all'immagine quando vengono richiesti. Ecco un semplice esempio di PictureCallback
per la gestione dei dati di immagini 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) } }
Ogni volta che vuoi scattare una foto, chiama il metodo takePicture()
sull'istanza Camera
. Questo metodo takePicture()
ha tre diversi parametri per tipi di dati diversi. Il primo parametro è per un elemento
ShutterCallback
(non definito in questo esempio). Il secondo parametro serve a PictureCallback
per gestire i dati non elaborati (non compressi) della videocamera. Il terzo parametro è quello utilizzato in questo esempio, poiché è un PictureCallback
per gestire i dati dell'immagine JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: controller della videocamera
CameraController
di CameraX mantiene la semplicità di Camera1 per l'acquisizione di immagini
implementando un proprio metodo takePicture()
. Qui, definisci una funzione 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) } } ) }
FotocameraX: CameraProvider
Scattare una foto con CameraProvider
funziona quasi nello 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)
Poi, ogni volta che vuoi scattare una foto, puoi chiamare
ImageCapture.takePicture()
. Consulta 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 è notevolmente più complicato rispetto agli scenari esaminati finora. Ogni parte del processo deve essere impostata correttamente, solitamente in un ordine particolare. Inoltre, potrebbe essere necessario verificare che il video e l'audio siano sincronizzati o risolvere ulteriori incongruenze con il dispositivo.
Come vedrai, di nuovo CameraX gestisce gran parte di questa complessità al posto tuo.
Fotocamera1
L'acquisizione video con Fotocamera1 richiede un'attenta gestione di Camera
e
MediaRecorder
e i metodi devono
essere richiamati in un ordine particolare. Affinché l'applicazione funzioni correttamente, devi seguire questo ordine:
- Apri la fotocamera.
- Prepara e avvia un'anteprima (se la tua app mostra il video registrato, come avviene di solito).
- Sblocca la fotocamera per utilizzarla entro il giorno
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
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
per impostare la qualità. Consulta la paginaCamcorderProfile
per conoscere tutte le opzioni relative alla qualità. - Chiama il numero
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - Se la tua app dispone di 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 del tuoMediaRecorder
.
- Connetti la tua istanza
- Per avviare la registrazione, chiama il numero
MediaRecorder.start()
. - Per interrompere la registrazione, chiama questi metodi. Segui sempre 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
MediaRecorder
future possano utilizzarla chiamandoCamera.lock()
.
- Chiama il numero
- Per interrompere l'anteprima, chiama
Camera.stopPreview()
. - Infine, per rilasciare
Camera
in modo che altri processi possano utilizzarlo, chiamaCamera.release()
.
Ecco la combinazione di tutti i passaggi:
// 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: controller della videocamera
Con CameraController
di CameraX, puoi attivare/disattivare ImageCapture
,
VideoCapture
e ImageAnalysis
UseCase
in modo indipendente,
a patto che l'elenco di casi d'uso possa essere utilizzato contemporaneamente.
Gli UseCase
ImageCapture
e ImageAnalysis
sono attivi per impostazione predefinita, motivo per cui non hai dovuto chiamare setEnabledUseCases()
per scattare una foto.
Per utilizzare un CameraController
per la registrazione video, devi prima usare
setEnabledUseCases()
per consentire l'UseCase
di VideoCapture
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
Quando vuoi iniziare a registrare un video, puoi chiamare la funzione CameraController.startRecording()
. Questa funzione può salvare il video registrato in un File
, come puoi vedere
nell'esempio riportato di seguito. Inoltre, devi passare un Executor
e una classe che implementi OnVideoSavedCallback
per gestire i callback di successo e di errore. Quando la registrazione dovrebbe terminare, chiama il numero
CameraController.stopRecording()
.
Nota: se utilizzi CameraX 1.3.0-alpha02 o versioni successive, è disponibile un parametro aggiuntivo AudioConfig
che consente di attivare o disattivare la registrazione audio del video. Per attivare
la 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 utilizzato per mettere in pausa,
ripristinare e interrompere la 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() ) }
FotocameraX: CameraProvider
Se utilizzi un CameraProvider
, devi creare un VideoCapture
UseCase
e passare in un oggetto Recorder
. Su Recorder.Builder
puoi
impostare la qualità video e, facoltativamente, un
FallbackStrategy
, che
gestisce i casi in cui un dispositivo non sia in grado di soddisfare le specifiche sulla qualità desiderate. Quindi
associa l'istanza VideoCapture
a CameraProvider
con gli altri
UseCase
.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
A questo punto, è possibile accedere a Recorder
nella proprietà videoCapture.output
. Recorder
può avviare registrazioni video salvate in un File
,
ParcelFileDescriptor
o MediaStore
. In questo esempio viene utilizzato MediaStore
.
Su Recorder
, esistono diversi metodi per prepararla. Richiama prepareRecording()
per impostare le opzioni di output di MediaStore
. Se la tua app è autorizzata a utilizzare il microfono del dispositivo, chiama anche withAudioEnabled()
.
Quindi, chiama start()
per iniziare a registrare, passando in un contesto e un
listener di eventi Consumer<VideoRecordEvent>
per gestire gli eventi di registrazione video. Se l'operazione ha esito positivo, l'elemento Recording
restituito può essere utilizzato per mettere in pausa, riprendere o interrompere la registrazione.
// 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 di questa guida si adattano a un'app Android completa.
Se vuoi ricevere ulteriore assistenza per la migrazione a CameraX o hai domande sulla suite di API Android Camera, contattaci tramite il Gruppo di discussione CameraX.