Академический Документы
Профессиональный Документы
Культура Документы
Tema 15
Multimedia y Cámara
TEMA 15. MULTIMEDIA Y CÁMARA
Introducción
Permisos necesarios
Para que una aplicación pueda reproducir elementos multimedia, debe solicitar los permisos
adecuados que le permitan acceder a las funcionalidades inherentes. Para ello, deberán
declararse los siguientes permisos en el manifiesto de la aplicación:
Este permiso también será necesario en caso de querer usar los métodos:
MediaPlayer.setScreenOnWhilePlaying()
MediaPlayer.setWakeMode()
Uso de MediaPlayer
El componente más importante del framework multimedia es MediaPlayer. Con esta clase se
podrá buscar, decodificar y reproducir tanto audio como vídeo de diversas fuentes de forma
sencilla (recursos locales, URIs internas obtenidas, por ejemplo, de proveedores de contenido,
URLs externas a través de streaming, etc.).
Por ejemplo, para reproducir un archivo de audio que se haya almacenado como recurso local
de la aplicación (en la carpeta “res/raw/”) bastarán dos líneas de código:
Debido a que el archivo utilizado se ha añadido como un recurso raw (“crudo”, “en bruto”), el
sistema no lo tratará de ningún modo y no realizará ningún proceso de optimización sobre el
mismo. Los recursos multimedia almacenados como raw podrán ser de cualquiera de los tipos
multimedia que son soportados en Android (.3gp, .mp4, .aac, .flac, .mp3, .mid, .mkv, .wav, .jpg,
.gif, .bmp, .png, .webm)
Y para reproducir un elemento multimedia desde una URL externa, vía HTTP 1, únicamente se
deberá cambiar la “fuente” de datos:
1
El servidor que alberga archivo accedido online deberá ser capaz de proporcionar descargas progresivas.
Preparación asíncrona
Debido a que la ejecución del método prepare() puede tardar cierto tiempo (este método
está encargado de la búsqueda y decodificación del elemento multimedia), nunca se deberá
invocar desde el hilo principal de la aplicación, ya que esta se dejaría de responder hasta que la
ejecución dicho método finalizase. Esto podría provocar un error ANR (Application Not
Responding) además de implicar una mala experiencia de usuario quien podría llegar a adquirir
la impresión de que la aplicación es muy lenta.
Para evitar este comportamiento, se deberá crear otro hilo para preparar el MediaPlayer y se
notificará al hilo principal cuando este haya sido preparado. Se puede implementar un hilo en
segundo plano de forma convencional pero, afortunadamente, el framework multimedia
proporciona el método prepareAsync() que, internamente, genera un hilo en segundo plano
para preparar el elemento multimedia. Este método finaliza inmediatamente y no cuelga la
aplicación. Cuando el elemento multimedia esté preparado, se invocará al método callback
onPrepared() del listener MediaPlayer.OnPreparedListener, el cual será configurado a
través de setOnPreparedListener().
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
Liberación de recursos
Cuando se finalice la reproducción del elemento multimedia será recomendable liberar los
recursos del sistema que estén asignados a MediaPlayer ya que pueden ser elevados, en
comparación con los recursos que haya disponibles en el dispositivo. Por lo tanto, se deberá
invocar siempre a release() y asignar a null la instancia del MediaPlayer en los siguientes
casos:
@Override
public void onStop() {
// Se ha finalizado la reproducción.
// Se liberan recursos
mediaPlayer.release();
mediaPlayer = null;
}
@Override
public void onCompletion(MediaPlayer mp) {
mp.release();
mp = null;
}
• En caso de error.
En el caso de que la actividad sea reiniciada o reactivada, se deberá crear una nueva instancia
de MediaPlayer y prepararla antes de poder reiniciar la reproducción.
Ejecución asíncrona
Por defecto, los servicios y actividades de una aplicación se ejecutan en el mismo hilo (el hilo
main, principal, de la aplicación). Por lo tanto, si un servicio va a realizar tareas largas, que
puedan bloquear el hilo principal, se deberán implementar dichas tareas de forma asíncrona,
bien creando un hilo, bien utilizando las funcionalidades que proporcione el framework. En el
caso de MediaPlayer, se podrá usar prepareAsync() tal y como ya se ha comentado,
implementando el listener MediaPlayer.OnPreparedListener.
Así mismo, se pueden implementar otros listeners en el servicio, de forma que reciba
notificaciones del MediaPlayer en caso, por ejemplo, de que finalice la reproducción o se
produzca un error 2, para que sean liberados los recursos.
Para invocar al servicio desde la actividad, se podrá lanzar un Intent explícito o bien uno
implícito cuya acción sea, por ejemplo, com.cursoandroid.action.PLAY. Para usar la
invocación implícita, se deberá definir un <intent-filter> en el manifiesto de la aplicación:
<service
android:name="com.cursoandroid.services.MediaService"
android:exported="false" >
<intent-filter>
<action android:name="com.cursoandroid.action.PLAY" />
</intent-filter>
</service>
2
Cuando se produzca un error, es importante recordar que el MediaPlayer adoptará el estado Error por lo que se
deberá reinicializar antes de volverlo a usar, invocando al método reset().
@Override
public void onCreate() {
super.onCreate();
}
initMediaPlayer();
}
return super.onStartCommand(intent, flags, startId);
}
try {
mMediaPlayer.setDataSource(urlMedia);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
wifiLock = ((WifiManager)
getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL,
"bloqueo_wifi");
wifiLock.acquire();
// Importante: se añade este servicio como listener de diversos
// eventos del MediaPlayer
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.prepareAsync();
}
@Override
/** Invocado cuando MediaPlayer finaliza la reproducción */
public void onCompletion(MediaPlayer mp) {
destroyMediaPlayer(mp);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
destroyMediaPlayer(mp);
Log.e("ERROR_MEDIA_PLAYER", "Error en la reproducción. What=" +
what + ". Extra=" + extra);
return false;
}
@Override
public void onDestroy() {
destroyMediaPlayer(mMediaPlayer);
super.onDestroy();
}
Para que MediaPlayer utilice esta funcionalidad, deberá invocar a setWakeMode() 3 durante
su inicialización. Cuando la reproducción sea pausada o parada, MediaPlayer liberará este
bloqueo automáticamente.
mMediaPlayer.setWakeMode(getApplicationContext(),
PowerManager.PARTIAL_WAKE_LOCK);
WifiLock wifiLock =
((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "bloqueo_wifi");
wifiLock.acquire();
Para liberar este bloqueo, se deberá invocar a wifiLock.release() cuando MediaPlayer sea
destruido.
En caso de que se quiera evitar que el servicio sea eliminado por el sistema, si los recursos
disponibles son escasos, se deberá pasar el servicio a primer plano. Para ello, se deberá crear
obligatoriamente una notificación que será mostrada mientras el servicio esté activo. Esta
notificación deberá ser mostrada, desde el servicio, a través del método
startForeground(NOTIFICATION_ID, notification) y será eliminada a través del método
stopForeground(true).
3
Para utilizar esta funcionalidad, la aplicación deberá solicitar el permiso WAKE_LOCK.
4
Ídem Nota al pie número 3
Debido a que Android es un entorno multi-tarea real, las aplicaciones que usan audio deben
compartir la salida de audio de forma colaborativa, en vez de competir por el uso de la misma.
Antes de Android 2.2, no existía ningún mecanismo que resolviera este problema, de forma
que el sonido de una notificación importante podría ser inaudible si otra aplicación estaba
reproduciendo música con un volumen elevado. A partir de Android 2.2, la plataforma
introduce un mecanismo de negociación de la salida de audio llamado Audio Focus.
Las aplicaciones que utilicen la salida de audio deberán solicitar el foco del audio antes de
comenzar la reproducción. Una vez se consiga el foco, se podrá utilizar libremente la salida de
audio. Además, se deberá reaccionar ante los cambios de foco, a través de un listener, para, en
caso de perder el foco del audio, bajar el volumen (ducking) o parar la reproducción (y
posiblemente liberar recursos) y, posteriormente, subir el volumen o reanudar la reproducción
cuando se recupere el foco del audio.
AudioManager audioManager =
(AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// No se ha podido obtener el foco del audio
Toast.makeText(this, R.string.no_focus, Toast.LENGTH_SHORT).show();
} else {
initMediaPlayer();
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// Se reanuda la reproducción
if (mMediaPlayer == null)
initMediaPlayer();
else if (!mMediaPlayer.isPlaying())
mMediaPlayer.start();
case AudioManager.AUDIOFOCUS_LOSS:
// Se ha perdido el foco del audio durante un periodo de tiempo
// sin especificar, por lo que se deberá parar la reproducción y
// liberar recursos
if (mMediaPlayer.isPlaying())
mMediaPlayer.stop();
destroyMediaPlayer(mMediaPlayer);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Se ha perdido el foco del audio durante un periodo de tiempo
// que va a ser corto, por lo que simplemente se pausa la
// reproducción
if (mMediaPlayer.isPlaying())
mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Se ha perdido el foco del audio durante un periodo de tiempo
// que va a ser corto y además es posible bajar el volumen y
// seguir reproduciendo
if (mMediaPlayer.isPlaying())
mMediaPlayer.setVolume(0.25f, 0.25f); // Volumen al 25%
break;
}
}
Debido a que AudioManager está disponible desde el nivel de API 8 (Android 2.2), para
crear aplicaciones compatibles con versiones anteriores se deberán adoptar estrategias
de compatibilidad “hacia atrás” (backward compatibility). Una de las estrategias de
compatibilidad se basa en el uso de reflection mientras que otra se basa en la
implementación de todas las funcionalidades relacionadas con el audio focus en una clase
separada (un helper). Se puede observar la implementación y uso de AudioFocusHelper
en la aplicación de ejemplo de este tema.
Las aplicaciones Android tienen la posibilidad de parar la reproducción del audio cuando se
desencadena un evento que hace que dicho audio pueda ser molesto, debido a que comience
a emitirse a través de los altavoces externos del dispositivo porque se hayan desconectado los
auriculares.
Para implementar este comportamiento, será necesario gestionar el Intent con acción
ACTION_AUDIO_BECOMING_NOISY que es emitido por el sistema vía broadcast. En el manifiesto,
será necesario registrar un receptor de dicho broadcast:
<receiver
android:name="com.cursoandroid.broadcastreceiver.MediaIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
@Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(
android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// Intent al servicio para parar la reproducción
Intent intentService = new Intent(SERVICE_ACTION_STOP);
// Este método hará que el Intent sea pasado a
// MediaService.onStartCommand(), esté o no iniciado el servicio.
ctx.startService(intentService);
}
}
}
Y, finalmente, el servicio que reproduce el audio, deberá capturar el nuevo Intent, declarando
el correspondiente filtro en el manifiesto, y añadiendo el siguiente código en el método
onStartCommand():
if (intent.getAction().equals(ACTION_STOP)) {
// Acción recibida desde MediaIntentReceiver
if (mMediaPlayer != null && mMediaPlayer.isPlaying())
mMediaPlayer.stop();
Toast.makeText(this, R.string.phones_unplugged,
Toast.LENGTH_SHORT).show();
destroyNotification(mNotificationID);
destroyMediaPlayer(mMediaPlayer);
}
}
Existen dos procedimientos distintos para incluir funcionalidades que permitan capturar
imágenes y vídeos en las aplicaciones. El primer procedimiento se basa en la delegación de
dicha funcionalidad en las aplicaciones tipo Cámara que ya estén instaladas en el dispositivo,
vía Intent, mientras que el segundo procedimiento, más complejo, permite la creación de
cámaras personalizadas en la propia aplicación a través de una SurfaceView.
Android permite la captura de imágenes y vídeo a través de la API Camera o vía Intent. Las
clases fundamentales son Camera, que permite controlar las cámaras del dispositivo,
SurfaceView, que mostrará la previsualización en tiempo real de la imagen (el “visor”)
MediaRecorder, que será utilizada para grabar vídeo, e Intent que, mediante las acciones
MediaStore.ACTION_IMAGE_CAPTURE y MediaStore.ACTION_VIDEO_CAPTURE permite
capturar imagen y vídeo sin el uso directo de un objeto tipo Camera.
Para utilizar la API Camera será necesario declarar ciertos permisos y características en el
manifiesto de la aplicación:
• Permiso para usar la Cámara: solo en caso de usar Camera directamente. En caso de
usar la funcionalidad vía Intent, no será necesario.
• Requisito de características del hardware Cámara: para que Google Play filtre
adecuadamente la aplicación y no la ofrezca a dispositivos que no tengan cámara u
otras características de hardware relacionadas, se deberá declarar el uso de las
siguientes características (features) según las funcionalidades que implemente la
aplicación.
• Otros permisos: para almacenar las imágenes y vídeos en la memoria externa del
dispositivo, para grabar audio cuando se grabe un vídeo o para etiquetar con
coordenadas GPS las imágenes capturadas:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.RECORD_AUDIO" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
El procedimiento más rápido para añadir la funcionalidad de captura de imagen y vídeo en una
aplicación es vía Intent, delegando dicha funcionalidad en cualquiera de las aplicaciones que
ya estén instaladas en el dispositivo. Simplemente se lanzará un Intent con una acción
concreta con el método startActivityForResult() para, una vez capturada la imagen o el
vídeo con la aplicación adecuada, recibir el resultado de dicho Intent en el método callback
onActivityResult():
El Intent que lance la cámara también podrá contener información adicional añadida en el
método putExtra() como, por ejemplo:
Los archivos multimedia que sean generados deberán ser almacenados en la memoria externa
del dispositivo (en general una tarjeta SD) para conservar el espacio reservado para el sistema
y para que los usuarios puedan acceder a dichos archivos desde otros dispositivos y
aplicaciones. Existen diversas localizaciones posibles para almacenar los archivos multimedia,
aunque se deberán considerar solo dos:
• Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)5.
Este método devolverá la localización compartida estándar y recomendada para
almacenar imágenes y vídeos. Este directorio es público de forma que cualquier otra
aplicación podrá leer, modificar o borrar los archivos que contenga. Al desinstalar
aplicaciones que hayan grabado archivos en esta localización, se conservarán dichos
archivos. Es recomendable crear subdirectorios para evitar mezclar las imágenes o
vídeos con los que almacenen otras aplicaciones.
• Environment.getExternalFilesDir(Environment.DIRECTORY_PICTURES). Este
método devolverá la localización estándar para almacenar imágenes y vídeos, asociada
a la aplicación. Si la aplicación es desinstalada, todos los archivos almacenados en este
directorio serán borrados. Los archivos almacenados en esta ubicación siguen siendo
públicos por lo que cualquier otra aplicación podrá acceder a ellos y los podrá leer,
modificar o borrar.
A continuación se muestra el código fuente de una actividad que utiliza la cámara del
dispositivo a través de Intent, para generar imágenes y vídeos.
5
Este método está dispobile desde el nivel de API 8 (Android 2.2). Para versiones anteriores, se deberá utilizar el
método Environment.getExternalStorageDirectory().
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_and_video);
}
if (fileUri != null) {
// Se añade el nombre del archivo al Intent para que sea
// usado por la aplicación Cámara cuando almacene la
// imagen.
// CUIDADO: al utilizar EXTRA_OUTPUT en el result Intent,
// getData() = null
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
// Se lanza el Intent
startActivityForResult(intent,
ACTIVITY_REQUEST_CODE_CAPTURE_PICTURE);
} else
Toast.makeText(this, getString(R.string.error_file_dir),
Toast.LENGTH_LONG).show();
}
if (fileUri != null) {
// Se lanza el Intent
startActivityForResult(intent,
ACTIVITY_REQUEST_CODE_CAPTURE_VIDEO);
} else
Toast.makeText(this, getString(R.string.error_file_dir),
Toast.LENGTH_LONG).show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == ACTIVITY_REQUEST_CODE_CAPTURE_VIDEO) {
// Se comprueba si el resultado de la acción fue
// satisfactorio
if (resultCode == RESULT_OK) {
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.e(getString(R.string.app_name),
"No se ha podido crear el directorio.");
return null;
}
}
} else
return null;
return Uri.fromFile(mediaFile);
}
Para crear una interfaz personalizada para la cámara, dentro de una aplicación, será necesario:
Para detectar en tiempo de ejecución si el dispositivo tiene cámara o no, así como si tiene
autofocus, flash o cámara frontal, se accederá a gestor de paquetes del contexto 6 o,
directamente, a la Cámara:
6
PackageManager es una clase que devuelve distintos tipos de información relacionados con los paquetes de
aplicación instalados en el dispositivo, muchos de los cuales están dedicados a gestionar diversos componentes de
hardware del dispositivo.
Una vez que se ha comprobado que el dispositivo tiene cámara(s), se deberá acceder a la
misma a través de una instancia de la clase Camera. La generación de la instancia (a través de
Camera.open()) puede lanzar una excepción que deberá ser capturada para evitar un FC
(force close) de la aplicación:
Camera c = null;
try {
// Se intenta obtener una instancia de la clase Camera
c = Camera.open();
}
catch (Exception e){
// La cámara no está disponible (está en uso o no existe)
// Toast(…);
}
Después de obtener el acceso a la cámara, se podrá conseguir más información sobre sus
funcionalidades y capacidades por medio del objeto de tipo Camera.Parametes que devuelve
el método Camera.getParameters(). A partir de Android 2.3 se puede averiguar si la cámara
es frontal o no, así como la orientación de la imagen, a través del método
Camera.getCameraInfo().
Para mostrar la imagen en tiempo real que captura la cámara, es necesario crear una clase de
previsualización que extienda de SurfaceView y que será incluida en un layout. La clase
deberá implementar la interfaz SurfaceHolder.Callback para capturar los eventos de
creación, modificación o destrucción de la View contenida en el layout, ya que es necesario
para iniciar o reiniciar la previsualización de la cámara.
@Override
public void surfaceCreated(SurfaceHolder holder) {
// La superficie se ha creado, se dice a la cámara dónde mostrar
// la previsualización
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG_ERROR,
"Error estableciendo la previsualización de la cámara: " +
e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Es mejor liberar la previsualización de la cámara en la
// actividad.
// mCamera.stopPreview();
// mCamera.release();
// mCamera = null;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w,
int h) {
// Se capturarán eventos de cambio de tamaño o rotación de la
// superficie de la previsualización de la cámara.
// Es necesario parar la previsualización antes de redimensionar
// o cambiar sus proporciones.
if (mHolder.getSurface() == null)
// No existe superficie de previsualización
return;
Para usar la clase CameraPreview implementada en la sección anterior se podrá diseñar por
ejemplo un layout que incluya un FrameLayout el cual actuará como contenedor de la clase de
previsualización. Al utilizar un FrameLayout se podrán superponer los botones necesarios para
controlar la cámara, así como información adicional.
<FrameLayout
android:id="@+id/cameraPreview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:padding="10dp" >
<Button
android:id="@+id/buttonCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_weight="1"
android:onClick="cancel"
android:text="@string/cancel" />
<Button
android:id="@+id/buttonCapture"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="20dp"
android:layout_weight="1"
android:onClick="shoot"
android:text="@string/shoot" />
</LinearLayout>
</RelativeLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_preview_layout);
if (mCamera != null) {
// Se crea la previsualización y se asocia al FrameLayout
try {
mCameraPreview = new CameraPreview(this, mCamera);
} catch (InstantiationException e) {
Log.d(TAG_ERROR,
"Error asociando previsualización de cámara al layout: " +
e.getMessage());
}
FrameLayout preview =
(FrameLayout) findViewById(R.id.cameraPreview);
preview.addView(mCameraPreview);
}
}
También es importante que el método onPause() de la actividad libere el recurso Camera para
que otras aplicaciones puedan usarla:
@Override
public void onPause() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
super.onPause(); // Es importante invocar a super() al final
}
}
Grabación de imágenes
Una vez construida la clase que previsualiza la imagen que captura la cámara, así como el
layout que contiene dicha previsualización, se deberán añadir los listeners necesarios en la
interfaz de usuario para realizar las fotos.
Para obtener la imagen que capture la cámara, será necesario invocar al método
Camera.takePicture(). Este método recibe diversos parámetros siendo el último de ellos el
más importante ya que ha de ser una instancia de la interfaz Camera.PictureCallback. El
método callback que implementa esta interfaz, onPictureTaken() es el método que será
invocado cuando se realice una foto, y será el método donde se implemente la grabación de la
foto en una ubicación del dispositivo.
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile =
FileUtils.getExternalMediaFile(FileUtils.MEDIA_TYPE_IMAGE);
if (pictureFile == null) {
Log.d(TAG_ERROR, "Error creando el archivo de imagen.
Comprueba que se tiene permiso de acceso al
almacenamiento.");
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG_ERROR, "Archivo no encontrado: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG_ERROR, "Error al acceder al archivo: " +
e.getMessage());
}
}
};
(La clase FileUtils contiene los métodos de gestión de archivos ya utilizados en un tema
anterior.)
Grabación de vídeo
La grabación de vídeo requiere una gestión cuidadosa y ordenada de la instancia de Camera así
como una coordinación con la clase MediaRecorder, ya que se deberá permitir a dicha clase
acceder al hardware de la cámara, lo cual implicará la gestión de los métodos Camera.lock(),
Camera.unlock() 7, Camera.open() y Camera.release().
7
A partir de Android 4.0, las llamadas a Camera.lock() y Camera.unlock() son gestionadas automáticamente por
el sistema, por lo que, a la hora de grabar vídeo, se podrán obviar los pasos 4.1 y 5.4, en caso de que
MediaRecorder.prepare() no falle.
mCamera = getCameraInstance();
state = MediaRecorderState.INITIAL;
try {
// Se activa la opción de Autofocus en la cámara, en caso de que
// esté disponible
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// La cámara tiene Autofocus, se establece.
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// Se añade a los parámetros de la cámara
mCamera.setParameters(params);
}
// 2: Se conecta la previsualización
mCamera.setPreviewDisplay(mSurfaceHolder);
// 3: Se inicia la previsualización
mCamera.startPreview();
} catch (IOException ioe) {
Log.e(TAG_ERROR, "Error al establecer la previsualización
para el vídeo: " + ioe.getMessage());
ioe.printStackTrace();
}
state = MediaRecorderState.INITIALIZED;
state = MediaRecorderState.DATA_SOURCE_CONFIGURED;
// (Opcional)
mMediaRecorder.setVideoFrameRate(25);
}
Una vez configurado MediaRecorder será necesario esperar a que se cree la superficie que
muestra la previsualización para asignársela y finalizar así la preparación de la videocámara. Se
deberá implementar, por lo tanto, el método callback surfaceCreated() con un código
similar al siguiente:
if (mMediaRecorder != null) {
try {
// 4.2.6: Se establece la Surface para previsualizar el vídeo.
// No se asocia el display hasta que la superficie es creada.
mMediaRecorder.setPreviewDisplay(holder.getSurface());
state = MediaRecorderState.PREPARED;
} catch (IllegalStateException e) {
Log.d(TAG_DEBUG, "IllegalStateException preparando MediaRecorder:
" + e.getMessage());
releaseMediaRecorder();
} catch (IOException e) {
Log.d(TAG_DEBUG, "IOException preparando MediaRecorder: "
+ e.getMessage());
releaseMediaRecorder();
}
}
}
Para finalizar la grabación del vídeo, bastará invocar a su método stop() y liberar la cámara,
así como otros recursos, en caso de que no se vayan a grabar más vídeos:
if (mMediaRecorder != null) {
// Se elimina la configuración de la cámara de vídeo
mMediaRecorder.reset();
state = MediaRecorderState.INITIAL;
mMediaRecorder.release(); // Se libera el objeto
state = MediaRecorderState.RELEASED;
mMediaRecorder = null;
state = MediaRecorderState.NULL;
mCamera.lock(); // Se bloquea la cámara para futuros usos
}
}
Por último, es importante tener en cuenta que los métodos de MediaRecorder hacen que este
adopte uno u otro estado y que, por lo tanto, solo se pueden invocar ciertos métodos cuando
MediaRecorder adopta dichos estados, según el siguiente diagrama: