Visão geral do MediaRecorder

O framework multimídia do Android inclui suporte para captura e codificação de vários formatos comuns de áudio e vídeo. Você pode usar as APIs MediaRecorder se o hardware do dispositivo oferecer suporte a isso.

Este documento mostra como usar o MediaRecorder para criar um aplicativo que captura áudio de um microfone do dispositivo, salvar e reproduzir o áudio (com MediaPlayer). Para gravar vídeos, você precisa usar a câmera do dispositivo com o MediaRecorder. Isso é descrito no guia Câmera.

Observação:o Android Emulator não pode gravar áudio. Teste seu código em um dispositivo real que possa fazer gravações.

Como solicitar permissão para gravar áudio

Para gravar, seu app precisa informar ao usuário que ele acessará a entrada de áudio do dispositivo. Inclua esta tag de permissão no arquivo de manifesto do app:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

A RECORD_AUDIO é considerada uma permissão "perigosa" porque pode representar um risco à privacidade do usuário. A partir do Android 6.0 (nível 23 da API), um app que usa uma permissão perigosa precisa solicitar a aprovação do usuário no momento da execução. Depois que o usuário concede a permissão, o app precisa se lembrar e não perguntar novamente. O exemplo de código abaixo mostra como implementar esse comportamento usando ActivityCompat.requestPermissions().

Como criar e executar um MediaRecorder

Inicialize uma nova instância de MediaRecorder com estas chamadas:

  • Defina a fonte de áudio com setAudioSource(). Você provavelmente vai usar MIC.

    Observação:a maioria das fontes de áudio (incluindo DEFAULT) aplica um processamento ao sinal de áudio. Para gravar áudio bruto, selecione UNPROCESSED. Alguns dispositivos não oferecem suporte a entradas não processadas. Primeiro, chame AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED) para confirmar que ele está disponível. Se não estiver, tente usar VOICE_RECOGNITION, que não emprega AGC nem supressão de ruído. Você pode usar UNPROCESSED como uma fonte de áudio mesmo quando a propriedade não tiver suporte, mas não há garantia de que o sinal não será processado ou não nesse caso.

  • Defina o formato do arquivo de saída usando setOutputFormat(). A partir do Android 8.0 (API de nível 26), MediaRecorder oferece suporte ao formato MPEG2_TS, que é útil para streaming:

    Kotlin

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
    

    Java

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
    
  • Defina o nome do arquivo de saída usando setOutputFile(). É necessário especificar um descritor de arquivo que represente um arquivo real.
  • Defina o codificador de áudio com setAudioEncoder().
  • Conclua a inicialização chamando prepare().

Inicie e pare o gravador chamando start() e stop(), respectivamente.

Quando terminar de usar a instância MediaRecorder, libere os recursos o mais rápido possível chamando release().

Observação:em dispositivos com o Android 9 (nível 28 da API) ou versões mais recentes, os apps em execução em segundo plano não podem acessar o microfone. Portanto, seu app precisa gravar áudio somente quando estiver em primeiro plano ou quando você incluir uma instância de MediaRecorder em um serviço em primeiro plano.

Como usar o MediaMuxer para gravar vários canais

A partir do Android 8.0 (nível 26 da API), você pode usar uma MediaMuxer para gravar vários streams simultâneos de áudio e vídeo. Nas versões anteriores do Android, você só pode gravar uma faixa de áudio e/ou de vídeo por vez.

Use o método addTrack() para combinar várias faixas.

Você também pode adicionar uma ou mais faixas de metadados com informações personalizadas para cada frame, mas somente a contêineres de MP4. Seu app define o formato e o conteúdo dos metadados.

Como adicionar metadados

Metadados podem ser úteis para processamento off-line. Por exemplo, os dados capturados pelo sensor de giroscópio podem ser usados para estabilizar o vídeo.

Quando você adiciona uma faixa de metadados, o formato MIME da faixa precisa começar com o prefixo application/. Escrever metadados é o mesmo que gravar dados de vídeo ou áudio, com a diferença de que os dados não vêm de um MediaCodec. Em vez disso, o app transmite ao método writeSampleData() um ByteBuffer com um carimbo de data/hora associado. O carimbo de data/hora precisa estar na mesma base de tempo que as faixas de vídeo e áudio.

O arquivo MP4 gerado usa o TextMetaDataSampleEntry definido na seção 12.3.3.2 da especificação ISO BMFF para sinalizar o formato MIME dos metadados. Quando você usa um MediaExtractor para extrair um arquivo que contém faixas de metadados, o formato MIME dos metadados aparece como uma instância de MediaFormat.

Exemplo de código

O exemplo do MediaRecorder demonstra como fazer uma gravação de vídeo usando o MediaRecorder e a API Camera.

A atividade de exemplo abaixo mostra como usar o MediaRecorder para gravar um arquivo de áudio. Ela também usa MediaPlayer para reproduzir o áudio.

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;
        }
    }
}

Saiba mais

Essas páginas abrangem tópicos relacionados à gravação, ao armazenamento e à reprodução de áudio e vídeo.