Android移動開發-音樂的示波器、均衡、重低音和音場的實現
阿新 • • 發佈:2019-02-17
本Demo無須介面佈局檔案,使用一個LinearLayout容器來盛裝一個示波器View元件,該示波器View元件將負責繪製Visualizer傳過來的資料:LinearLayout新增多個SeekBar來控制Equalizer支援的所有頻率的均衡值;LinearLayout還新增一個SeekBar來控制重低音的強度;LinearLayout還新增一個Spinner讓使用者選擇預設音場。
- MainActivity邏輯程式碼:
package com.fukaimei.mediaplayertest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.PresetReverb;
import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
// 定義播放聲音的MediaPlayer
private MediaPlayer mPlayer;
// 定義系統的示波器
private Visualizer mVisualizer;
// 定義系統的均衡器
private Equalizer mEqualizer;
// 定義系統的重低音控制器
private BassBoost mBass;
// 定義系統的預設音場控制器
private PresetReverb mPresetReverb;
private LinearLayout layout;
private List<Short> reverbNames = new ArrayList<Short>();
private List<String> reverbVals = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 設定控制音樂聲音
setVolumeControlStream(AudioManager.STREAM_MUSIC);
layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
setContentView(layout);
// 建立MediaPlayer物件
mPlayer = MediaPlayer.create(this, R.raw.xijuzhiwang);
// 初始化示波器
setupVisualizer();
// 初始化均衡控制器
setupEqualizer();
// 初始化重低音控制器
setupBassBoost();
// 初始化預設音場控制器
setupPresetReverb();
// 開發播放音樂
mPlayer.start();
}
private void setupVisualizer() {
// 建立MyVisualizerView元件,用於顯示波形圖
final MyVisualizerView mVisualizerView =
new MyVisualizerView(this);
mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
(int) (120f * getResources().getDisplayMetrics().density)));
// 將MyVisualizerView元件新增到layout容器中
layout.addView(mVisualizerView);
// 以MediaPlayer的AudioSessionId建立Visualizer
// 相當於設定Visualizer負責顯示該MediaPlayer的音訊資料
mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
// 為mVisualizer設定監聽器
mVisualizer.setDataCaptureListener(
new Visualizer.OnDataCaptureListener() {
@Override
public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
}
@Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
// 用waveform波形資料更新mVisualizerView元件
mVisualizerView.updateVisualizer(waveform);
}
}, Visualizer.getMaxCaptureRate() / 2, true, false);
mVisualizer.setEnabled(true);
}
// 初始化均衡控制器的方法
private void setupEqualizer() {
// 以MediaPlayer的AudioSessionId建立Equalizer
// 相當於設定Equalizer負責控制該MediaPlayer
mEqualizer = new Equalizer(0, mPlayer.getAudioSessionId());
// 啟用均衡控制效果
mEqualizer.setEnabled(true);
TextView eqTitle = new TextView(this);
eqTitle.setText("均衡器:");
layout.addView(eqTitle);
// 獲取均衡控制器支援最小值和最大值
final short minEQLevel = mEqualizer.getBandLevelRange()[0];
short maxEQLevel = mEqualizer.getBandLevelRange()[1];
// 獲取均衡控制器支援的所有頻率
short brands = mEqualizer.getNumberOfBands();
for (short i = 0; i < brands; i++) {
TextView eqTextView = new TextView(this);
// 建立一個TextView,用於顯示頻率
eqTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
// 設定該均衡控制器的頻率
eqTextView.setText((mEqualizer.getCenterFreq(i) / 1000) + " Hz");
layout.addView(eqTextView);
// 建立一個水平排列元件的LinearLayout
LinearLayout tmpLayout = new LinearLayout(this);
tmpLayout.setOrientation(LinearLayout.HORIZONTAL);
// 建立顯示均衡控制器最小值的TextView
TextView minDbTextView = new TextView(this);
minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// 顯示均衡控制器的最小值
minDbTextView.setText((minEQLevel / 100) + " dB");
// 建立顯示均衡控制器最大值的TextView
TextView maxDbTextView = new TextView(this);
maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// 顯示均衡控制器的最大值
maxDbTextView.setText((maxEQLevel / 100) + " dB");
LinearLayout.LayoutParams layoutParams = new
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.weight = 1;
// 定義SeekBar作為調整工具
SeekBar bar = new SeekBar(this);
bar.setLayoutParams(layoutParams);
bar.setMax(maxEQLevel - minEQLevel);
bar.setProgress(mEqualizer.getBandLevel(i));
final short brand = i;
// 為SeekBar的拖動事件設定事件監聽器
bar.setOnSeekBarChangeListener(new SeekBar
.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 設定該頻率的均衡值
mEqualizer.setBandLevel(brand,
(short) (progress + minEQLevel));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// 使用水平排列元件的LinearLayout“盛裝”三個元件
tmpLayout.addView(minDbTextView);
tmpLayout.addView(bar);
tmpLayout.addView(maxDbTextView);
// 將水平排列元件的LinearLayout新增到myLayout容器中
layout.addView(tmpLayout);
}
}
// 初始化重低音控制器
private void setupBassBoost() {
// 以MediaPlayer的AudioSessionId建立BassBoost
// 相當於設定BassBoost負責控制該MediaPlayer
mBass = new BassBoost(0, mPlayer.getAudioSessionId());
// 設定啟用重低音效果
mBass.setEnabled(true);
TextView bbTitle = new TextView(this);
bbTitle.setText("重低音:");
layout.addView(bbTitle);
// 使用SeekBar作為重低音的調整工具
SeekBar bar = new SeekBar(this);
// 重低音的範圍為0~1000
bar.setMax(1000);
bar.setProgress(0);
// 為SeekBar的拖動事件設定事件監聽器
bar.setOnSeekBarChangeListener(new SeekBar
.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 設定重低音的強度
mBass.setStrength((short) progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
layout.addView(bar);
}
// 初始化預設音場控制器
private void setupPresetReverb() {
// 以MediaPlayer的AudioSessionId建立PresetReverb
// 相當於設定PresetReverb負責控制該MediaPlayer
mPresetReverb = new PresetReverb(0,
mPlayer.getAudioSessionId());
// 設定啟用預設音場控制
mPresetReverb.setEnabled(true);
TextView prTitle = new TextView(this);
prTitle.setText("音場");
layout.addView(prTitle);
// 獲取系統支援的所有預設音場
for (short i = 0; i < mEqualizer.getNumberOfPresets(); i++) {
reverbNames.add(i);
reverbVals.add(mEqualizer.getPresetName(i));
}
// 使用Spinner作為音場選擇工具
Spinner sp = new Spinner(this);
sp.setAdapter(new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_spinner_item, reverbVals));
// 為Spinner的列表項選中事件設定監聽器
sp.setOnItemSelectedListener(new Spinner
.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> arg0
, View arg1, int arg2, long arg3) {
// 設定音場
mPresetReverb.setPreset(reverbNames.get(arg2));
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
layout.addView(sp);
}
@Override
protected void onPause() {
super.onPause();
if (isFinishing() && mPlayer != null) {
// 釋放所有物件
mVisualizer.release();
mEqualizer.release();
mPresetReverb.release();
mBass.release();
mPlayer.release();
mPlayer = null;
}
}
private static class MyVisualizerView extends View {
// bytes陣列儲存了波形抽樣點的值
private byte[] bytes;
private float[] points;
private Paint paint = new Paint();
private Rect rect = new Rect();
private byte type = 0;
public MyVisualizerView(Context context) {
super(context);
bytes = null;
// 設定畫筆的屬性
paint.setStrokeWidth(1f);
paint.setAntiAlias(true);
paint.setColor(Color.GREEN);
paint.setStyle(Style.FILL);
}
public void updateVisualizer(byte[] ftt) {
bytes = ftt;
// 通知該元件重繪自己
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent me) {
// 當用戶觸碰該元件時,切換波形型別
if (me.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
type++;
if (type >= 3) {
type = 0;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bytes == null) {
return;
}
// 繪製白色背景(主要為了印刷時好看)
canvas.drawColor(Color.WHITE);
// 使用rect物件記錄該元件的寬度和高度
rect.set(0, 0, getWidth(), getHeight());
switch (type) {
// -------繪製塊狀的波形圖-------
case 0:
for (int i = 0; i < bytes.length - 1; i++) {
float left = getWidth() * i / (bytes.length - 1);
// 根據波形值計算該矩形的高度
float top = rect.height() - (byte) (bytes[i + 1] + 128)
* rect.height() / 128;
float right = left + 1;
float bottom = rect.height();
canvas.drawRect(left, top, right, bottom, paint);
}
break;
// -------繪製柱狀的波形圖(每隔18個抽樣點繪製一個矩形)-------
case 1:
for (int i = 0; i < bytes.length - 1; i += 18) {
float left = rect.width() * i / (bytes.length - 1);
// 根據波形值計算該矩形的高度
float top = rect.height() - (byte) (bytes[i + 1] + 128)
* rect.height() / 128;
float right = left + 6;
float bottom = rect.height();
canvas.drawRect(left, top, right, bottom, paint);
}
break;
// -------繪製曲線波形圖-------
case 2:
// 如果point陣列還未初始化
if (points == null || points.length < bytes.length * 4) {
points = new float[bytes.length * 4];
}
for (int i = 0; i < bytes.length - 1; i++) {
// 計算第i個點的x座標
points[i * 4] = rect.width() * i / (bytes.length - 1);
// 根據bytes[i]的值(波形點的值)計算第i個點的y座標
points[i * 4 + 1] = (rect.height() / 2)
+ ((byte) (bytes[i] + 128)) * 128
/ (rect.height() / 2);
// 計算第i+1個點的x座標
points[i * 4 + 2] = rect.width() * (i + 1)
/ (bytes.length - 1);
// 根據bytes[i+1]的值(波形點的值)計算第i+1個點的y座標
points[i * 4 + 3] = (rect.height() / 2) + ((byte) (bytes[i + 1] + 128)) * 128
/ (rect.height() / 2);
}
// 繪製波形曲線
canvas.drawLines(points, paint);
break;
}
}
}
}
上面的程式程式碼定義了4個方法:setupVisualize()、setupEqualizer()、setupBassBoost()和setupPresetReverb(),這4個方法分別建立Visualize、Equalizer、BassBoost和PresetReverb物件,並呼叫它們的方法控制音樂特效。
上面的Demo中包括了一個MyVisualizerView內部類,該內部類會根據Visualizer傳過來的資料動態繪製波形效果。本Demo提供了三種波形:塊狀波形、柱狀波形和曲線波形——當用戶單擊MyVisualizerView元件時將會切換波形。
- 注意:最後別忘了在清單檔案AndroidManifest.xml裡授權音場效果必要的許可權。
<!-- 使用音場效果必要的許可權 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- Demo執行效果截圖: