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
から取得されない点を除き、動画データまたは音声データの書き込みと同じです。代わりに、アプリは、関連付けられたタイムスタンプを含む ByteBuffer
を writeSampleData()
メソッドに渡します。タイムスタンプは、動画トラックや音声トラックと同じタイムベースにする必要があります。
生成された 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; } } }
詳細
以下は音声と動画の録音、録画、保存、再生に関するトピックを扱うページです。