Descripción general de MediaRecorder

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 uses MIC.

    Nota: La mayoría de las fuentes de audio (incluido DEFAULT) procesan la señal de audio. Para grabar audio sin procesar, selecciona UNPROCESSED. Algunos dispositivos no admiten entradas no procesadas. Primero, llama al AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED) para verificar que esté disponible. Si no es así, intenta usar VOICE_RECOGNITION en su lugar, que no emplea AGC ni supresión de ruido. Puedes usar UNPROCESSED 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.