API de Camera

El framework de Android admite diversas cámaras y características de cámara disponibles en los dispositivos. Esto permite capturar fotos y videos en las aplicaciones. En este documento, se brinda un enfoque rápido y simple sobre la captura de fotos y videos, y se proporciona un enfoque avanzado sobre la creación de experiencias de cámara personalizadas para los usuarios.

Nota: En esta página, se describe la clase Camera, que quedó obsoleta. Recomendamos usar la biblioteca CameraX de Jetpack o, en casos de uso específicos, la clase camera2. Tanto CameraX como Camera2 funcionan en Android 5.0 (nivel de API 21) y versiones posteriores.

Consideraciones

Antes de habilitar tu aplicación para el uso de cámaras en dispositivos Android, debes considerar algunas preguntas sobre la forma en que tu app pretende usar esta función de hardware.

  • Requisito de cámara: ¿es el uso de la cámara tan importante para tu aplicación que no deseas que se instale en un dispositivo que no tenga una cámara? De ser así, debes declarar el requisito de cámara en el manifiesto.
  • Foto rápida o Cámara personalizada: ¿de qué manera tu aplicación utilizará la cámara? ¿Solo deseas tomar una foto rápida o un videoclip? ¿O tu aplicación presentará una nueva forma de usar las cámaras? Para tomar una foto rápida o un clip, revisa Cómo usar las aplicaciones de cámara existentes. Para desarrollar una función de cámara personalizada, consulta la sección Cómo compilar una app de cámara.
  • Requisito de servicios en primer plano: ¿cuándo interactúa tu app con la cámara? En Android 9 (nivel de API 28) y versiones posteriores, las apps que se ejecutan en segundo plano no pueden acceder a la cámara. Por lo tanto, debes usar la cámara cuando tu app se encuentre en primer plano o como parte de un servicio en primer plano.
  • Almacenamiento: ¿las imágenes o los videos que genera la aplicación deben ser visibles únicamente para tu aplicación o pueden compartirse a fin de que otras aplicaciones, como Galería u otras apps de medios o de redes sociales, puedan usarlos? ¿Deseas que las fotos y los videos estén disponibles aunque se desinstale tu aplicación? Consulta la sección Cómo guardar archivos de medios para ver la forma de implementar estas opciones.

Conceptos básicos

El framework de Android admite la captura de imágenes y videos a través de la API de android.hardware.camera2 o la cámara Intent. Estas son las clases relevantes:

android.hardware.camera2
Este paquete es la API principal para controlar las cámaras de los dispositivos. Se puede utilizar para tomar fotos y videos al compilar una aplicación de cámara.
Camera
Esta clase es la API obsoleta anterior para controlar las cámaras de los dispositivos.
SurfaceView
Esta clase se utiliza para presentar una vista previa de la cámara en vivo al usuario.
MediaRecorder
Esta clase se usa para grabar video de la cámara.
Intent
Se puede usar un tipo de acción de intent de MediaStore.ACTION_IMAGE_CAPTURE o MediaStore.ACTION_VIDEO_CAPTURE para capturar imágenes o videos sin usar directamente el objeto Camera.

Declaraciones del manifiesto

Antes de iniciar el desarrollo de una aplicación con la API de Camera, debes comprobar que el manifiesto contenga las declaraciones adecuadas para permitir el uso del hardware de cámara y otras características relacionadas.

  • Permiso de cámara: tu aplicación debe solicitar permiso para usar una cámara del dispositivo.
    <uses-permission android:name="android.permission.CAMERA" />
    

    Nota: Si se invoca una app de cámara existente para usar la cámara, tu aplicación no necesita solicitar este permiso.

  • Funciones de cámara: Tu aplicación también debe declarar el uso de funciones de cámara, por ejemplo:
    <uses-feature android:name="android.hardware.camera" />
    
    .

    Para ver una lista de las funciones de cámara, consulta el manifiesto Referencia de las funciones.

    Al agregar funciones de cámara al manifiesto, Google Play impide que tu aplicación se instale en dispositivos que no incluyen una cámara o no admiten las funciones de cámara especificadas. Para obtener más información sobre el uso de filtros basados en funciones con Google Play, consulta Google Play y filtrado por funciones.

    Si tu aplicación puede usar una cámara o una función de cámara para su correcto funcionamiento, pero no la requiere, debes especificar esto en el manifiesto. Para ello, incluye el atributo android:required y configúralo en false:

    <uses-feature android:name="android.hardware.camera" android:required="false" />
    
  • Permiso de almacenamiento: Tu aplicación puede guardar imágenes o videos en el almacenamiento externo del dispositivo (tarjeta SD) si se orienta a Android 10 (nivel de API 29) o versiones anteriores y especifica lo siguiente en el manifiesto.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  • Permiso de grabación de audio: tu aplicación debe solicitar el permiso de captura de audio para grabar audio con captura de video.
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  • Permiso de ubicación: si tu aplicación etiqueta las imágenes con información de ubicación de GPS, debes solicitar el permiso ACCESS_FINE_LOCATION. Ten en cuenta que, si tu app se orienta a Android 5.0 (nivel de API 21) o versiones posteriores, también debes declarar que la app utiliza el GPS del dispositivo:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />
    

    Para obtener más información sobre la obtención de la ubicación del usuario, consulta Estrategias de ubicación.

Cómo usar las apps de cámara existentes

Una forma rápida de habilitar la captura de fotos o videos en una aplicación sin implementar demasiado código adicional es usar un objeto Intent para invocar una aplicación de cámara Android existente. Los detalles se describen en las lecciones de capacitación Cómo tomar fotos fácilmente y Cómo grabar videos fácilmente.

Cómo compilar una app de cámara

Algunos desarrolladores pueden necesitar una interfaz de usuario de cámara personalizada según el aspecto de su aplicación o con funciones especiales. La escritura de un código de captura de fotos propio puede brindar una experiencia más atractiva a los usuarios.

Nota: La siguiente guía es para la API anterior de Camera, ya obsoleta. Para las aplicaciones de cámara nuevas o avanzadas, se recomienda usar la API de android.hardware.camera2 más reciente.

Los pasos generales para crear una interfaz de cámara personalizada de la aplicación son los siguientes:

  • Detectar la cámara y obtener acceso: crea el código para comprobar la existencia de cámaras y solicitar acceso.
  • Crear una clase de vista previa: crea una clase de vista previa de la cámara que extienda SurfaceView e implemente la interfaz SurfaceHolder. Esta clase brinda una vista previa de las imágenes en vivo de la cámara.
  • Crear un diseño de vista previa: después de obtener la clase de vista previa de la cámara, crea un diseño de vista donde se incorporen la vista previa y los controles de interfaz de usuario que desees.
  • Configurar receptores para la captura: conecta receptores para que los controles de la interfaz inicien la captura de imágenes o videos en respuesta a acciones de los usuarios, como presionar un botón.
  • Capturar y guardar archivos: configura el código para capturar fotos o videos y guardar la salida.
  • Liberar la cámara: después de usar la cámara, la aplicación debe liberarla adecuadamente para que la usen otras aplicaciones.

El hardware de cámara es un recurso compartido que se debe administrar cuidadosamente para que la aplicación no entre en conflicto con otras aplicaciones que también deseen usarlo. En las siguientes secciones, se analiza cómo detectar el hardware de cámara, solicitar acceso a una cámara, capturar fotos o videos, y liberar la cámara cuando la aplicación termina de usarla.

Precaución: Recuerda liberar el objeto Camera mediante una llamada a Camera.release() cuando tu aplicación termine de usarlo. Si tu aplicación no libera adecuadamente la cámara, los intentos subsiguientes de acceder a la cámara, incluidos los de la propia aplicación, fallarán y tanto tu aplicación como otras podrían cerrarse.

Cómo detectar el hardware de cámara

Si tu aplicación no requiere específicamente una cámara mediante una declaración en el manifiesto, debes comprobar si existe una cámara disponible durante el tiempo de ejecución. Para realizar esta comprobación, utiliza el método PackageManager.hasSystemFeature(), como se muestra en el siguiente código de ejemplo:

Kotlin

/** Check if this device has a camera */
private fun checkCameraHardware(context: Context): Boolean {
    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
        // this device has a camera
        return true
    } else {
        // no camera on this device
        return false
    }
}

Java

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

Los dispositivos Android pueden tener varias cámaras, por ejemplo, una posterior para fotografías y una frontal para videollamadas. Android 2.3 (nivel de API 9) y las versiones posteriores permiten comprobar la cantidad de cámaras disponibles en un dispositivo mediante el método Camera.getNumberOfCameras().

Cómo acceder a las cámaras

Si determinaste que el dispositivo en el que se ejecuta tu aplicación tiene una cámara, debes solicitar acceso a ella. Para ello, obtén una instancia de Camera (a menos que uses un intent para acceder a la cámara).

Para acceder a la cámara principal, utiliza el método Camera.open() y asegúrate de captar todas las excepciones, como se muestra en el código a continuación:

Kotlin

/** A safe way to get an instance of the Camera object. */
fun getCameraInstance(): Camera? {
    return try {
        Camera.open() // attempt to get a Camera instance
    } catch (e: Exception) {
        // Camera is not available (in use or does not exist)
        null // returns null if camera is unavailable
    }
}

Java

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

Precaución: Cuando uses Camera.open(), busca siempre excepciones. Si no buscas las excepciones cuando la cámara se encuentra en uso o no existe, el sistema cerrará la aplicación.

En los dispositivos que ejecutan Android 2.3 (nivel de API 9) o una versión posterior, puedes acceder a cámaras específicas mediante Camera.open(int). El código de ejemplo anterior accederá a la primera cámara posterior de un dispositivo con más de una cámara.

Cómo comprobar las funciones de cámara

Después de acceder a una cámara, puedes obtener más información sobre sus capacidades utilizando el método Camera.getParameters() y revisando el objeto Camera.Parameters mostrado para ver las capacidades compatibles. Si usas el nivel de API 9 o superior, utiliza Camera.getCameraInfo() para determinar si una cámara se encuentra en la parte frontal o posterior del dispositivo, y la orientación de la imagen.

Cómo crear una clase de vista previa

Para que los usuarios tomen fotos o videos con eficacia, deben poder ver lo que ve la cámara del dispositivo. Una clase de vista previa de la cámara es un elemento SurfaceView que puede mostrar los datos de imagen en vivo procedentes de una cámara para que los usuarios puedan encuadrar y capturar una foto o un video.

En el siguiente código de ejemplo, se muestra la forma de crear una clase de vista previa de la cámara básica que se puede incluir en un diseño de View. Esta clase implementa SurfaceHolder.Callback para capturar los eventos de devolución de llamada destinados a crear y destruir la vista, lo que se necesita para asignar la entrada de vista previa de la cámara.

Kotlin

/** A basic Camera preview class */
class CameraPreview(
        context: Context,
        private val mCamera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val mHolder: SurfaceHolder = holder.apply {
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        addCallback(this@CameraPreview)
        // deprecated setting, but required on Android versions prior to 3.0
        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.
        mCamera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "Error setting camera preview: ${e.message}")
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // empty. 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 (mHolder.surface == null) {
            // preview surface does not exist
            return
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview()
        } catch (e: Exception) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        mCamera.apply {
            try {
                setPreviewDisplay(mHolder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "Error starting camera preview: ${e.message}")
            }
        }
    }
}

Java

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 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 (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

Si deseas establecer un tamaño específico para la vista previa de la cámara, establece esto en el método surfaceChanged() como se indica en los comentarios anteriores. Al establecer el tamaño de vista previa, debes usar valores de getSupportedPreviewSizes(). No establezcas valores arbitrarios en el método setPreviewSize().

Nota: Debido a la introducción de la función Multiventana en Android 7.0 (nivel de API 24) y versiones posteriores, ya no se puede asumir la relación de aspecto de la vista previa es la misma que la de tu actividad incluso después de llamar a setDisplayOrientation(). Según el tamaño de la ventana y la relación de aspecto, es posible que debas ajustar una vista previa de la cámara amplia a un diseño vertical, o viceversa, mediante un diseño letterbox (formato 16:9).

Cómo colocar la vista previa en un diseño

Para tomar una foto o un video, se debe colocar una clase de vista previa de la cámara, como la del ejemplo de la sección anterior, en el diseño de una actividad junto con los otros controles de interfaz de usuario. En esta sección, se muestra la forma de crear un diseño básico y la actividad para la vista previa.

El siguiente código de diseño brinda una vista sumamente básica que se puede utilizar para mostrar una vista previa de la cámara. En este ejemplo, el elemento FrameLayout pretende ser el contenedor de la clase de vista previa de la cámara. Este tipo de diseño se utiliza para poder superponer información o controles de foto adicionales en las imágenes de vista previa de la cámara en vivo.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>

En la mayoría de los dispositivos, la orientación predeterminada de la vista previa de la cámara es horizontal. Este diseño de ejemplo especifica un diseño horizontal y el código debajo corrige la orientación de la aplicación a horizontal. Para simplificar la representación de una vista previa de la cámara, debes agregar lo siguiente a tu manifiesto a fin de cambiar la orientación de la actividad de vista previa de tu aplicación a horizontal.

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Nota: Una vista previa de la cámara no necesita estar en modo horizontal. A partir de Android 2.2 (nivel de API 8), puedes utilizar el método setDisplayOrientation() para establecer la rotación de la imagen de vista previa. Para cambiar la orientación de la vista previa cuando el usuario gira el teléfono, dentro del método surfaceChanged() de la clase de vista previa, debes detener la vista previa con Camera.stopPreview(), cambiar la orientación y volver a iniciar la vista previa con Camera.startPreview().

En la actividad de la vista de cámara, agrega la clase de vista previa al elemento FrameLayout que se muestra en el ejemplo anterior. La actividad de la cámara también debe garantizar la liberación de la cámara cuando esta se cierra o se pausa. En el siguiente ejemplo, se muestra la forma de modificar la actividad de una cámara para adjuntar la clase de vista previa mostrada en Cómo crear una clase de vista previa.

Kotlin

class CameraActivity : Activity() {

    private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create an instance of Camera
        mCamera = getCameraInstance()

        mPreview = mCamera?.let {
            // Create our Preview view
            CameraPreview(this, it)
        }

        // Set the Preview view as the content of our activity.
        mPreview?.also {
            val preview: FrameLayout = findViewById(R.id.camera_preview)
            preview.addView(it)
        }
    }
}

Java

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }
}

Nota: El método getCameraInstance() del ejemplo anterior hace referencia al método de ejemplo que se muestra en Cómo acceder a las cámaras.

Cómo capturar fotos

Después de crear una clase de vista previa y un diseño de vista para mostrarla, estás listo para comenzar a capturar imágenes con la aplicación. En el código de la aplicación, debes configurar receptores para que los controles de la interfaz de usuario tomen una foto en respuesta a una acción del usuario.

Para recuperar una foto, utiliza el método Camera.takePicture(). Este método toma tres parámetros que reciben datos de la cámara. Para recibir los datos en formato JPEG, debes implementar una interfaz Camera.PictureCallback a fin de recibir los datos de imagen y escribirlos en un archivo. En el siguiente código, se muestra una implementación básica de la interfaz Camera.PictureCallback para guardar una imagen recibida de la cámara.

Kotlin

private val mPicture = 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.message}")
    } catch (e: IOException) {
        Log.d(TAG, "Error accessing file: ${e.message}")
    }
}

Java

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};

Llama al método Camera.takePicture() para activar la captura de una imagen. En el siguiente código de ejemplo, se muestra la forma de llamar a este método desde un botón View.OnClickListener.

Kotlin

val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    // get an image from the camera
    mCamera?.takePicture(null, null, picture)
}

Java

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an image from the camera
            mCamera.takePicture(null, null, picture);
        }
    }
);

Nota: El miembro mPicture del siguiente ejemplo hace referencia al código de ejemplo anterior.

Precaución: Recuerda liberar el objeto Camera mediante una llamada a Camera.release() cuando tu aplicación termine de usarlo. Para obtener información sobre la forma de liberar la cámara, consulta Cómo liberar la cámara.

Cómo capturar videos

La captura de videos mediante el framework de Android requiere una administración atenta del objeto Camera y una coordinación con la clase MediaRecorder. Cuando grabas videos con Camera, debes administrar las llamadas Camera.lock() y Camera.unlock() para permitir el acceso de MediaRecorder al hardware de cámara, además de las llamadas Camera.open() y Camera.release().

Nota: A partir de Android 4.0 (nivel de API 14), las llamadas Camera.lock() y Camera.unlock() se administran de automáticamente.

A diferencia de la toma de fotos con la cámara de un dispositivo, la captura de video requiere un orden de llamadas especial. Es necesario seguir un orden específico de ejecución para prepararse correctamente y capturar el video con la aplicación, tal como se detalla a continuación.

  1. Abrir la cámara: utiliza Camera.open() para obtener una instancia del objeto de cámara.
  2. Conectar la vista previa: Conecta un SurfaceView a la cámara mediante Camera.setPreviewDisplay() para preparar una vista previa de imagen de la cámara en vivo.
  3. Iniciar la vista previa: llama a Camera.startPreview() para comenzar a mostrar las imágenes de la cámara en vivo.
  4. Iniciar la grabación de video: Se deben completar los siguientes pasos para grabar correctamente un video:
    1. Desbloquear la cámara: desbloquea la cámara para que la use MediaRecorder mediante una llamada a Camera.unlock().
    2. Configurar MediaRecorder: llama a los siguientes métodos MediaRecorder en este orden. Para obtener más información, consulta la documentación de referencia de MediaRecorder.
      1. setCamera(): configura la cámara para usarla en la captura de video; utiliza la instancia actual de Camera de la aplicación.
      2. setAudioSource(): configura la fuente de audio; utiliza MediaRecorder.AudioSource.CAMCORDER.
      3. setVideoSource(): configura la fuente de video; utiliza MediaRecorder.VideoSource.CAMERA.
      4. Establece el formato de salida de video y la codificación. Para Android 2.2 (nivel de API 8) y versiones posteriores, utiliza el método MediaRecorder.setProfile y obtén una instancia de perfil mediante CamcorderProfile.get(). Para las versiones de Android anteriores a la 2.2, debes establecer los parámetros de formato de salida de video y codificación:
        1. setOutputFormat(): establece el formato de salida; especifica el valor predeterminado o MediaRecorder.OutputFormat.MPEG_4.
        2. setAudioEncoder(): establece el tipo de codificación de sonido; especifica el valor predeterminado o MediaRecorder.AudioEncoder.AMR_NB.
        3. setVideoEncoder(): establece el tipo de codificación de video; especifica el valor predeterminado o MediaRecorder.VideoEncoder.MPEG_4_SP.
      5. setOutputFile(): establece el archivo de salida; utiliza getOutputMediaFile(MEDIA_TYPE_VIDEO).toString() del método de ejemplo en la sección Cómo guardar archivos de medios.
      6. setPreviewDisplay(): especifica el elemento de diseño de vista previa SurfaceView para la aplicación. Utiliza el mismo objeto especificado para Conectar la vista previa.

      Precaución: Debes llamar a estos métodos de configuración MediaRecorder en este orden; de lo contrario, tu aplicación experimentará errores y fallará la grabación.

    3. Preparar MediaRecorder: prepara MediaRecorder con los valores de configuración proporcionados mediante una llamada a MediaRecorder.prepare().
    4. Iniciar MediaRecorder: inicia la grabación de video mediante una llamada a MediaRecorder.start().
  5. Detener la grabación de video: llama a los siguientes métodos en orden para completar correctamente una grabación de video:
    1. Detener MediaRecorder: detén la grabación de video mediante una llamada a MediaRecorder.stop().
    2. Restablecer MediaRecorder: de forma opcional, puedes quitar los valores de configuración del grabador mediante una llamada a MediaRecorder.reset().
    3. Liberar MediaRecorder: libera MediaRecorder mediante una llamada a MediaRecorder.release().
    4. Bloquear la cámara: bloquea la cámara para que las sesiones futuras de MediaRecorder puedan usarla mediante una llamada a Camera.lock(). A partir de Android 4.0 (nivel de API 14), no se requiere esta llamada, a menos que la llamada a MediaRecorder.prepare() falle.
  6. Detener la vista previa: cuando hayas terminado de usar la cámara, detén la vista previa mediante Camera.stopPreview().
  7. Liberar la cámara: libera la cámara para que otras aplicaciones puedan usarla mediante una llamada a Camera.release().

Nota: Es posible usar MediaRecorder sin crear una vista previa de la cámara antes y omitir los primeros pasos de este proceso. Sin embargo, como los usuarios generalmente prefieren obtener una vista previa antes de iniciar una grabación, ese proceso no se describe aquí.

Sugerencia: Si tu aplicación se usa principalmente para grabar video, establece setRecordingHint(boolean) en true antes de iniciar la vista previa. Esta configuración ayuda a reducir el tiempo que se requiere para iniciar la grabación.

Cómo configurar MediaRecorder

Al usar la clase MediaRecorder para grabar video, primero es necesario realizar los pasos de configuración en un orden específico y, posteriormente, llamar al método MediaRecorder.prepare() para comprobar e implementar la configuración. En el siguiente código de ejemplo, se muestra la forma de configurar correctamente y preparar la clase MediaRecorder para la grabación de video.

Kotlin

private fun prepareVideoRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    mCamera?.let { camera ->
        // Step 1: Unlock and set camera to MediaRecorder
        camera?.unlock()

        mediaRecorder?.run {
            setCamera(camera)

            // Step 2: Set sources
            setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
            setVideoSource(MediaRecorder.VideoSource.CAMERA)

            // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
            setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

            // Step 4: Set output file
            setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

            // Step 5: Set the preview output
            setPreviewDisplay(mPreview?.holder?.surface)

            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
            setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)


            // Step 6: Prepare configured MediaRecorder
            return try {
                prepare()
                true
            } catch (e: IllegalStateException) {
                Log.d(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            } catch (e: IOException) {
                Log.d(TAG, "IOException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            }
        }

    }
    return false
}

Java

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

Antes de Android 2.2 (nivel de API 8), debes establecer los parámetros de formato de salida de video y codificación directamente, en lugar de usar CamcorderProfile. Este enfoque se muestra en el siguiente código:

Kotlin

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder?.apply {
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    }

Java

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

Los siguientes parámetros de grabación de video para MediaRecorder reciben valores de configuración predeterminados; sin embargo, se recomienda ajustar estos valores según la aplicación:

Cómo iniciar y detener MediaRecorder

Al iniciar y detener la grabación de video mediante la clase MediaRecorder, debes seguir el orden específico que se indica a continuación.

  1. Desbloquear la cámara con Camera.unlock()
  2. Configurar MediaRecorder como se muestra en el ejemplo de código anterior
  3. Iniciar la grabación mediante MediaRecorder.start()
  4. Grabar el video
  5. Detener la grabación mediante MediaRecorder.stop()
  6. Liberar el grabador de medios con MediaRecorder.release()
  7. Bloquear la cámara mediante Camera.lock()

En el siguiente código de ejemplo, se muestra la forma de conectar un botón para iniciar y detener correctamente la grabación de video mediante la cámara y la clase MediaRecorder.

Nota: Al completar una grabación de video, no liberes la cámara; si lo haces, se detendrá la vista previa.

Kotlin

var isRecording = false
val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    if (isRecording) {
        // stop recording and release camera
        mediaRecorder?.stop() // stop the recording
        releaseMediaRecorder() // release the MediaRecorder object
        mCamera?.lock() // take camera access back from MediaRecorder

        // inform the user that recording has stopped
        setCaptureButtonText("Capture")
        isRecording = false
    } else {
        // initialize video camera
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared,
            // now you can start recording
            mediaRecorder?.start()

            // inform the user that recording has started
            setCaptureButtonText("Stop")
            isRecording = true
        } else {
            // prepare didn't work, release the camera
            releaseMediaRecorder()
            // inform user
        }
    }
}

Java

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

Nota: En el ejemplo anterior, el método prepareVideoRecorder() hace referencia al código de ejemplo que se muestra en Cómo configurar MediaRecorder. Este método se ocupa de bloquear la cámara, realizar la configuración y preparar la instancia de MediaRecorder.

Cómo liberar la cámara

Las cámaras son un recurso que comparten las aplicaciones de un dispositivo. La aplicación puede hacer uso de la cámara después de obtener una instancia de Camera; es necesario prestar especial atención a liberar el objeto de cámara cuando la aplicación deja de usarlo y tan pronto como la aplicación se pone en pausa (Activity.onPause()). Si tu aplicación no libera adecuadamente la cámara, los intentos subsiguientes de acceder a la cámara, incluidos los de la propia aplicación, fallarán y tanto tu aplicación como otras podrían cerrarse.

Para liberar una instancia del objeto Camera, utiliza el método Camera.release(), tal como se muestra en el código de ejemplo anterior.

Kotlin

class CameraActivity : Activity() {
    private var mCamera: Camera?
    private var preview: SurfaceView?
    private var mediaRecorder: MediaRecorder?

    override fun onPause() {
        super.onPause()
        releaseMediaRecorder() // if you are using MediaRecorder, release it first
        releaseCamera() // release the camera immediately on pause event
    }

    private fun releaseMediaRecorder() {
        mediaRecorder?.reset() // clear recorder configuration
        mediaRecorder?.release() // release the recorder object
        mediaRecorder = null
        mCamera?.lock() // lock camera for later use
    }

    private fun releaseCamera() {
        mCamera?.release() // release the camera for other applications
        mCamera = null
    }
}

Java

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView preview;
    private MediaRecorder mediaRecorder;

    ...

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();   // clear recorder configuration
            mediaRecorder.release(); // release the recorder object
            mediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}

Precaución: Si tu aplicación no libera adecuadamente la cámara, los intentos subsiguientes de acceder a la cámara, incluidos los de la propia aplicación, fallarán y tanto tu aplicación como otras podrían cerrarse.

Cómo guardar archivos de medios

Los archivos de medios que crean los usuarios, como fotos y videos, deben guardarse en el directorio de almacenamiento externo de un dispositivo (tarjeta SD) para ahorrar espacio del sistema y permitir que los usuarios accedan a estos archivos sin su dispositivo. Existen numerosas ubicaciones de directorio posibles para guardar los archivos de medios en un dispositivo. Sin embargo, son solo dos las ubicaciones estándar que debes considerar como desarrollador:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES): este método muestra la ubicación estándar, compartida y recomendada para guardar fotos y videos. Este directorio es compartido (público), por lo que las aplicaciones pueden detectar, leer, modificar y borrar de forma fácil los archivos guardados en esta ubicación. Si el usuario desinstala la aplicación, no se borran los archivos de medios guardados en esta ubicación. A fin de evitar interferir en las fotos y los videos existentes de los usuarios, debes crear un subdirectorio para los archivos de medios de la aplicación dentro de este directorio, como se muestra en el ejemplo de código anterior. Este método se encuentra disponible en Android 2.2 (nivel de API 8); para ver las llamadas equivalentes en versiones de API anteriores, consulta Guardar archivos compartidos.
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES): este método muestra una ubicación estándar para guardar las fotos y los videos asociados con la aplicación. Si se desinstala la aplicación, se quitan todos los archivos guardados en esta ubicación. No se aplican medidas de seguridad sobre los archivos de esta ubicación, por lo que otras aplicaciones pueden leerlos, modificarlos y borrarlos.

En el siguiente código de ejemplo, se muestra la forma de crear una ubicación File o Uri para un archivo de medios que se puede usar al invocar la cámara de un dispositivo con un objeto Intent o al compilar una app de cámara.

Kotlin

val MEDIA_TYPE_IMAGE = 1
val MEDIA_TYPE_VIDEO = 2

/** Create a file Uri for saving an image or video */
private fun getOutputMediaFileUri(type: Int): Uri {
    return Uri.fromFile(getOutputMediaFile(type))
}

/** Create a File for saving an image or video */
private fun getOutputMediaFile(type: Int): File? {
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    val mediaStorageDir = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp"
    )
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    mediaStorageDir.apply {
        if (!exists()) {
            if (!mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory")
                return null
            }
        }
    }

    // Create a media file name
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return when (type) {
        MEDIA_TYPE_IMAGE -> {
            File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")
        }
        MEDIA_TYPE_VIDEO -> {
            File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")
        }
        else -> null
    }
}

Java

public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
      return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES), "MyCameraApp");
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "IMG_"+ timeStamp + ".jpg");
    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "VID_"+ timeStamp + ".mp4");
    } else {
        return null;
    }

    return mediaFile;
}

Nota: Environment.getExternalStoragePublicDirectory() está disponible en Android 2.2 (nivel de API 8) o versiones posteriores. Si deseas apuntar a dispositivos con versiones anteriores de Android, utiliza Environment.getExternalStorageDirectory() en su lugar. Para obtener más información, consulta Cómo guardar archivos compartidos.

Para lograr que el URI admita perfiles de trabajo, primero debes convertir el URI de archivo en un URI de contenido. Luego, debes agregar el URI de contenido a EXTRA_OUTPUT en un Intent.

Para obtener más información sobre la forma de guardar archivos en un dispositivo Android, consulta Almacenamiento de datos.

Funciones de cámara

Android admite una amplia gama de funciones de cámara que puedes controlar con tu aplicación de cámara, como el formato de foto, el modo de flash, los ajustes de enfoque y muchas más. En esta sección, se enumeran las funciones de cámara más comunes y se describe brevemente la forma de usarlas. El acceso y la configuración de la mayoría de las funciones de cámara se pueden lograr mediante el objeto Camera.Parameters. Sin embargo, existen varias funciones importantes para las que se necesita más que una simple configuración en Camera.Parameters. Estas funciones se abordan en las siguientes secciones:

Para obtener información general sobre la forma de usar las funciones controladas mediante Camera.Parameters, revisa la sección Cómo usar funciones de la cámara. Para obtener información detallada sobre la forma de usar las funciones controladas mediante el objeto de parámetro de cámara, sigue los vínculos a la documentación de referencia de la API en la lista de funciones que aparece a continuación.

Tabla 1: Funciones de cámara comunes ordenadas según el nivel de API de Android en el que se presentaron.

Función Nivel de API Descripción
Detección de rostro 14 Identificar rostros humanos dentro de una foto y usarlos para enfoque, medición y balance de blancos
Áreas de medición 14 Especificar una o varias áreas dentro de una imagen para calcular el balance de blancos
Áreas de interés 14 Establecer una o varias áreas dentro de una imagen para usar como enfoque
White Balance Lock 14 Detener o iniciar los ajustes automáticos de balance de blancos
Exposure Lock 14 Detener o iniciar los ajustes automáticos de exposición
Video Snapshot 14 Tomar una foto mientras se graba un video (captura de fotograma)
Video en time lapse 11 Grabar fotogramas con intervalos de retraso para registrar un video en time lapse
Multiple Cameras 9 Admitir más de una cámara en un dispositivo, incluidas las cámaras frontales y las posteriores
Focus Distance 9 Informar distancias entre la cámara y los objetos que parecen estar en foco
Zoom 8 Establecer la ampliación de la imagen
Exposure Compensation 8 Aumentar o disminuir el nivel de exposición a la luz
GPS Data 5 Incluir u omitir datos de ubicación geográfica en la imagen
White Balance 5 Establecer el modo de balance de blancos, lo que afecta los valores de color en la imagen capturada
Focus Mode 5 Establecer el modo en que la cámara se enfoca en un objeto, por ejemplo, automático, fijo, macro o infinito
Scene Mode 5 Aplicar un modo preestablecido para tipos específicos de situaciones de fotografía, como escenas nocturnas, en la playa, en la nieve o a la luz de las velas
JPEG Quality 5 Establecer el nivel de compresión de una imagen JPEG, lo que aumenta o disminuye la calidad y el tamaño del archivo de salida de imagen
Flash Mode 5 Activar y desactivar el flash, o usar la configuración automática
Color Effects 5 Aplicar un efecto de color sobre una imagen capturada, por ejemplo, blanco y negro, sepia o negativo
Anti-Banding 5 Reducir el efecto de bandas en gradientes de color debido a la compresión JPEG
Picture Format 1 Especificar el formato de archivo para la foto
Picture Size 1 Especificar las dimensiones en píxeles de la foto guardada

Nota: Estas funciones no son compatibles con todos los dispositivos debido a las diferencias de hardware y la implementación de software. Para obtener información sobre la disponibilidad de funciones en el dispositivo donde se ejecuta la aplicación, consulta Cómo comprobar la disponibilidad de funciones.

Cómo comprobar la disponibilidad de funciones

Lo primero que es necesario comprender al comenzar a usar funciones de cámara en dispositivos Android es que no todas las funciones son compatibles con todos los dispositivos. Asimismo, los dispositivos compatibles con una función determinada pueden admitirla en diferentes niveles o con diferentes opciones. Por lo tanto, una parte del proceso de toma de decisiones como desarrollador durante el desarrollo de una aplicación de cámara es determinar qué funciones de cámara deseas admitir y en qué nivel. Una vez que tomes esa decisión, debes planear la incorporación de un código en la aplicación de cámara para comprobar si el hardware del dispositivo admite esas funciones y genera un error digno cuando la función no está disponible.

Para comprobar la disponibilidad de las funciones de cámara, puedes obtener una instancia de un objeto de parámetro de cámara y revisar los métodos correspondientes. En el siguiente ejemplo de código, se muestra la forma de obtener un objeto Camera.Parameters y comprobar si la cámara admite la función de enfoque automático:

Kotlin

val params: Camera.Parameters? = camera?.parameters
val focusModes: List<String>? = params?.supportedFocusModes
if (focusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO) == true) {
    // Autofocus mode is supported
}

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}

Puedes usar la técnica anterior con la mayoría de las funciones de cámara. El objeto Camera.Parameters proporciona un método getSupported...(), is...Supported() o getMax...() para determinar si (y en qué medida) se admite una función.

Si la aplicación requiere ciertas funciones de cámara para funcionar correctamente, puedes exigirlas a través de adiciones al manifiesto de la aplicación. Cuando se declara el uso de funciones de cámara específicas, como el flash y el enfoque automático, Google Play limita la instalación de la aplicación en dispositivos que no admiten estas funciones. Para obtener una lista de las funciones de cámara que se pueden declarar en el manifiesto de tu app, consulta el manifiesto Referencia de las funciones.

Cómo usar las funciones de cámara

La mayoría de las funciones de cámara se activan y se controlan mediante un objeto Camera.Parameters. Para obtener este objeto, primero debes obtener una instancia del objeto Camera, llamar al método getParameters(), cambiar el objeto de parámetro mostrado y, luego, volver a establecerlo en el objeto de cámara, como se muestra en el siguiente código de ejemplo:

Kotlin

val params: Camera.Parameters? = camera?.parameters
params?.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera?.parameters = params

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);

Esta técnica funciona con casi todas las funciones de cámara. La mayoría de los parámetros se pueden cambiar en cualquier momento después de obtener una instancia del objeto Camera. Normalmente, el usuario puede ver los cambios en los parámetros de inmediato en la vista previa de la cámara de la aplicación. En el software, la implementación de los cambios en los parámetros puede llevar varios fotogramas, ya que el hardware de la cámara debe procesar las nuevas instrucciones y enviar los datos de imagen actualizados.

Importante: Algunas funciones de la cámara no se pueden cambiar en el futuro. En especial, el cambio de la orientación o del tamaño de la vista previa de la cámara requiere primero detener la vista previa, cambiar el tamaño de la vista previa y reiniciar la vista previa. A partir de Android 4.0 (nivel de API 14), es posible cambiar la orientación de la vista previa sin reiniciar la vista previa.

Otras funciones de cámara requieren más código para su implementación. Algunas de estas son las siguientes:

  • Áreas de enfoque y medición
  • Detección de rostro
  • Video en time lapse

En las siguientes secciones, se proporciona una descripción rápida de la forma de implementar estas funciones.

Áreas de enfoque y medición

En algunos escenarios fotográficos, la medición de la luz y el enfoque automáticos pueden no producir los resultados deseados. A partir de Android 4.0 (nivel de API 14), tu aplicación de cámara puede proporcionar controles adicionales para que tu app o los usuarios puedan especificar áreas específicas de una imagen y usarlas con el objetivo de determinar los ajustes de enfoque o nivel de luz y transferir estos valores al hardware de la cámara a fin de usarlos en la captura de imágenes o videos.

Las áreas de medición y enfoque funcionan de forma muy similar a otras funciones de cámara en el sentido de que se controlan a través de métodos en el objeto Camera.Parameters. En el siguiente código, se muestra la configuración de dos áreas de medición de luz para una instancia de Camera:

Kotlin

// Create an instance of Camera
camera = getCameraInstance()

// set Camera parameters
val params: Camera.Parameters? = camera?.parameters

params?.apply {
    if (maxNumMeteringAreas > 0) { // check that metering areas are supported
        meteringAreas = ArrayList<Camera.Area>().apply {
            val areaRect1 = Rect(-100, -100, 100, 100) // specify an area in center of image
            add(Camera.Area(areaRect1, 600)) // set weight to 60%
            val areaRect2 = Rect(800, -1000, 1000, -800) // specify an area in upper right of image
            add(Camera.Area(areaRect2, 400)) // set weight to 40%
        }
    }
    camera?.parameters = this
}

Java

// Create an instance of Camera
camera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = camera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of image
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of image
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

camera.setParameters(params);

El objeto Camera.Area contiene dos parámetros de datos: un objeto Rect para especificar un área dentro del campo visual de la cámara y un valor de peso, que indica a la cámara el nivel de importancia que esta área debe tener en la medición de la luz o los cálculos de foco.

El campo Rect de un objeto Camera.Area describe una forma rectangular mapeada en una cuadrícula de 2000 x 2000 unidades. Las coordenadas -1000, -1000 representan la esquina superior izquierda de la imagen de la cámara, y las coordenadas 1000, 1000 representan la esquina inferior derecha de la imagen de la cámara, como se muestra en la ilustración a continuación.

Figura 1: las líneas rojas ilustran el sistema de coordenadas para especificar un elemento Camera.Area dentro de la vista previa de la cámara. El cuadro azul muestra la ubicación y la forma de un área de cámara con los valores de Rect 333,333,667,667.

Los límites de este sistema de coordenadas siempre coinciden con el borde externo de la imagen visible en la vista previa de la cámara y no se contraen ni se expanden con el nivel de zoom. De forma similar, la rotación de la vista previa de la imagen mediante Camera.setDisplayOrientation() no vuelve a mapear el sistema de coordenadas.

Detección de rostro

En las fotos con personas, los rostros generalmente son la parte más importante de la imagen y se deben usar para determinar el enfoque y el balance de blancos cuando se captura una imagen. El framework de Android 4.0 (nivel de API 14) proporciona diversas API para identificar rostros y calcular los ajustes de la foto mediante tecnología de reconocimiento facial.

Nota: Mientras se ejecuta la función de detección de rostro setWhiteBalance(String), setFocusAreas(List<Camera.Area>) y setMeteringAreas(List<Camera.Area>) no tienen efecto alguno.

Para usar la función de detección de rostro en la aplicación de cámara, se requieren algunos pasos generales:

  • Comprobar que la detección de rostro sea compatible con el dispositivo
  • Crear un objeto de escucha de detección de rostro
  • Agregar el objeto de escucha de detección de rostro al objeto de cámara
  • Iniciar la detección de rostro después de la vista previa (y después de cada reinicio de la vista previa)

La función de detección de rostro no es compatible con todos los dispositivos. Para comprobar si esta función es compatible, llama a getMaxNumDetectedFaces(). En el método de ejemplo de startFaceDetection() que aparece a continuación, se muestra un ejemplo de esta comprobación.

Para recibir un aviso y responder a la detección de un rostro, la aplicación de cámara debe establecer un receptor de los eventos de detección de rostro. Con tal fin, debes crear una clase de receptor que implemente la interfaz de Camera.FaceDetectionListener como se muestra en el siguiente código de ejemplo.

Kotlin

internal class MyFaceDetectionListener : Camera.FaceDetectionListener {

    override fun onFaceDetection(faces: Array<Camera.Face>, camera: Camera) {
        if (faces.isNotEmpty()) {
            Log.d("FaceDetection", ("face detected: ${faces.size}" +
                    " Face 1 Location X: ${faces[0].rect.centerX()}" +
                    "Y: ${faces[0].rect.centerY()}"))
        }
    }
}

Java

class MyFaceDetectionListener implements Camera.FaceDetectionListener {

    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}

Después de crear esta clase, debes incluirla en el objeto Camera de la aplicación, como se muestra en el siguiente código de ejemplo:

Kotlin

camera?.setFaceDetectionListener(MyFaceDetectionListener())

Java

camera.setFaceDetectionListener(new MyFaceDetectionListener());

La aplicación debe iniciar la función de detección de rostro cada vez que se inicia (o reinicia) la vista previa de la cámara. Crea un método para iniciar la detección de rostro y llamarla según sea necesario, como se muestra en el siguiente código de ejemplo.

Kotlin

fun startFaceDetection() {
    // Try starting Face Detection
    val params = mCamera?.parameters
    // start face detection only *after* preview has started

    params?.apply {
        if (maxNumDetectedFaces > 0) {
            // camera supports face detection, so can start it:
            mCamera?.startFaceDetection()
        }
    }
}

Java

public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}

Debes iniciar la detección de rostro cada vez que inicias (o reinicias) la vista previa de la cámara. Si utilizas la clase de vista previa que se muestra en Cómo crear una clase de vista previa, agrega el método startFaceDetection() a los métodos surfaceCreated() y surfaceChanged() de la clase de vista previa, como se muestra en el siguiente código de ejemplo.

Kotlin

override fun surfaceCreated(holder: SurfaceHolder) {
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // start face detection feature
    } catch (e: IOException) {
        Log.d(TAG, "Error setting camera preview: ${e.message}")
    }
}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    if (holder.surface == null) {
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null")
        return
    }
    try {
        mCamera.stopPreview()
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: ${e.message}")
    }
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // re-start face detection feature
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: ${e.message}")
    }
}

Java

public void surfaceCreated(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // start face detection feature

    } catch (IOException e) {
        Log.d(TAG, "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

    if (holder.getSurface() == null){
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null");
        return;
    }

    try {
        mCamera.stopPreview();

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
    }

    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // re-start face detection feature

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

Nota: Recuerda llamar a este método después de llamar a startPreview(). No intentes iniciar la detección de rostro en el método onCreate() de la actividad principal de la app de cámara, ya que la vista previa no estará disponible en este punto de la ejecución de la aplicación.

Video en time lapse

Con el video en time lapse, los usuarios pueden crear videoclips compuestos por fotos tomadas con unos pocos segundos o minutos de diferencia. Esta función utiliza MediaRecorder para registrar las imágenes en una secuencia de intervalo de tiempo.

Para grabar un video en time lapse con MediaRecorder, debes configurar el objeto grabador como si grabaras un video normal, pero debes establecer los fotogramas capturados por segundo en un valor menor y usar uno de los ajustes de calidad de intervalo de tiempo, como se muestra en el ejemplo de código a continuación.

Kotlin

mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH))
mediaRecorder.setCaptureRate(0.1) // capture a frame every 10 seconds

Java

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));
...
// Step 5.5: Set the video capture rate to a low number
mediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds

Es necesario determinar estos ajustes como parte de un procedimiento de configuración mayor para MediaRecorder. Si quieres obtener un ejemplo de código de configuración total, consulta Cómo configurar MediaRecorder. Una vez completada la configuración, inicia la grabación de video como si grabaras un videoclip normal. Para obtener más información sobre la configuración y la ejecución de MediaRecorder, consulta Cómo capturar videos.

En los ejemplos de Camera2Video y HdrViewfinder, se demuestra aún más el uso de las API que se abordan en esta página.

Campos de cámara que requieren permiso

Las apps que ejecutan Android 10 (nivel de API 29) o versiones posteriores deben tener el permiso CAMERA para acceder a los valores de los siguientes campos que muestra el método getCameraCharacteristics():

  • LENS_POSE_ROTATION
  • LENS_POSE_TRANSLATION
  • LENS_INTRINSIC_CALIBRATION
  • LENS_RADIAL_DISTORTION
  • LENS_POSE_REFERENCE
  • LENS_DISTORTION
  • LENS_INFO_HYPERFOCAL_DISTANCE
  • LENS_INFO_MINIMUM_FOCUS_DISTANCE
  • SENSOR_REFERENCE_ILLUMINANT1
  • SENSOR_REFERENCE_ILLUMINANT2
  • SENSOR_CALIBRATION_TRANSFORM1
  • SENSOR_CALIBRATION_TRANSFORM2
  • SENSOR_COLOR_TRANSFORM1
  • SENSOR_COLOR_TRANSFORM2
  • SENSOR_FORWARD_MATRIX1
  • SENSOR_FORWARD_MATRIX2

Código de ejemplo adicional

Para descargar apps de ejemplo, consulta el ejemplo de Camera2Basic y la app de ejemplo de CameraX oficial.