Android音频开发(7):音乐可视化

您所在的位置:网站首页 音乐的可视化分析 Android音频开发(7):音乐可视化

Android音频开发(7):音乐可视化

#Android音频开发(7):音乐可视化| 来源: 网络整理| 查看: 265

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/

一、演示

image

二、实现 实现流程: 使用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); 四、开始播放 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, "请检查录音权限"); } }

波形数据和傅里叶数据的关系请看面这张图: image

快速傅里叶转换(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); } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3