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; } } }
詳細
以下は音声と動画の録音、録画、保存、再生に関するトピックを扱うページです。