项目地址:https://github.com/zhaolewei/MusicVisualizer
视频演示地址:https://www.bilibili.com/video/av30388154/
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.MODIFY_AUDIO_SETTINGS
};
ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, 1);
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
private MediaPlayer.OnPreparedListener preparedListener = new
/**
* 播放音频
*
* @param raw 资源文件id
*/
private void doPlay(final int raw) {
try {
mediaPlayer = MediaPlayer.create(MyApp.getInstance(), raw);
if (mediaPlayer == null) {
Logger.e(TAG, "mediaPlayer is null");
return;
}
mediaPlayer.setOnErrorListener(errorListener);
mediaPlayer.setOnPreparedListener(preparedListener);
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
}
}
/**
* 获取MediaPlayerId
* 可视化类Visualizer需要此参数
* @return MediaPlayerId
*/
public int getMediaPlayerId() {
return mediaPlayer.getAudioSessionId();
}
Visualizer 有两个比较重要的参数
private Visualizer.OnDataCaptureListener dataCaptureListener = new Visualizer.OnDataCaptureListener() {
@Override
public void onWaveFormDataCapture(Visualizer visualizer, final byte[] waveform, int samplingRate) {
audioView.post(new Runnable() {
@Override
public void run() {
audioView.setWaveData(waveform);
}
});
}
@Override
public void onFftDataCapture(Visualizer visualizer, final byte[] fft, int samplingRate) {
audioView2.post(new Runnable() {
@Override
public void run() {
audioView2.setWaveData(fft);
}
});
}
};
private void initVisualizer() {
try {
int mediaPlayerId = mediaPlayer.getMediaPlayerId();
if (visualizer != null) {
visualizer.release();
}
visualizer = new Visualizer(mediaPlayerId);
/**
*可视化数据的大小: getCaptureSizeRange()[0]为最小值,getCaptureSizeRange()[1]为最大值
*/
int captureSize = Visualizer.getCaptureSizeRange()[1];
int captureRate = Visualizer.getMaxCaptureRate() * 3 / 4;
visualizer.setCaptureSize(captureSize);
visualizer.setDataCaptureListener(dataCaptureListener, captureRate, true, true);
visualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);
visualizer.setEnabled(true);
} catch (Exception e) {
Logger.e(TAG, "请检查录音权限");
}
}
快速傅里叶转换(FFT)详细分析: https://zhuanlan.zhihu.com/p/19763358
byte 的范围: -128~127
/**
* 预处理数据
*
* @return
*/
private static byte[] readyData(byte[] fft) {
byte[] newData = new byte[LUMP_COUNT];
byte abs;
for (int i = 0; i < LUMP_COUNT; i++) {
abs = (byte) Math.abs(fft[i]);
//描述:Math.abs -128时越界
newData[i] = abs < 0 ? 127 : abs;
}
return newData;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
wavePath.reset();
for (int i = 0; i < LUMP_COUNT; i++) {
if (waveData == null) {
canvas.drawRect((LUMP_WIDTH + LUMP_SPACE) * i,
LUMP_MAX_HEIGHT - LUMP_MIN_HEIGHT,
(LUMP_WIDTH + LUMP_SPACE) * i + LUMP_WIDTH,
LUMP_MAX_HEIGHT,
lumpPaint);
continue;
}
switch (upShowStyle) {
case STYLE_HOLLOW_LUMP:
drawLump(canvas, i, false);
break;
case STYLE_WAVE:
drawWave(canvas, i, false);
break;
default:
break;
}
switch (downShowStyle) {
case STYLE_HOLLOW_LUMP:
drawLump(canvas, i, true);
break;
case STYLE_WAVE:
drawWave(canvas, i, true);
break;
default:
break;
}
}
}
/**
* 绘制矩形条
*/
private void drawLump(Canvas canvas, int i, boolean reversal) {
int minus = reversal ? -1 : 1;
if (waveData[i] < 0) {
Logger.w("waveData", "waveData[i] < 0 data: %s", waveData[i]);
}
float top = (LUMP_MAX_HEIGHT - (LUMP_MIN_HEIGHT + waveData[i] * SCALE) * minus);
canvas.drawRect(LUMP_SIZE * i,
top,
LUMP_SIZE * i + LUMP_WIDTH,
LUMP_MAX_HEIGHT,
lumpPaint);
}
/**
* 绘制曲线
* 这里使用贝塞尔曲线来绘制
*/
private void drawWave(Canvas canvas, int i, boolean reversal) {
if (pointList == null || pointList.size() < 2) {
return;
}
float ratio = SCALE * (reversal ? -1 : 1);
if (i < pointList.size() - 2) {
Point point = pointList.get(i);
Point nextPoint = pointList.get(i + 1);
int midX = (point.x + nextPoint.x) >> 1;
if (i == 0) {
wavePath.moveTo(point.x, LUMP_MAX_HEIGHT - point.y * ratio);
}
wavePath.cubicTo(midX, LUMP_MAX_HEIGHT - point.y * ratio,
midX, LUMP_MAX_HEIGHT - nextPoint.y * ratio,
nextPoint.x, LUMP_MAX_HEIGHT - nextPoint.y * ratio);
canvas.drawPath(wavePath, lumpPaint);
}
}