兩種主流方案
| 方案 | 適用場景 | 優點 | 缺點 |
| MediaRecorder.getMaxAmplitude() | 錄音/語音輸入時即時音量條 | API 簡單、無權限顧慮 (已有 RECORD_AUDIO) | 需輪詢、數值範圍 0~32767 需自行映射 |
| Visualizer (API 9+) | 播放音樂/系統聲音波形、FFT 頻譜 | 提供波形 (byte[]) 與 FFT (float[])、高效原生 | 需 `MODIFY_AUDIO_SETTINGS` 權限、僅能掛載播放 Session |
方案一:MediaRecorder 即時音量 (適合「按住說話」)
public class VoiceLevelMeter {
private MediaRecorder recorder;
private Runnable pollTask;
private Handler handler = new Handler(Looper.getMainLooper());
private OnLevelListener listener;
public interface OnLevelListener {
void onLevel(float level); // 0.0f ~ 1.0f
}
public void start(OnLevelListener listener) {
this.listener = listener;
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile("/dev/null"); // 不存檔
try {
recorder.prepare();
recorder.start();
} catch (IOException e) { e.printStackTrace(); return; }
pollTask = new Runnable() {
@Override public void run() {
if (recorder != null) {
int amp = recorder.getMaxAmplitude(); // 0~32767
float level = Math.min(amp / 32767f, 1f);
listener.onLevel(level);
handler.postDelayed(this, 50); // 20fps
}
}
};
handler.post(pollTask);
}
public void stop() {
handler.removeCallbacks(pollTask);
if (recorder != null) {
recorder.stop();
recorder.release();
recorder = null;
}
}
}
使用方式 (按住說話按鈕)
VoiceLevelMeter meter = new VoiceLevelMeter();
btnSpeak.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
meter.start(level -> {
// 更新 UI: 波形圖、音量條、圖片縮放等
waveView.setLevel(level);
});
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
meter.stop();
// 送出語音辨識/上傳
return true;
}
return false;
});
開源專案參考
關鍵提醒
- **權限**:`RECORD_AUDIO` (危險權限,**運行時請求**)。
- **生命週期**:`onPause/onStop` 必須呼叫 `meter.stop()` 釋放 `MediaRecorder`,否則下次啟動失敗。
- **數值平滑**:原始振幅波動大,建議用 **EMA (指數移動平均)** 平滑:`smooth = 0.3 * current + 0.7 * prev`。