MediaRecorder の概要

Android マルチメディア フレームワークは、さまざまな形式の一般的な音声と動画に対するキャプチャとエンコードをサポートしています。デバイス ハードウェアでサポートされている場合、MediaRecorder API を使用できます。

このドキュメントでは、デバイスのマイクから音声をキャプチャし、音声を保存し、MediaPlayer を使って再生するアプリを MediaRecorder を使用して作成する方法について紹介します。動画を録画するには、デバイスのカメラと MediaRecorder を使用する必要があります。これについては、カメラのガイドをご覧ください。

注: Android Emulator では録音できません。必ず、録音できる実機を使ってコードをテストしてください。

録音する権限をリクエストする

録音するには、アプリがデバイスの音声入力にアクセスすることをユーザーに知らせる必要があります。アプリのマニフェスト ファイルに次の権限タグを含めてください。

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

RECORD_AUDIO はユーザーのプライバシーを危険にさらすおそれがあるため、「危険な」権限とみなされます。Android 6.0(API レベル 23)以降、危険な権限を使用するアプリでは、ランタイムにユーザーの承認を求める必要があります。ユーザーから権限を与えられたら、アプリはそれを記憶し、再度求めないようにします。下記のサンプルコードは、ActivityCompat.requestPermissions() を使ってこの動作を実装する方法を示します。

MediaRecorder を作成して実行する

以下の呼び出しを行って、MediaRecorder の新しいインスタンスを初期化します。

  • setAudioSource() を呼び出して音源を設定します。通常は MIC を使用します。

    注: DEFAULT も含めて音源のほとんどは、オーディオ信号になんらかの加工をします。未加工の音声を録音するには、UNPROCESSED を選択します。未加工の指定に対応していないデバイスもあります。先に AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED) を呼び出して、対応しているかどうかを確認してください。対応していない場合は、代わりに VOICE_RECOGNITION を指定してみます。これは、AGC やノイズ キャンセレーションを行いません。対応していない場合でも UNPROCESSED を音源として使用できます。ただしその場合、未加工の信号がかどうかは保証されません。

  • setOutputFormat() を呼び出して、出力ファイル形式を設定します。Android 8.0(API レベル 26)以降、MediaRecorder は MPEG2_TS 形式をサポートしています。これはストリーミングに便利です。

    Kotlin

        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
        

    Java

        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
        
  • setOutputFile() を呼び出して、出力ファイル名を設定します。実際のファイルを示すファイル記述子を指定する必要があります。
  • setAudioEncoder() を呼び出して、音声エンコーダを設定します。
  • prepare() を呼び出して、初期化を完了します。

レコーダーの開始と停止には、start()stop() をそれぞれ呼び出します。

MediaRecorder インスタンスの使用を終えたら、できるだけ早く release() を呼び出してそのリソースを解放します。

注: Android 9(API レベル 28)以上を搭載するデバイスの場合、バックグラウンドで実行中のアプリはマイクにアクセスできません。そのためアプリの録音は、フォアグラウンドのとき、またはフォアグラウンド サービス内に MediaRecorder のインスタンスを含めているときのみ行う必要があります。

MediaMuxer を使って複数チャンネルを記録する

Android 8.0(API レベル 26)以降では、MediaMuxer を使って同時に複数の音声ストリームと動画ストリームを記録できます。Android のそれより前のバージョンでは、音声トラックと動画トラックを 1 つずつしか記録できませんでした。

複数トラックをミキシングするには、addTrack() メソッドを使用します。

また、MP4 コンテナの場合だけですが、フレームごとにカスタム情報を含むメタデータ トラックを追加することもできます(複数可)。アプリではメタデータの形式とコンテンツを定義します。

メタデータを追加する

メタデータをオフライン処理に活用できます。たとえば、ジャイロ センサーからキャプチャしたデータを使って動画を安定させることが考えられます。

メタデータ トラックを追加する場合、トラックの MIME 形式の先頭には application/ を指定する必要があります。メタデータの書き込みは、動画データや音声データの書き込みと同じですが、MediaCodec からデータを取得するわけではありません。その代わりにアプリが ByteBuffer と関連タイムスタンプを writeSampleData() メソッドに渡します。タイムスタンプは、動画トラック、音声トラックと同じタイムベースにする必要があります。

生成された MP4 ファイルでは TextMetaDataSampleEntry を使用します。これは、ISO BMFF 規格 12.3.3.2 項で定義されており、メタデータの MIME 形式を知らせます。MediaExtractor を使ってメタデータ トラックを含むファイルを抽出する場合、メタデータの MIME 形式は MediaFormat のインスタンスとして表されます。

サンプルコード

MediaRecorder のこのサンプルは、MediaRecorder と Camera API を使って動画を録画する方法を紹介します。

下記のサンプル アクティビティでは、MediaRecorder を使って音声ファイルを録音する方法を示します。さらに MediaPlayer を使ってその音声を再生します。

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

詳細

以下は音声と動画の録音、録画、保存、再生に関するトピックを扱うページです。