El marco de trabajo de contenido multimedia de Android admite la captura y la codificación de una variedad de formatos comunes de audio y video. Puedes usar las API de MediaRecorder
si son compatibles con el hardware del dispositivo.
En este documento, se muestra cómo usar MediaRecorder
para escribir una app que capture audio del micrófono de un dispositivo, lo guarde y lo reproduzca (con MediaPlayer
). Para grabar video, tendrás que usar la cámara del dispositivo junto con . Esto se describe en la guía de la cámara.
Nota: Android Emulator no puede grabar audio. Asegúrate de probar el código en un dispositivo real que pueda grabar.
Cómo solicitar permiso para grabar audio
Para poder grabar, la app le debe avisar al usuario que tendrá acceso a la entrada de audio del dispositivo. Tienes que incluir esta etiqueta de permiso en el archivo de manifiesto de la app:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
RECORD_AUDIO
se considera un permiso "peligroso" porque puede representar un riesgo para la privacidad del usuario. A partir de Android 6.0 (API nivel 23), toda app que use un permiso peligroso tiene que solicitar la aprobación del usuario en el momento de ejecución. Una vez que el usuario haya otorgado el permiso, la aplicación debe recordarlo y no volver a preguntar. En el código de muestra que aparece a continuación, se muestra cómo implementar este comportamiento mediante ActivityCompat.requestPermissions()
.
Cómo crear y ejecutar un MediaRecorder
Inicializa una nueva instancia de MediaRecorder
con las siguientes llamadas:
- Configura la fuente de audio mediante
setAudioSource()
. Probablemente usesMIC
.Nota: La mayoría de las fuentes de audio (incluido
DEFAULT
) procesan la señal de audio. Para grabar audio sin procesar, seleccionaUNPROCESSED
. Algunos dispositivos no admiten entradas no procesadas. Primero, llama alAudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED)
para verificar que esté disponible. Si no es así, intenta usarVOICE_RECOGNITION
en su lugar, que no emplea AGC ni supresión de ruido. Puedes usarUNPROCESSED
como fuente de audio incluso cuando la propiedad no sea compatible, pero no hay garantía de que la señal se vaya a procesar (o no) en ese caso. - Configura el formato del archivo de salida mediante
setOutputFormat()
. Ten en cuenta que a partir de Android 8.0 (API nivel 26)MediaRecorder
es compatible con el formato MPEG2_TS, que sirve para transmitir:Kotlin
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
Java
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
- Configura el nombre del archivo de salida mediante
setOutputFile()
. Tienes que especificar un descriptor del archivo que represente un archivo real. - Configura el codificador de audio con
setAudioEncoder()
. - Llama a
prepare()
para completar la inicialización.
Llama a start()
y stop()
para iniciar la grabadora y para detenerla, respectivamente.
Cuando hayas terminado con la instancia MediaRecorder
, llama a release()
para liberar sus recursos lo antes posible.
Nota: En los dispositivos que ejecutan Android 9 (API nivel 28) o superior, las apps que se ejecutan en segundo plano no pueden acceder al micrófono. Por lo tanto, tu app tiene que grabar audio solo cuando está en primer plano o cuando incluyes una instancia de MediaRecorder
en un servicio en primer plano.
Cómo usar MediaMuxer para grabar varios canales
A partir de Android 8.0 (API nivel 26), puedes usar un MediaMuxer
para grabar varias transmisiones de video y audio simultáneas. Con versiones anteriores de Android, solo puedes grabar una pista de audio y/o una pista de video por vez.
Usa el método addTrack()
para mezclar varias pistas.
También puedes agregar una pista de metadatos o más con información personalizada para cada marco, pero solo a contenedores MP4. La app define el formato y el contenido de los metadatos.
Cómo agregar metadatos
Los metadatos pueden ser útiles para el procesamiento sin conexión. Por ejemplo, los datos que captura el sensor giroscópico se podrían usar para llevar a cabo la estabilización de video.
Cuando agregas una pista de metadatos, el formato MIME de la pista tiene que comenzar con el prefijo application/
. Escribir metadatos es lo mismo que escribir datos de video o audio, excepto que los datos no provienen de un MediaCodec
. En cambio, la aplicación pasa un ByteBuffer
con una marca de tiempo asociada al método writeSampleData()
.
La marca de tiempo tiene que estar en la misma base de tiempo que las pistas de video y audio.
El archivo MP4 generado usa el TextMetaDataSampleEntry
definido en la sección 12.3.3.2 de la especificación ISO BMFF para indicar el formato MIME de los metadatos. Cuando usas un MediaExtractor
para extraer un archivo que contiene pistas de metadatos, el formato MIME de los metadatos aparece como una instancia de MediaFormat
.
Ejemplo de código
El ejemplo MediaRecorder muestra cómo realizar una grabación de video con MediaRecorder y la API de la cámara.
En la siguiente actividad de ejemplo, se muestra cómo usar MediaRecorder
para grabar un archivo de audio. Además, se utiliza MediaPlayer
para reproducir el audio.
Kotlin
package com.android.audiorecordtest import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.media.MediaPlayer import android.media.MediaRecorder import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.util.Log import android.view.View.OnClickListener import android.view.ViewGroup import android.widget.Button import android.widget.LinearLayout import java.io.IOException private const val LOG_TAG = "AudioRecordTest" private const val REQUEST_RECORD_AUDIO_PERMISSION = 200 class AudioRecordTest : AppCompatActivity() { private var fileName: String = "" private var recordButton: RecordButton? = null private var recorder: MediaRecorder? = null private var playButton: PlayButton? = null private var player: MediaPlayer? = null // Requesting permission to RECORD_AUDIO private var permissionToRecordAccepted = false private var permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO) override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) { grantResults[0] == PackageManager.PERMISSION_GRANTED } else { false } if (!permissionToRecordAccepted) finish() } private fun onRecord(start: Boolean) = if (start) { startRecording() } else { stopRecording() } private fun onPlay(start: Boolean) = if (start) { startPlaying() } else { stopPlaying() } private fun startPlaying() { player = MediaPlayer().apply { try { setDataSource(fileName) prepare() start() } catch (e: IOException) { Log.e(LOG_TAG, "prepare() failed") } } } private fun stopPlaying() { player?.release() player = null } private fun startRecording() { recorder = MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) setOutputFile(fileName) setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) try { prepare() } catch (e: IOException) { Log.e(LOG_TAG, "prepare() failed") } start() } } private fun stopRecording() { recorder?.apply { stop() release() } recorder = null } internal inner class RecordButton(ctx: Context) : Button(ctx) { var mStartRecording = true var clicker: OnClickListener = OnClickListener { onRecord(mStartRecording) text = when (mStartRecording) { true -> "Stop recording" false -> "Start recording" } mStartRecording = !mStartRecording } init { text = "Start recording" setOnClickListener(clicker) } } internal inner class PlayButton(ctx: Context) : Button(ctx) { var mStartPlaying = true var clicker: OnClickListener = OnClickListener { onPlay(mStartPlaying) text = when (mStartPlaying) { true -> "Stop playing" false -> "Start playing" } mStartPlaying = !mStartPlaying } init { text = "Start playing" setOnClickListener(clicker) } } override fun onCreate(icicle: Bundle?) { super.onCreate(icicle) // Record to the external cache directory for visibility fileName = "${externalCacheDir.absolutePath}/audiorecordtest.3gp" ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION) recordButton = RecordButton(this) playButton = PlayButton(this) val ll = LinearLayout(this).apply { addView(recordButton, LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0f)) addView(playButton, LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0f)) } setContentView(ll) } override fun onStop() { super.onStop() recorder?.release() recorder = null player?.release() player = null } }
Java
package com.android.audiorecordtest; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import java.io.IOException; public class AudioRecordTest extends AppCompatActivity { private static final String LOG_TAG = "AudioRecordTest"; private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200; private static String fileName = null; private RecordButton recordButton = null; private MediaRecorder recorder = null; private PlayButton playButton = null; private MediaPlayer player = null; // Requesting permission to RECORD_AUDIO private boolean permissionToRecordAccepted = false; private String [] permissions = {Manifest.permission.RECORD_AUDIO}; @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){ case REQUEST_RECORD_AUDIO_PERMISSION: permissionToRecordAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED; break; } if (!permissionToRecordAccepted ) finish(); } private void onRecord(boolean start) { if (start) { startRecording(); } else { stopRecording(); } } private void onPlay(boolean start) { if (start) { startPlaying(); } else { stopPlaying(); } } private void startPlaying() { player = new MediaPlayer(); try { player.setDataSource(fileName); player.prepare(); player.start(); } catch (IOException e) { Log.e(LOG_TAG, "prepare() failed"); } } private void stopPlaying() { player.release(); player = null; } private void startRecording() { recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setOutputFile(fileName); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { recorder.prepare(); } catch (IOException e) { Log.e(LOG_TAG, "prepare() failed"); } recorder.start(); } private void stopRecording() { recorder.stop(); recorder.release(); recorder = null; } class RecordButton extends Button { boolean mStartRecording = true; OnClickListener clicker = new OnClickListener() { public void onClick(View v) { onRecord(mStartRecording); if (mStartRecording) { setText("Stop recording"); } else { setText("Start recording"); } mStartRecording = !mStartRecording; } }; public RecordButton(Context ctx) { super(ctx); setText("Start recording"); setOnClickListener(clicker); } } class PlayButton extends Button { boolean mStartPlaying = true; OnClickListener clicker = new OnClickListener() { public void onClick(View v) { onPlay(mStartPlaying); if (mStartPlaying) { setText("Stop playing"); } else { setText("Start playing"); } mStartPlaying = !mStartPlaying; } }; public PlayButton(Context ctx) { super(ctx); setText("Start playing"); setOnClickListener(clicker); } } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); // Record to the external cache directory for visibility fileName = getExternalCacheDir().getAbsolutePath(); fileName += "/audiorecordtest.3gp"; ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION); LinearLayout ll = new LinearLayout(this); recordButton = new RecordButton(this); ll.addView(recordButton, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0)); playButton = new PlayButton(this); ll.addView(playButton, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0)); setContentView(ll); } @Override public void onStop() { super.onStop(); if (recorder != null) { recorder.release(); recorder = null; } if (player != null) { player.release(); player = null; } } }
Más información
En estas páginas, se abordan temas relacionados con la grabación, el almacenamiento y la reproducción de audio y video.