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:
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:
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 accederdesde
onActivityResult
, contendrá un parámetro extra de nombredata
, que consistirá en un thumbnail de tipoBitmap
.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
delIntent
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 elIntent
de respuesta no se usará para devolver un thumbnail, y por lo tanto el parámetro extradata
tendrá como valornull
.
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.
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
:
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óny 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:
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:
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
:
También deberemos indicar en el manifest que la aplicación utiliza la característica de la cámara:
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:
Dentro del código de la aplicación, podremos detectar si la cámara está presente con:
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:
Podemos consultar el número de cámaras con las que cuenta el dispositivo con:
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
:
Podemos utilizar la vista anterior en una actividad:
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:
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:
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:
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:
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
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:
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:
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:
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
:
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):
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
.
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.
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
):
Añade un atributo a la clase
VideoActivity:
Inicializa el objeto
MediaRecorder
en el métodoonCreate
:
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 claseVideoActivity
implemente la interfazSurfaceHolder.Callback
. Para implementar esta interfaz deberás añadir los siguiente métodos a la clase:
Añadimos en
onCreate
el código necesario para obtener el holder de la superficie y asociarle como manejador la propia claseVideoActivity
:
Gracias al método
surfaceCreated
podremos asociar el objetoMediaRecorder
al holder delSurfaceView
. Dentro de esta misma función le daremos al atributo booleanopreparado
el valor true, lo cual nos permitirá saber que ya podemos iniciar la reproducción:
En el método
surfaceDestroyed
simplemente invocaremos el métodorelease
del objetoMediaRecorder
, para liberar los recursos del objeto al finalizar la actividad.Se ha añadido un método
configurar
a la claseVideoActivity
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 aprepare
al final:
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 objetoMediaRecorder
, sin olvidar realizar una llamada previa al métodoconfigurar
.En el manejador del botón Parar invocamos en primer lugar el método
stop
y en segundo lugar el métodoreset
del objetoMediaRecorder
. Con esto podríamos volver a utilizar este objeto llamando aconfigurar
y astart
.
A la hora de redactar estos ejercicios existía un bug que impedía volver a utilizar un objeto
MediaRecorder
tras haber usadoreset
. Puede que sea necesario que tras hacer unreset
debas invocar el métodorelease
y crear una nueva instancia del objetoMediaRecorder
con el operadornew
.
Last updated