Androidにおける音量デシベル値の取得

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のコード例では、その処理も含まれています。

タグ: Android AudioRecord MediaRecorder デシベル 音量測定

7月14日 17:08 投稿