美文网首页Android技术知识Android开发Android开发
Android音频开发(7):音乐可视化-FFT频谱图

Android音频开发(7):音乐可视化-FFT频谱图

作者: android_赵乐玮 | 来源:发表于2018-08-26 23:01 被阅读46次

    项目地址:https://github.com/zhaolewei/MusicVisualizer
    视频演示地址:https://www.bilibili.com/video/av30388154/

    一、演示

    image

    二、实现

    • 实现流程:
    1. 使用MediaPlayer播放传入的音乐,并拿到mediaPlayerId
    2. 使用Visualizer类拿到拿到MediaPlayer播放中的音频数据(wave/fft)
    3. 将数据用自定义控件展现出来

    三、准备工作

    • 使用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 有两个比较重要的参数

    1. 设置可视化数据的数据大小 范围[Visualizer.getCaptureSizeRange()[0]~Visualizer.getCaptureSizeRange()[1]]
    2. 社会可视化数据的采集频率 范围[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

    六、编写自定义控件,展示数据

    1. 处理数据: 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;
        }
    
    1. 紧接着就是根据数据去绘制图形
        @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音频开发(7):音乐可视化-FFT频谱图

        本文链接:https://www.haomeiwen.com/subject/txpaiftx.html