Controllare la videocamera

In questa lezione parleremo di come controllare l'hardware della videocamera direttamente utilizzando le API del framework.

Nota:questa pagina fa riferimento alla classe Camera, che è stata ritirata. Ti consigliamo di utilizzare CameraX o, per casi d'uso specifici, Camera2. Sia CameraX sia Camera2 supportano Android 5.0 (livello API 21) e versioni successive.

Il controllo diretto della fotocamera di un dispositivo richiede molto più codice rispetto alla richiesta di foto o video dalle applicazioni per fotocamere esistenti. Tuttavia, se vuoi creare un'applicazione per videocamere specializzata o qualcosa di completamente integrato nell'interfaccia utente della tua app, questa lezione ti mostra come.

Consulta le seguenti risorse correlate:

Apri l'oggetto Videocamera

L'ottenimento di un'istanza dell'oggetto Camera è il primo passaggio della procedura di controllo diretto della videocamera. Come per l'applicazione Fotocamera di Android, il modo consigliato per accedere alla fotocamera è aprire Camera in un thread separato avviato da onCreate(). Questo approccio è una buona idea poiché può richiedere del tempo e potrebbe rallentare il thread dell'interfaccia utente. In un'implementazione più di base, l'apertura della fotocamera può essere rimandata al metodo onResume() per semplificare il riutilizzo del codice e mantenere semplice il flusso di controllo.

La chiamata a Camera.open() genera un'eccezione se la videocamera è già in uso da un'altra applicazione, quindi la racchiudiamo in un blocco try.

KotlinJava
private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}
private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

A partire dal livello API 9, il framework della fotocamera supporta più fotocamere. Se utilizzi l'API precedente e chiami open() senza un argomento, viene visualizzata la prima fotocamera posteriore.

Creare l'anteprima della fotocamera

Di solito, per scattare una foto è necessario che gli utenti vedano un'anteprima del soggetto prima di fare clic sul pulsante di scatto. A tale scopo, puoi utilizzare un SurfaceView per disegnare le anteprime di ciò che viene rilevato dal sensore della fotocamera.

Classe di anteprima

Per iniziare a visualizzare un'anteprima, devi avere la classe di anteprima. La preview richiede un'implementazione dell'interfaccia android.view.SurfaceHolder.Callback, che viene utilizzata per trasmettere i dati delle immagini dall'hardware della fotocamera all'applicazione.

KotlinJava
class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}
class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

La classe di anteprima deve essere passata all'oggetto Camera prima che sia possibile avviare l'anteprima dinamica delle immagini, come mostrato nella sezione successiva.

Impostare e avviare l'anteprima

Un'istanza della videocamera e la relativa anteprima devono essere create in un ordine specifico, con l'oggetto della videocamera in primo piano. Nello snippet riportato di seguito, il processo di inizializzazione della videocamera è incapsulato in modo che Camera.startPreview() venga chiamato dal metodo setCamera() ogni volta che l'utente esegue un'azione per modificare la videocamera. L'anteprima deve essere riavviata anche nel metodo di callback surfaceChanged() della classe di anteprima.

KotlinJava
fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        startPreview()
    }
}
public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

Modificare le impostazioni della fotocamera

Le impostazioni della fotocamera cambiano il modo in cui la fotocamera scatta le foto, dal livello di zoom alla compensazione dell'esposizione. Questo esempio modifica solo le dimensioni dell'anteprima. Per altri esempi, consulta il codice sorgente dell'applicazione Fotocamera.

KotlinJava
override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

Impostare l'orientamento dell'anteprima

La maggior parte delle applicazioni della fotocamera blocca il display in modalità Orizzontale perché è l'orientamento naturale del sensore della fotocamera. Questa impostazione non impedisce di scattare foto in modalità Ritratto, perché l'orientamento del dispositivo viene registrato nell'intestazione EXIF. Il metodo setCameraDisplayOrientation() ti consente di modificare la modalità di visualizzazione dell'anteprima senza influire sulla registrazione dell'immagine. Tuttavia, in Android precedente al livello API 14, devi interrompere l'anteprima prima di cambiare l'orientamento e poi riavviarla.

Scatta una foto

Utilizza il metodo Camera.takePicture() per scattare una foto dopo l'avvio dell'anteprima. Puoi creare oggetti Camera.PictureCallback e Camera.ShutterCallback e passarli a Camera.takePicture().

Se vuoi acquisire immagini continuamente, puoi creare un Camera.PreviewCallback che implementi onPreviewFrame(). Per una situazione intermedia, puoi acquisire solo frame di anteprima selezionati o configurare un'azione ritardata per chiamare takePicture().

Riavviare l'anteprima

Dopo aver scattato una foto, devi riavviare l'anteprima prima che l'utente possa scattarne un'altra. In questo esempio, il riavvio viene eseguito sovraccaricando il pulsante di scatto.

KotlinJava
fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}
@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

Interrompi l'anteprima e rilascia la fotocamera

Una volta che l'applicazione ha finito di utilizzare la videocamera, è il momento di eseguire le operazioni di pulizia. In particolare, devi rilasciare l'oggetto Camera, altrimenti rischi di arrestare in modo anomalo altre applicazioni, incluse nuove istanze della tua applicazione.

Quando dovresti interrompere l'anteprima e rilasciare la fotocamera? Beh, se la superficie di anteprima viene distrutta, è un buon indizio che è arrivato il momento di interrompere l'anteprima e rilasciare la fotocamera, come mostrato in questi metodi della classe Preview.

KotlinJava
override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

All'inizio della lezione, questa procedura faceva parte anche del metodo setCamera(), quindi l'inizializzazione di una videocamera inizia sempre con l'interruzione dell'anteprima.