Android音频开发(7):音乐可视化-FFT频谱图
16lz
2021-12-04
Android 音频开发 目录
- Android音频开发(1):音频相关知识
- Android音频开发(2):使用AudioRecord录制pcm格式音频
- Android音频开发(3):使用AudioRecord实现录音的暂停和恢复
- Android音频开发(4):PCM转WAV格式音频
- Android音频开发(5):Mp3的录制 - 编译Lame源码
- Android音频开发(6):Mp3的录制 - 使用Lame实时录制MP3格式音频
- Android音频开发(7):音乐可视化-FFT频谱图
项目地址:https://github.com/zhaolewei/MusicVisualizer
视频演示地址:https://www.bilibili.com/video/av30388154/
一、演示
二、实现
- 实现流程:
- 使用MediaPlayer播放传入的音乐,并拿到mediaPlayerId
- 使用Visualizer类拿到拿到MediaPlayer播放中的音频数据(wave/fft)
- 将数据用自定义控件展现出来
三、准备工作
- 使用Visualizer需要录音的动态权限, 如果播放sd卡音频需要STORAGE权限
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获取当前音频数据
Visualizer 有两个比较重要的参数
- 设置可视化数据的数据大小 范围[Visualizer.getCaptureSizeRange()[0]~Visualizer.getCaptureSizeRange()[1]]
- 社会可视化数据的采集频率 范围[0~Visualizer.getMaxCaptureRate()]
- OnDataCaptureListener 有2个回调,一个用于显示FFT数据,展示不同频率的振幅,另一个用于显示声音的波形图
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
六、编写自定义控件,展示数据
- 处理数据: visualizer 回调中的数据中是存在负数的,需要转换一下,用于显示
- 当byte 为 -128时Math.abs(fft[i]) 计算出来的值会越界,需要手动处理一下
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); } }
更多相关文章
- Android音频开发(3):使用AudioRecord实现录音的暂停和恢复
- android操作通讯录的联系人
- Android(安卓)之 GrideView网格视图
- android操作通讯录的联系人
- android 杂疑汇总
- Android(安卓)实现显示文字的Gallery
- Android基本概念
- Java(Android)数据结构汇总 -- 总纲
- android 数据库 备份还原