Androidデバイスのマイクからの音量レベルをデシベル(dB)単位で取得する方法について説明します。これには、AudioRecordクラスとMediaRecorderクラスの2つのアプローチがあります。
AudioRecordを使用したデシベル値の取得
AudioRecordクラスは、オーディオデータを直接PCM形式でキャプチャするための低レベルAPIを提供します。これを使用して、オーディオバッファから音量レベルを計算できます。
AudioRecordDemo.java
package com.example.audioanalyzer;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
public class AudioRecordAnalyzer {
private static final String TAG = "AudioRecordAnalyzer";
private static final int SAMPLE_RATE = 8000; // サンプリングレート (Hz)
private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_DEFAULT;
private int bufferSize;
private AudioRecord audioRecorder;
private volatile boolean isAnalyzing = false;
private final Object lock = new Object();
public AudioRecordAnalyzer() {
bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_ENCODING);
if (bufferSize == AudioRecord.ERROR_BAD_VALUE || bufferSize == AudioRecord.ERROR) {
Log.e(TAG, "バッファサイズの取得に失敗しました。");
bufferSize = 2048; // デフォルト値
}
}
/**
* 音量デシベル値の分析を開始します。
*/
public void startVolumeAnalysis() {
if (isAnalyzing) {
Log.w(TAG, "分析は既に実行中です。");
return;
}
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_ENCODING,
bufferSize);
if (audioRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "AudioRecordの初期化に失敗しました。");
return;
}
isAnalyzing = true;
new Thread(this::analyzeAudio).start();
}
/**
* 音量デシベル値の分析を停止します。
*/
public void stopVolumeAnalysis() {
isAnalyzing = false;
if (audioRecorder != null) {
synchronized (lock) {
lock.notifyAll(); // wait() を解除
}
}
}
private void analyzeAudio() {
try {
audioRecorder.startRecording();
short[] audioBuffer = new short[bufferSize];
while (isAnalyzing) {
int bytesRead = audioRecorder.read(audioBuffer, 0, bufferSize);
if (bytesRead <= 0) {
Log.w(TAG, "オーディオデータの読み取りに失敗しました。バイト数: " + bytesRead);
continue;
}
long sumOfSquares = 0;
for (int sample : audioBuffer) {
sumOfSquares += (long)sample * sample;
}
// 平均二乗振幅を計算
double meanSquare = sumOfSquares / (double)bytesRead;
// デシベル値を計算 (0dBFS基準ではない、相対的な値)
// meanSquareが0に近い場合、対数は発散するため、微小な値を加えるか、
// 基準値と比較するなどの処理が必要ですが、ここでは単純計算します。
double decibels = 0;
if (meanSquare > 0) {
decibels = 10 * Math.log10(meanSquare);
}
Log.d(TAG, "音量 (dB): " + decibels);
// 例: 特定の閾値を超えた場合の処理
if (decibels > 80) {
Log.d(TAG, "大きな音を検知しました!");
// ここでクーポン発行などの処理を実装
}
// 100ミリ秒待機 (約10回/秒のサンプリング)
synchronized (lock) {
try {
lock.wait(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 中断フラグを立てる
Log.e(TAG, "分析スレッドが中断されました。", e);
break; // ループを抜ける
}
}
}
} finally {
releaseAudioRecorder();
}
}
private void releaseAudioRecorder() {
if (audioRecorder != null) {
if (audioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecorder.stop();
}
audioRecorder.release();
audioRecorder = null;
Log.i(TAG, "AudioRecord リソースを解放しました。");
}
}
}
MediaRecorderを使用したデシベル値の取得
MediaRecorderクラスは、オーディオやビデオの録音・エンコードに使用される高レベルAPIです。このクラスのgetMaxAmplitude()メソッドを使用すると、現在マイクで拾っている音の最大振幅を取得でき、これを用いて相対的なデシベル値を計算できます。
MediaRecorderAnalyzer.java
package com.example.audioanalyzer;
import android.media.MediaRecorder;
import android.os.Handler;
import android.util.Log;
import java.io.File;
import java.io.IOException;
public class MediaRecorderAnalyzer {
private static final String TAG = "MediaRecorderAnalyzer";
private static final int MAX_RECORDING_DURATION_MS = 60 * 10 * 1000; // 最大録音時間 10分
private static final int SAMPLE_INTERVAL_MS = 100; // 音量測定の間隔 (ミリ秒)
private MediaRecorder mediaRecorder;
private String outputFilePath;
private long recordingStartTime;
private final Handler statusHandler = new Handler();
private volatile boolean isRecording = false;
private static final int AMPLITUDE_BASE = 1; // 振幅計算の基準値
public MediaRecorderAnalyzer() {
// デフォルトでは /dev/null に出力 (録音はしないが、振幅測定は可能)
this.outputFilePath = "/dev/null";
}
public MediaRecorderAnalyzer(File outputFile) {
if (outputFile == null) {
throw new IllegalArgumentException("出力ファイルがnullです。");
}
this.outputFilePath = outputFile.getAbsolutePath();
}
/**
* マイクの音量測定を開始します。
*/
public void startVolumeMeasurement() {
if (isRecording) {
Log.w(TAG, "録音は既に開始されています。");
return;
}
mediaRecorder = new MediaRecorder();
try {
// 1. オーディオソースの設定
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 2. 出力フォーマットの設定 (録音しない場合はDEFAULTで良い)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
// 3. オーディオエンコーダの設定 (録音しない場合はAMR_NBで良い)
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 4. 出力ファイルの指定
mediaRecorder.setOutputFile(outputFilePath);
// 5. 最大録音時間の指定 (任意)
mediaRecorder.setMaxDuration(MAX_RECORDING_DURATION_MS);
// 6. 準備
mediaRecorder.prepare();
// 7. 録音開始
mediaRecorder.start();
isRecording = true;
recordingStartTime = System.currentTimeMillis();
Log.i(TAG, "録音開始: " + recordingStartTime);
// 音量測定を開始
updateVolumeStatus();
} catch (IllegalStateException e) {
Log.e(TAG, "録音開始の準備中にエラーが発生しました (IllegalStateException): " + e.getMessage(), e);
releaseMediaRecorder();
} catch (IOException e) {
Log.e(TAG, "録音開始の準備中にエラーが発生しました (IOException): " + e.getMessage(), e);
releaseMediaRecorder();
} catch (SecurityException e) {
Log.e(TAG, "マイクへのアクセス権限がありません: " + e.getMessage(), e);
releaseMediaRecorder();
}
}
/**
* マイクの音量測定を停止します。
* @return 録音時間 (ミリ秒)
*/
public long stopVolumeMeasurement() {
if (!isRecording || mediaRecorder == null) {
return 0L;
}
long endTime = System.currentTimeMillis();
Log.i(TAG, "録音終了: " + endTime);
// 音量測定タイマーを停止
statusHandler.removeCallbacks(volumeUpdateRunnable);
try {
mediaRecorder.stop();
} catch (RuntimeException e) {
// stop()がIllegalStateExceptionを投げる場合がある (録音が短すぎる、など)
Log.w(TAG, "録音停止中にエラーが発生しました: " + e.getMessage(), e);
} finally {
releaseMediaRecorder();
}
isRecording = false;
return endTime - recordingStartTime;
}
// 音量測定を実行するRunnable
private final Runnable volumeUpdateRunnable = new Runnable() {
@Override
public void run() {
updateVolumeStatus();
}
};
/**
* 現在のマイクの音量レベルを更新し、ログに出力します。
*/
private void updateVolumeStatus() {
if (mediaRecorder != null && isRecording) {
// 最大振幅を取得 (0以上)
int maxAmplitude = mediaRecorder.getMaxAmplitude();
double decibelValue = 0;
if (maxAmplitude > AMPLITUDE_BASE) {
// 振幅からデシベル値を計算 (相対値)
// dB = 20 * log10(Amplitude / ReferenceAmplitude)
// ここでのReferenceAmplitudeは1として計算
decibelValue = 20 * Math.log10((double) maxAmplitude / AMPLITUDE_BASE);
} else if (maxAmplitude > 0) {
// 振幅が1未満の場合 (通常は起こりにくいが念のため)
decibelValue = 20 * Math.log10((double) maxAmplitude);
}
// デシベル値が負にならないように調整 (通常、振幅が1以上なら正の値になる)
if (decibelValue < 0) decibelValue = 0;
Log.d(TAG, "音量 (dB): " + decibelValue + " (振幅: " + maxAmplitude + ")");
// 次回の測定をスケジュール
statusHandler.postDelayed(volumeUpdateRunnable, SAMPLE_INTERVAL_MS);
}
}
/**
* MediaRecorderオブジェクトを解放します。
*/
private void releaseMediaRecorder() {
if (mediaRecorder != null) {
try {
mediaRecorder.reset(); // リセット
mediaRecorder.release(); // リソース解放
} catch (Exception e) {
Log.e(TAG, "MediaRecorderの解放中にエラーが発生しました。", e);
} finally {
mediaRecorder = null;
Log.i(TAG, "MediaRecorder リソースを解放しました。");
}
}
}
}
MainActivityでの利用例
これらのクラスをMainActivityから呼び出して、音量取得機能を実装します。
MainActivity.java
package com.example.audioanalyzer;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.Manifest;
import android.content.pm.PackageManager;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends AppCompatActivity {
private static final int RECORD_AUDIO_PERMISSION_CODE = 101;
// private AudioRecordAnalyzer recordAnalyzer; // AudioRecordを使用する場合
private MediaRecorderAnalyzer recorderAnalyzer; // MediaRecorderを使用する場合
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// マイク録音権限の確認と要求
if (checkMicrophonePermission()) {
startAudioAnalysis();
}
}
private boolean checkMicrophonePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
// 権限がない場合、リクエストする
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
RECORD_AUDIO_PERMISSION_CODE);
return false;
}
return true; // 権限がある場合
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == RECORD_AUDIO_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 権限が付与された場合、分析を開始
startAudioAnalysis();
} else {
// 権限が付与されなかった場合、ユーザーに通知
Toast.makeText(this, "マイク録音権限が必要です。", Toast.LENGTH_LONG).show();
// 必要に応じて、権限がない場合のUIを表示したり、アプリを終了したりする
}
}
}
private void startAudioAnalysis() {
// --- AudioRecord を使用する場合 ---
// recordAnalyzer = new AudioRecordAnalyzer();
// recordAnalyzer.startVolumeAnalysis();
// --- MediaRecorder を使用する場合 ---
// ファイルを指定しない場合は、/dev/null に出力されます
recorderAnalyzer = new MediaRecorderAnalyzer();
// recorderAnalyzer = new MediaRecorderAnalyzer(new File(getExternalCacheDir(), "audio_measurement.amr")); // ファイル出力する場合
recorderAnalyzer.startVolumeMeasurement();
Toast.makeText(this, "音量測定を開始しました。", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
// Activity終了時にリソースを解放
// if (recordAnalyzer != null) {
// recordAnalyzer.stopVolumeAnalysis();
// }
if (recorderAnalyzer != null) {
recorderAnalyzer.stopVolumeMeasurement();
}
}
}
AndroidManifest.xml の設定
マイクを使用するために、AndroidManifest.xml ファイルに以下のパーミッションを追加する必要があります。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- ファイルに録音する場合は、ストレージ権限も必要になる場合があります -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> -->
Android 6.0 (APIレベル23) 以降では、実行時にユーザーからマイク録音の権限をリクエストする必要があります。上記のMainActivityのコード例では、その処理も含まれています。