Cómo controlar la cámara

En esta lección, analizaremos cómo controlar el hardware de la cámara directamente con las API del framework.

Para controlar la cámara de un dispositivo, se requiere mucho más código que para solicitar imágenes o videos de las aplicaciones de cámara existentes. Sin embargo, si quieres desarrollar una aplicación de cámara especializada o algo completamente integrado en la IU de tu app, en esta lección, descubrirás cómo hacerlo.

Consulta los siguientes recursos relacionados:

Abre el objeto de la cámara

Obtener una instancia del objeto Camera es el primer paso del proceso para controlar directamente la cámara. Al igual que lo hace la propia aplicación de cámara de Android, la forma recomendada de acceder a la cámara es abrir Camera en un subproceso separado que se inicia desde onCreate(). Este enfoque es una buena idea, ya que puede tardar un tiempo y ralentizar el subproceso de IU. En una implementación más básica, la apertura de la cámara puede diferirse al método onResume() para facilitar la reutilización del código y mantener el flujo de control simple.

Llamar a Camera.open() arroja una excepción si otra aplicación ya está usando la cámara. Por eso, lo unimos a un bloque de 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 partir de la API nivel 9, el framework de la cámara admite varias cámaras. Si usas la API heredada y llamas a open() sin un argumento, obtienes la primera cámara posterior.

Crea la vista previa de la cámara

Tomar una foto suele requerir que los usuarios vean una vista previa de su tema antes de hacer clic en el obturador. Para ello, puedes usar un SurfaceView a fin de obtener vistas previas de lo que capta el sensor de la cámara.

Clase de vista previa

Para empezar a mostrar vistas previas, necesitas una clase de vista previa. La vista previa requiere una implementación de la interfaz android.view.SurfaceHolder.Callback, que se usa para pasar datos de la imagen del hardware de la cámara a la aplicación.

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

Se debe pasar la clase de vista previa al objeto Camera antes de que se pueda iniciar la vista previa de la imagen en vivo, como se muestra en la siguiente sección.

Establece e inicia la vista previa

Una instancia de la cámara y su vista previa relacionada deben crearse en un orden específico; se debe empezar por el objeto de la cámara. En el fragmento incluido a continuación, se encapsula el proceso de inicialización de la cámara para que el método setCamera() llame a Camera.startPreview(), siempre que el usuario haga algo para cambiar la cámara. También debe reiniciarse la vista previa en el método de devolución de llamada surfaceChanged() de la clase de vista previa.

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 la configuración de la cámara

La configuración de la cámara cambia la forma en que la cámara toma fotos, desde el nivel de zoom hasta la compensación de exposición. En este ejemplo, solo se cambia el tamaño de la vista previa. Consulta el código fuente de la aplicación de cámara para conocer muchas más opciones.

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

Establece la orientación de vista previa

La mayoría de las aplicaciones de cámara bloquean la pantalla en modo horizontal porque esa es la orientación natural del sensor de la cámara. Este ajuste no te impide tomar fotos en modo vertical, ya que la orientación del dispositivo se registra en el encabezado EXIF. El método setCameraDisplayOrientation() permite cambiar cómo se muestra la vista previa sin afectar la forma en que se registra la imagen. Sin embargo, en Android, antes de API nivel 14, debes detener la vista previa antes de cambiar la orientación y luego reiniciarla.

Toma una foto

Usa el método Camera.takePicture() para tomar una foto una vez que se inicie la vista previa. Puedes crear objetos Camera.PictureCallback y Camera.ShutterCallback, y pasarlos a Camera.takePicture().

Si deseas tomar imágenes continuamente, puedes crear una Camera.PreviewCallback que implemente onPreviewFrame(). Para realizar una acción intermedia, puedes capturar solo las tomas de vista previa seleccionadas o configurar una acción demorada para llamar a takePicture().

Reinicia la vista previa

Después de tomar una foto, debes reiniciar la vista previa antes de que el usuario pueda hacer otra foto. En este ejemplo, se realiza el reinicio mediante la sobrecarga del botón del obturador.

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

Detén la vista previa y libera la cámara

Una vez que la aplicación termine de usar la cámara, es hora de realizar una limpieza. En particular, debes liberar el objeto Camera o corres el riesgo de que fallen otras aplicaciones, incluidas las instancias nuevas de tu propia app.

¿Cuándo debes detener la vista previa y liberar la cámara? Destruir la superficie de vista previa indica que es hora de detener la vista previa y liberar la cámara, como se muestra en estos métodos de la clase 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;
        }
    }
    

Al comienzo de la lección, este procedimiento también era parte del método setCamera(), por lo que el proceso de inicializar una cámara siempre comienza con la detención de la vista previa.