El framework de contenido multimedia de Android admite la captura y codificación de una variedad de formatos comunes de audio y video. Puedes usar las APIs de MediaRecorder
si son compatibles con el hardware del dispositivo.
En este documento, se muestra cómo usar MediaRecorder
para escribir una aplicación que capture audio del micrófono de un dispositivo, lo guarde y lo reproduzca (con MediaPlayer
). Para grabar video, deberás usar la cámara del dispositivo junto con MediaRecorder
. 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 debe indicarle al usuario que accederá 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 (nivel de API 23), una app que use un permiso riesgoso debe solicitar la aprobación del usuario durante el tiempo de ejecución. Una vez que el usuario haya otorgado el permiso, la app debería recordarlo y no volver a preguntar. En el siguiente código de muestra, se indica cómo implementar este comportamiento con ActivityCompat.requestPermissions()
.
Cómo crear y ejecutar un MediaRecorder
Inicializa una instancia nueva de MediaRecorder
con las siguientes llamadas:
- Configura la fuente de audio mediante
setAudioSource()
. Es probable que 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 sin procesar. Primero, llama alAudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED)
para verificar que esté disponible. Si no es así, intenta usarVOICE_RECOGNITION
, 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 no se procese o no en ese caso. - Configura el formato de 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);
- Establece 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()
, respectivamente, para iniciar y detener la grabadora.
Cuando termines de usar la instancia MediaRecorder
, llama a release()
para liberar sus recursos lo antes posible.
Nota: En los dispositivos con Android 9 (nivel de API 28) o versiones posteriores, las apps que se ejecutan en segundo plano no pueden acceder al micrófono. Por lo tanto, tu app debe 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 (nivel de API 26), puedes usar un MediaMuxer
para grabar varias transmisiones de audio y video simultáneas. En versiones anteriores de Android, solo puedes grabar una pista de audio o una pista de video a la vez.
Usa el método addTrack()
para mezclar varias pistas.
También puedes agregar una o más pistas de metadatos 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 capturados con el sensor giroscópico se podrían usar para realizar la estabilización de video.
Cuando agregas una pista de metadatos, el formato MIME de la pista debe comenzar con el prefijo application/
. Escribir metadatos es lo mismo que escribir datos de audio o video, excepto que los datos no provienen de un MediaCodec
. En cambio, la app 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
.
Código de muestra
En el ejemplo de MediaRecorder, se muestra cómo realizar una grabación de video con MediaRecorder y la API de Camera.
En la siguiente actividad de ejemplo, se muestra cómo usar MediaRecorder
para grabar un archivo de audio. También usa 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 analizan temas relacionados con la grabación, el almacenamiento y la reproducción de audio y video.