Captura de medios en Android

En esta sesión nos centraremos en estudiar los métodos para capturar audio y vídeo desde un dispositivo Android. Las últimas versiones del SDK de Android permiten emular la captura de video y audio en nuestros dispositivos virtuales, lo cual facilitará las pruebas de este tipo de aplicaciones. En concreto, es posible realizar esta simulación por medio de una webcam, que se utilizará para captar lo que se supone que estaría captando la cámara del dispositivo real.

Existen dos formas básicas de capturar medios:

  • Utilizar un intent para llamar a la aplicación de captura y obtener el resultado que ésta nos proporcione.

  • Crear nuestra propia aplicación de captura. Este método es más complejo pero nos permite personalizar la forma en la que se realiza la captura.

Captura de medios mediante intents

La forma más sencilla de capturar medios es lanzando la aplicación nativa que realiza esta tarea mediante un intent. Al lanzar el intent deberemos indicar mediante un parámetro (extra) el lugar donde queremos guardar el medio capturado. Deberemos seleccionar la ubicación adecuada según el uso que le queramos dar.

Almacenamiento de medios

En cualquier caso los medios deberán ser almacenados en la tarjeta SD para no gastar el almacenamiento externo y permitir a los usuarios tener acceso a los medios capturados. Básicamente tenemos dos opciones:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) indica el directorio externo donde almacenar los medios de forma pública e independiente de la aplicación. Se recomienda crear dentro de estas carpeta un subdirectorio por cada aplicación.

    Este método sólo esta disponible a partir de Android 2.2 (API 8). Si queremos compatibilidad con APIs anteriores utilizaremos Environment.getExternalStorageDirectory()

  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) nos indica un directorio propio de la aplicación donde almacenar los medios. Si la aplicación se desinstala, todos los medios almacenados en este directorio se borrarán.

Para poder escribir en el soporte de almacenamiento externo deberemos solicitar el siguiente permiso:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

En los dispositivos Android también contamos con un proveedor de contenido denominado Media Store. Este proveedor de contenidos consiste en una base de datos de medios almacenados en el dispositivo donde podremos registrar aquellos medios que capturemos desde nuestra aplicación, de forma que sean fácilmente accesibles desde cualquier otra aplicación del dispositivo. Más adelante veremos como registrar un fichero (fotografía, audio o vídeo) en esta base de datos.

Toma de fotografías

En esta sección veremos cómo tomar fotografías desde nuestra aplicación y utilizar la imagen obtenida para realizar alguna tarea. Como veremos se tratará ni más ni menos que un ejemplo clarísimo de intent implícito, en el que pediremos al sistema que se lance una actividad que pueda tomar fotografías. Por medio de este mecanismo de comunicación obtendremos la imagen capturada (o una dirección a la localización de la misma en el dispositivo) para trabajar con ellas.

Hoy en día es posible simular la cámara del dispositivo virtual por medio de una webcam, así que no es necesario utilizar un dispositivo real para poder probar estos ejemplos.

La acción a solicitar mediante el Intent implícito será MediaStore.ACTION_IMAGE_CAPTURE (más adelante hablaremos de la clase MediaStore). Lanzaremos el Intent por medio del método startActivityForResult, con lo que en realidad estaremos haciendo uso de una subactividad. Recuerda que esto tenía como consecuencia que al terminar la subactividad se invoca el método onActivityResult de la actividad padre. En este caso el identificador que se le ha dado a la subactividad es TAKE_PICTURE, que se habrá definido como una constante en cualquier otro lugar de la clase:

startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), TAKE_PICTURE);

Si no hemos hecho ningún cambio al respecto en nuestro sistema, esta llamada lanzará la actividad nativa para la toma de fotografías. No podemos evitar recordar una vez más la ventaja que esto supone para el desarrollador Android, ya que en lugar de tener que desarrollar una nueva actividad para la captura de imágenes desde cero, es posible hacer uso de los recursos del sistema.

Según los parámetros del Intent anterior, podemos hablar de dos modos de funcionamiento en cuanto a la toma de fotografías:

  • Modo thumbnail: este es el modo de funcionamiento por defecto. El Intent devuelto como respuesta por la subactividad, al que podremos acceder

    desde onActivityResult, contendrá un parámetro extra de nombre data, que consistirá en un thumbnail de tipo Bitmap.

  • Modo de imagen completa: la captura de imágenes se realizará de esta forma si se especifica una URI como valor del parámetro extra MediaStore.EXTRA_OUTPUT del Intent usado para lanzar la actividad de toma de fotografías. En este caso se guardará la imagen obtenida por la cámara, en su resolución completa, en el destino indicado en dicho parámetro extra. En este caso el Intent de respuesta no se usará para devolver un thumbnail, y por lo tanto el parámetro extra data tendrá como valor null.

En el siguiente ejemplo tenemos el esqueleto de una aplicación en el que se utiliza un Intent para tomar una fotografía, ya sea en modo thumbnail o en modo de imagen completa. Según queramos una cosa o la otra deberemos llamar a los métodos getThumbnailPicture o saveFullImage, respectivamente. En onActivityResult se determina el modo empleado examinando el valor del campo extra data del Intent de respuesta. Por último, una vez tomada la fotografía, se puede almacenar en el Media Store (hablamos de esto un poco más adelante) o procesarla dentro de nuestra aplicación antes de descartarla.

private static int TAKE_PICTURE = 1;
private Uri ficheroSalidaUri;

private void getThumbailPicture() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    startActivityForResult(intent, TAKE_PICTURE);
}

private void saveFullImage() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File file = new File(Environment.getExternalStorageDirectory(), "prueba.jpg");
    ficheroSalidaUri = Uri.fromFile(file);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, ficheroSalidaUri);
    startActivityForResult(intent, TAKE_PICTURE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == TAKE_PICTURE) {
        Uri imagenUri = null;
        // Comprobamos si el Intent ha devuelto un thumbnail
        if (data != null) {
            if (data.hasExtra("data")) {
                Bitmap thumbnail = data.getParcelableExtra("data");
                // HACER algo con el thumbnail
            }
        }
        else {
             // HACER algo con la imagen almacenada en ficheroSalidaUri
        }
    }
}

Captura de vídeo

La manera más sencilla de comenzar a grabar vídeo es mediante la constante ACTION_VIDEO_CAPTURE definida en la clase MediaStore, que deberá utilizarse conjuntamente con un Intent que se pasará como parámetro a startActivityForResult:

startActivityForResult(new Intent(MediaStore.ACTION_VIDEO_CAPTURE), GRABAR_VIDEO);

Esto lanzará la aplicación nativa de grabación de vídeos en Android, permitiendo al usuario comenzar o detener la grabación, revisar lo que se ha grabado, y volver a comenzar la grabación en el caso en el que se desee. La ventaja como desarrolladores será la misma de siempre: al utilizar el componente nativo nos ahorramos el tener que desarrollar una actividad para la captura de vídeo desde cero.

La acción de captura de vídeo que se pasa como parámetro al Intent acepta dos párametros extra opcionales, cuyos identificadores se definen como constantes en la clase MediaStore:

  • EXTRA_OUTPUT: por defecto el vídeo grabado será guardado en el Media Store. Para almacenarlo en cualquier otro lugar indicaremos una URI como parámetro extra utilizando este identificador.

  • EXTRA_VIDEO_QUALITY: mediante un entero podemos especificar la calidad del vídeo capturado. Sólo hay dos valores posibles: 0 para tomar vídeos en baja resolución

    y 1 para tomar vídeos en alta resolución (este último valor es el que se toma por defecto).

A continuación se puede ver un ejemplo en el que se combinan todos estos conceptos vistos hasta ahora:

private static int GRABAR_VIDEO = 1;
private static int ALTA_CALIDAD = 1;
private static int BAJA_CALIDAD = 0;

private void guardarVideo(Uri uri) {
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

    // Si se define una uri se especifica que se desea almacenar el
    // vídeo en esa localización. En caso contrario se hará uso
    // del Media Store
    if (uri != null)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, output);

    // En la siguiente línea podríamos utilizar cualquiera de las
    // dos constantes definidas anteriormente: ALTA_CALIDAD o BAJA_CALIDAD
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, ALTA_CALIDAD);

    startActivityForResult(intent, GRABAR_VIDEO);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == GRABAR_VIDEO) {
        Uri videoGrabado = data.getData();
        // Hacer algo con el vídeo
    }
}

Captura de medios desde nuestra actividad

En el caso en el que se desee reemplazar la aplicación nativa de captura o se quiera tener más control sobre la grabación será posible utilizar la clase MediaRecorder.

Para poder capturar audio y video desde nuestra propia actividad, sin lanzar la aplicación nativa, será necesario incluir los siguientes permisos en el manifest:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>

La cámara

Tanto si queremos tomar fotografías como grabar vídeo necesitaremos tener acceso a la cámara del dispositivo. Para ello deberemos solicitar el permiso CAMERA:

<uses-permission android:name="android.permission.CAMERA" />

También deberemos indicar en el manifest que la aplicación utiliza la característica de la cámara:

<uses-feature android:name="android.hardware.camera" />

Podemos especificar diferentes requerimientos de hardware de la cámara para nuestra aplicación:

  • En el caso de que la aplicación pueda utilizar la cámara, pero no sea obligatorio contar con una cámara para poder ejecutar nuestra aplicación, podremos indicar que dicha característica no es obligatoria con:

    <uses-feature android:name="android.hardware.camera" android:required="false" />

    Dentro del código de la aplicación, podremos detectar si la cámara está presente con:

    boolean tieneCamara = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
  • Actualmente es habitual encontrar dispositivos que incorporan más de una cámara. Si necesitamos que el dispositivo cuente con cámara frontal se puede especificar el siguiente requerimiento:

    <uses-feature android:name="android.hardware.camera.front" />

    Podemos consultar el número de cámaras con las que cuenta el dispositivo con:

    int numCamaras = Camera.getNumberOfCameras();
  • También podemos solicitar otras características, como que cuente con autofocus o flash.

Para acceder a la cámara utilizaremos el método Camera.open(). Este método abrirá la cámara principal del dispositivo, si queremos utilizar otra cámara podremos utilizar Camara.open(int numCamara).

Podemos utilizar el objeto Camera para mostrar la previsualización de la cámara mientras hacemos captura de foto o vídeo. Para ello necesitaremos una vista de tipo SurfaceView:

public class CameraPreview extends SurfaceView
                        implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

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

        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    public void surfaceCreated(SurfaceHolder holder) {
              // Pone en marcha el preview de la camara
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) { }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
       if (mHolder.getSurface() != null){

                    // Detiene el preview con el formato anterior
            try {
                mCamera.stopPreview();
            } catch (Exception e){ }

                    // Reanuda el preview con el nuevo formato
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
            } catch (Exception e){ }
             }
    }
}

Podemos utilizar la vista anterior en una actividad:

public class CapturaActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
                try {
                    mCamera = Camera.open();
                    mPreview = new CameraPreview(this, mCamera);
                    setContentView(mPreview);
                } catch(Exception e) {
                    // No tiene acceso a la camara
                }
    }
}

Dentro de esta actividad podremos capturar fotos directamente a través del objeto Camera mientras se previsualiza, o capturar vídeo utilizando un objeto MediaRecorder.

Es importante liberar la cámara cuando no se vaya a utilizar más con Camera.release(). Un lugar adecuado para hacer esto puede ser el método onDestroy() de la actividad:

        @Override
        public void onDestroy() {
                super.onDestroy();
                mCamera.release();
        }

Captura de fotografías

Podemos utilizar el objeto Camera para tomar fotografías. Para ello llamaremos al método takePicture pasando como parámetro un listener de tipo PictureCallback, al que se llamará cuando la imagen haya sido tomada:

mCamera.takePicture(null, null, new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
                // Grabar data en directorio de medios
                ...
    }
});

El primer parámetro de takePicture es opcional y nos permite definir un callback para el obturador. El segundo y tercer parámetro sirve para especificar un callback para guardar la imagen en formato RAW y JPEG respectivamente.

Por ejemplo, podríamos guardar la imagen JPEG en el almacenamiento externo con:

try {
    File file = new File(Environment.getExternalStorageDirectory(),
                             "prueba.jpg");
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(data);
    fos.close();
} catch (Exception e) { }

API de Camara 2

A partir de Android 5.0 (Lollipop) aparece una nueva versión de API de cámara (android.hardware.camera2), que nos permite tener un mayor control sobre este dispositivo, pasando la antigua API a estar desaprobada. Sin embargo, la nueva API no es compatible con versiones anteriores de Android, por lo que si queremos mantener la compatibilidad con versiones de Android anteriores a la 5.0 (API 21) deberemos utilizar la antigua cámara, o bien código separado para cada versión:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Camara 2
} else {
    // Camara 1
}

La nueva API de cámara deja desaprobada la clase Camera, y en su lugar utilizar CameraDevice. Para acceder a las cámaras disponibles se proporciona la clase CameraManager.

Captura de vídeo con MediaRecorder

Podemos utilizar la clase MediaRecorder para capturar audio o video desde nuestras propias actividades. La creación de un objeto de esta clase es sencilla:

MediaRecorder mediaRecorder = new MediaRecorder();

La clase MediaRecorder permite especificar el origen del audio o vídeo, el formato del fichero de salida y los codecs a utilizar. Como en el caso de la clase MediaPlayer, la clase MediaRecorder maneja la grabación mediante una máquina de estados. Esto quiere decir que el orden en el cual se inicializa y se realizan operaciones con los objetos de este tipo es importante. En resumen, los pasos para utilizar un objeto MediaRecorder serían los siguientes:

  • Crear un nuevo objeto MediaRecorder.

  • Asignarle las fuentes a partir de las cuales grabar el contenido.

  • Definir el formato de salida.

  • Especificar las características del vídeo: codec, framerate y resolución de salida.

  • Seleccionar un fichero de salida.

  • Prepararse para la grabación.

  • Realizar la grabación.

  • Terminar la grabación.

Una vez finalizamos la grabación hemos de hacer uso del método release del objeto MediaRecorder para liberar todos sus recursos asociados:

mediaRecorder.release();

Configurando y controlando la grabación de vídeo

Como se ha indicado anteriormente, antes de grabar se deben especificar la fuente de entrada, el formato de salida, el codec de audio o vídeo y el fichero de salida, en ese estricto orden.

Los métodos setAudioSource y setVideoSource permiten especificar la fuente de datos por medio de constantes estáticas definidas en MediaRecorder.AudioSource y MediaRecorder.VideoSource, respectivamente. El siguiente paso consiste en especificar el formato de salida por medio del método setOutputFormat que recibirá como parámetro una constante entre las definidas en MediaRecorder.OutputFormat. A continuación usamos el método setAudioEnconder o setVideoEncoder para especificar el codec usado para la grabación, utilizando alguna de las constantes definidas en MediaRecorder.AudioEncoder o MediaRecorder.VideoEncoder, respectivamente. Es en este punto en el que podremos definir el framerate o la resolución de salida si se desea. Finalmente indicamos la localización del fichero donde se guardará el contenido grabado por medio del método setOutputFile. El último paso antes de la grabación será la invocación del método prepare.

El siguiente código muestra cómo configurar un objeto MediaRecorder para capturar audio y vídeo del micrófono y la cámara usando un codec estándar y grabando el resultado en la tarjeta SD:

MediaRecorder mediaRecorder = new MediaRecorder();

mCamera.unlock();
mediaRecorder.setCamera(mCamera);

// Configuramos las fuentes de entrada
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

// Seleccionamos el formato de salida
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);

// Seleccionamos el codec de audio y vídeo
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

// Especificamos el fichero de salida
mediaRecorder.setOutputFile("/mnt/sdcard/mificherodesalida.mp4");

// Nos preparamos para grabar
mediaRecorder.prepare();

Recuerda que los métodos que hemos visto en el ejemplo anterior deben invocarse en ese orden concreto, ya que de lo contrario se lanzará una excepción de tipo Illegal State Exception.

Para comenzar la grabación, una vez inicializados todos los parámetros, utilizaremos el método start:

mediaRecorder.start();

Cuando se desee finalizar la grabación se deberá hacer uso en primer lugar del método stop, y a continuación invocar el método reset. Una vez seguidos estos pasos es posible volver a utilizar el objeto invocando de nuevo a setAudioSource y setVideoSource. Llama a release para liberar los recursos asociados al objeto MediaRecorder (el objeto no podrá volver a ser usado, se tendrá que crear de nuevo):

mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();

Previsualización

Durante la grabación de vídeo es recomendable mostrar una previsualización de lo que se está captando a través de la cámara en tiempo real. Para ello utilizaremos el método setPreviewDisplay, que nos permitirá asignar un objeto Surface sobre el cual mostrar dicha previsualización.

El comportamiento en este caso es muy parecido al de la clase MediaPlayer para la reproducción de vídeo. Debemos definir una actividad que incluya una vista de tipo SurfaceView en su interfaz y que implemente la interfaz SurfaceHolder.Callback. Una vez que el objeto SurfaceHolder ha sido creado podemos asignarlo al objeto MediaRecorder invocando al método setPreviewDisplay, tal como se puede ver en el siguiente código. El vídeo comenzará a previsualizarse tan pronto como se haga uso del método prepare.

public class CapturaActivity extends Activity implements SurfaceHolder.Callback
{

    private MediaRecorder mMediaRecorder;

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

        SurfaceView surface = (SurfaceView)findViewById(R.id.surface);
        SurfaceHolder holder = surface.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        holder.setFixedSize(400, 300);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        if (mMediaRecorder != null) {
            try {
                mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

                mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);

                mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
                mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

                mMediaRecorder.setOutputFile("/sdcard/myoutputfile.mp4");

                // Asociando la previsualización a la superficie
                mMediaRecorder.setPreviewDisplay(holder.getSurface());
                mMediaRecorder.prepare();
            } catch (IllegalArgumentException e) {
                Log.d("MEDIA_PLAYER", e.getMessage());
            } catch (IllegalStateException e) {
                Log.d("MEDIA_PLAYER", e.getMessage());
            } catch (IOException e) {
                Log.d("MEDIA_PLAYER", e.getMessage());
            }
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mMediaRecorder.release();
    }

    public void surfaceChanged(SurfaceHolder holder,
                    int format, int width, int height) { }
}

Agregar ficheros multimedia en el Media Store

El comportamiento por defecto en Android con respecto al acceso de contenido multimedia es que los ficheros multimedia generados u obtenidos por una aplicación no podrán ser accedidos por el resto. En el caso de que deseemos que un nuevo fichero multimedia sí pueda ser accedido desde el exterior de nuestra aplicación deberemos almacenarlo en el Media Store, que mantiene una base de datos de la metainformación de todos los ficheros almacenados tanto en dispositivos externos como internos del terminal telefónico.

El Media Store es un proveedor de contenidos, y por lo tanto utilizaremos el mecanismo estándar para acceso a dichos proveedores para acceder a la información que contiene.

Existen varias formas de incluir un fichero multimedia en el Media Store. La más sencilla es hacer uso de la clase MediaScannerConnection, que permitirá determinar automáticamente de qué tipo de fichero se trata, de tal forma que se pueda añadir sin necesidad de proporcionar ninguna información adicional.

La clase MediaScannerConnection proporciona un método scanFile para realizar esta tarea. Sin embargo, antes de escanear un fichero se deberá llamar al método connect y esperar una conexión al Media Store. La llamada a connect es asíncrona, lo cual quiere decir que deberemos crear un objeto MediaScannerConnectionClient que nos notifique en el momento en el que se complete la conexión. Esta misma clase también puede ser utilizada para que se lleve a cabo una notificación en el momento en el que el escaneado se haya completado, de tal forma que ya podremos desconectarnos del Media Store.

En el siguiente ejemplo de código podemos ver un posible esqueleto para un objeto MediaScannerConnectionClient. En este código se hace uso de una instancia de la clase MediaScannerConnection para manejar la conexión y escanear el fichero. El método onMediaScannerConected será llamado cuando la conexión ya se haya establecido, con lo que ya será posible escanear el fichero. Una vez se complete el escaneado se llamará al método onScanCompleted, en el que lo más aconsejable es llevar a cabo la desconexión del Media Store.

MediaScannerConnectionClient mediaScannerClient = new MediaScannerConnectionClient() {
    private MediaScannerConnection msc = null;
    {
        msc = new MediaScannerConnection(getApplicationContext(), this);
    msc.connect();
    }

    public void onMediaScannerConnected() {
        msc.scanFile("/mnt/sdcard/DCIM/prueba.mp4", null);
    }

    public void onScanCompleted(String path, Uri uri) {

        // Realizar otras acciones adicionales

        msc.disconnect();
    }
};

Ejercicios

Grabación de vídeo con MediaRecorder

En este ejercicio optativo utilizaremos la aplicación Video que se te proporciona en las plantillas para crear una aplicación que permita guardar vídeo, mostrándolo en pantalla mientras éste se graba. La interfaz de la actividad principal tiene dos botones, Grabar y Parar, y una vista SurfaceView sobre la que se previsualizará el vídeo siendo grabado.

Debes seguir los siguientes pasos:

  • Añade los permisos necesarios en el Manifest de la aplicación para poder grabar audio y vídeo y para poder guardar el resultado en la tarjeta SD (recuerda

    que el siguiente código debe aparecer antes del elemento application):

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • Añade un atributo a la clase VideoActivity:

MediaRecorder mediaRecorder;
  • Inicializa el objeto MediaRecorder en el método onCreate:

mediaRecorder = new MediaRecorder();
  • Para poder previsualizar el vídeo en el SurfaceView hemos de obtener su holder. Como esta operación es asíncrona, debemos añadir los manejadores adecuados, de tal forma que sólo se pueda reproducir la previsualización cuando todo esté listo. El primer paso consiste en hacer que la clase VideoActivity implemente la interfaz SurfaceHolder.Callback. Para implementar esta interfaz deberás añadir los siguiente métodos a la clase:

public void surfaceCreated(SurfaceHolder holder) {
    // TODO: asociar la superficie al MediaRecorder
}

public void surfaceDestroyed(SurfaceHolder holder) {
    // TODO: liberar los recursos
}
  • Añadimos en onCreate el código necesario para obtener el holder de la superficie y asociarle como manejador la propia clase VideoActivity:

m_holder = superficie.getHolder();
m_holder.addCallback(this);
m_holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  • Gracias al método surfaceCreated podremos asociar el objeto MediaRecorder al holder del SurfaceView. Dentro de esta misma función le daremos al atributo booleano preparado el valor true, lo cual nos permitirá saber que ya podemos iniciar la reproducción:

mediaRecorder.setPreviewDisplay(holder.getSurface());
preparado = true;
  • En el método surfaceDestroyed simplemente invocaremos el método release del objeto MediaRecorder, para liberar los recursos del objeto al finalizar la actividad.

  • Se ha añadido un método configurar a la clase VideoActivity que se utilizará para indicar la fuente de audio y vídeo, el nombre del fichero donde guardaremos el vídeo grabado, y algunos parámetros más. En esa función debes añadir el siguiente código. Fíjate cómo se ha incluido una llamada a prepare al final:

if (mediaRecorder != null) {
    try {

        // Inicializando el objeto MediaRecorder
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"video.mp4");
        mediaRecorder.setOutputFile(file.getPath());

        mediaRecorder.prepare();

    } catch (IllegalArgumentException e) {
        Log.d("MEDIA_PLAYER", e.getMessage());
    } catch (IllegalStateException e) {
        Log.d("MEDIA_PLAYER", e.getMessage());
    } catch (IOException e) {
        Log.d("MEDIA_PLAYER", e.getMessage());
    }
}
  • Sólo queda introducir el código necesario para iniciar y detener la reproducción. En el manejador del botón Grabar invocaremos al método start del objeto MediaRecorder, sin olvidar realizar una llamada previa al método configurar.

  • En el manejador del botón Parar invocamos en primer lugar el método stop y en segundo lugar el método reset del objeto MediaRecorder. Con esto podríamos volver a utilizar este objeto llamando a configurar y a start.

A la hora de redactar estos ejercicios existía un bug que impedía volver a utilizar un objeto MediaRecorder tras haber usado reset. Puede que sea necesario que tras hacer un reset debas invocar el método release y crear una nueva instancia del objeto MediaRecorder con el operador new.

Last updated