Controllare la videocamera

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

Nota: questa pagina fa riferimento alla classe Fotocamera, che è stata ritirata. Ti consigliamo di utilizzare FotocameraX o, per casi d'uso specifici, Fotocamera2. Sia CameraX che 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 immagini o video dalle applicazioni di fotocamera esistenti. Tuttavia, se vuoi creare un'applicazione per fotocamera specializzata o qualcosa di completamente integrato nell'interfaccia utente dell'app, questa lezione ti spiegherà come procedere.

Consulta le seguenti risorse correlate:

Apri l'oggetto Fotocamera

La creazione di un'istanza dell'oggetto Camera è il primo passaggio del processo di controllo diretto della videocamera. Come fa 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 perché può richiedere un po' di tempo e bloccare il thread dell'interfaccia utente. In un'implementazione più di base, l'apertura della fotocamera può essere rimandata al metodo onResume() per facilitare il riutilizzo del codice e mantenere semplice il flusso di controllo.

La chiamata a Camera.open() genera un'eccezione se la videocamera è già utilizzata da un'altra applicazione, pertanto viene aggregata in un blocco try.

Kotlin

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

Java

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ù videocamere. Se utilizzi l'API legacy e chiami open() senza un argomento, ottieni la prima fotocamera posteriore.

Crea l'anteprima della fotocamera

Scattare una foto richiede in genere che gli utenti vedano un'anteprima del soggetto prima di fare clic sul pulsante di scatto. A questo scopo, puoi utilizzare un SurfaceView per disegnare anteprime di ciò che il sensore della fotocamera sta rilevando.

Anteprima corso

Per iniziare a visualizzare un'anteprima, è necessaria l'anteprima del corso. L'anteprima richiede un'implementazione dell'interfaccia android.view.SurfaceHolder.Callback, che viene utilizzata per passare i dati delle immagini dall'hardware della fotocamera all'applicazione.

Kotlin

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)
    }
    ...
}

Java

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 di poter avviare l'anteprima dell'immagine live, come mostrato nella sezione successiva.

Impostazione e avvio dell'anteprima

L'istanza della videocamera e la relativa anteprima devono essere create in un ordine specifico, con l'oggetto della videocamera che deve essere il primo. Nello snippet seguente, il processo di inizializzazione della videocamera viene incapsulato in modo che Camera.startPreview() venga chiamato con il metodo setCamera() ogni volta che l'utente fa qualcosa per cambiare la videocamera. L'anteprima deve essere riavviata anche nel metodo di callback della classe di anteprima surfaceChanged().

Kotlin

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()
    }
}

Java

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();
    }
}

Modifica impostazioni 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 molte altre, vedi il codice sorgente dell'applicazione Fotocamera.

Kotlin

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()
    }
}

Java

@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();
}

Imposta l'orientamento dell'anteprima

La maggior parte delle applicazioni della fotocamera blocca la visualizzazione in modalità Orizzontale perché questo è l'orientamento naturale del sensore della fotocamera. Questa impostazione non impedisce di scattare foto in modalità verticale, 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 modalità di registrazione dell'immagine. Tuttavia, in Android precedenti al livello API 14, devi interrompere l'anteprima prima di modificare l'orientamento e riavviarla.

Scatta una foto

Utilizza il metodo Camera.takePicture() per scattare una foto dopo avere avviato l'anteprima. Puoi creare oggetti Camera.PictureCallback e Camera.ShutterCallback e trasferirli in Camera.takePicture().

Se vuoi acquisire immagini continuamente, puoi creare un Camera.PreviewCallback che implementi onPreviewFrame(). Per trovare una via di mezzo, puoi acquisire solo i frame di anteprima selezionati o impostare un'azione ritardata per chiamare takePicture().

Riavvia l'anteprima

Dopo aver scattato una foto, devi riavviare l'anteprima prima che l'utente possa scattare un'altra foto. In questo esempio, il riavvio avviene mediante il sovraccarico del pulsante di scatto.

Kotlin

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()
}

Java

@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

Dopo aver utilizzato la fotocamera, è il momento di pulire l'applicazione. In particolare, devi rilasciare l'oggetto Camera per evitare di causare l'arresto anomalo di altre applicazioni, incluse le nuove istanze della tua applicazione.

Quando devi interrompere l'anteprima e rilasciare la fotocamera? L'eliminazione della superficie di anteprima è un buon indizio che è giunto il momento di interrompere l'anteprima e rilasciare la fotocamera, come mostrato in questi metodi della classe Preview.

Kotlin

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

Java

@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;
    }
}

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