MediaRecorder の概要

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

このドキュメントでは、MediaRecorder を使用して、デバイスのマイクから音声をキャプチャし、その音声を保存して再生する(MediaPlayer を使用)アプリを作成する方法について説明します。動画を録画するには、デバイスのカメラを 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 つの音声トラックまたは 1 つの動画トラックのみです。

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

フレームごとにカスタム情報を含む 1 つ以上のメタデータ トラックを追加することもできますが、MP4 コンテナにのみ追加できます。アプリではメタデータの形式とコンテンツを定義します。

メタデータを追加する

メタデータをオフライン処理に活用できます。たとえば、ジャイロセンサーからキャプチャされたデータは、動画の手ぶれ補正の実行に使用できます。

メタデータ トラックを追加する場合、トラックの MIME 形式の先頭は application/ の接頭辞にする必要があります。メタデータの書き込みは、データが MediaCodec から取得されない点を除き、動画データまたは音声データの書き込みと同じです。代わりに、アプリは、関連付けられたタイムスタンプを含む ByteBufferwriteSampleData() メソッドに渡します。タイムスタンプは、動画トラックや音声トラックと同じタイムベースにする必要があります。

生成された MP4 ファイルは、ISO BMFF 仕様のセクション 12.3.3.2 で定義されている TextMetaDataSampleEntry を使用して、メタデータの 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;
        }
    }
}

詳細

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