Descripción general de MediaRecorder

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 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 sin procesar. Primero, llama al AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED) para verificar que esté disponible. Si no es así, intenta usar VOICE_RECOGNITION, 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 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.