android绘制播放音频的波形图

作者: 7b3a41b7334e | 来源:发表于2018-01-03 17:06 被阅读495次

    之前做过android的录音,编辑(裁剪和合成(WAV格式)),思路大概是从麦克风获取音频的详细数据填充到list集合中,再将这些数据经过计算画到屏幕上,算是实时录制的波形图!之后有一段时间没碰过那个项目了,虽然功能是做出来了,但是还不算是完整的,那要是播放的时候呢?播放的时候怎么实时动态的获取音频数据来绘制呢?思考良久,在逛github的时候,发现了这个功能!在这里做个记录,也给没有这方面知识的朋友们做个补充,分享一下!

    OK,先看效果图吧!

    这里写图片描述

    这个效果图是线性和圆形的音频傅里叶数据图形,当然还有柱状的效果图,这里并没有展示,整完这篇博客后,大家可以自己下载demo自己运行看看效果。

    获取音频播放的实时数据并绘制,涉及到android提供的一个类,Visualizer,这个类可以捕获使用MediaPlayer的时候音频数据,主要返回两种类型的数据,一种是音频的波形数据,一种是傅里叶数据(未考究),android系统中关于这个类的描述几乎为0,并不像其它类会有大把英文注释!很烦。。

    代码使用方法如下:

    这里写图片描述

    `

    很简单吧!只需要实现一个捕获监听即可!

    OK,我们再来看看柱状图。
    效果图如下:

    这里写图片描述

    界面的效果有点糙?不急,我们理解了原理,之后慢慢改呗!哪有什么东西都是现成的?

    先看自定义的界面展示的代码:

    /**
     * Copyright 2011, Felix Palmer
     *
     * Licensed under the MIT license:
     * http://creativecommons.org/licenses/MIT/
     */
    package com.tian.audio.wave.widget;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Bitmap.Config;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.PorterDuff.Mode;
    import android.graphics.PorterDuffXfermode;
    import android.graphics.Rect;
    import android.media.MediaPlayer;
    import android.media.audiofx.Visualizer;
    import android.util.AttributeSet;
    import android.view.View;
    
    import com.tian.audio.wave.dao.AudioData;
    import com.tian.audio.wave.dao.FFTData;
    import com.tian.audio.wave.renderer.Renderer;
    
    
    /**
     * A class that draws visualizations of data received from a
     * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } and
     * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
     */
    public class VisualizerView extends View {
      private static final String TAG = "VisualizerView";
    
      private byte[] mBytes;
      private byte[] mFFTBytes;
      private Rect mRect = new Rect();
      private Visualizer mVisualizer;
    
      private Set<Renderer> mRenderers;
    
      private Paint mFlashPaint = new Paint();
      private Paint mFadePaint = new Paint();
    
      public VisualizerView(Context context, AttributeSet attrs, int defStyle){
        super(context, attrs);
        init();
      }
    
      public VisualizerView(Context context, AttributeSet attrs)
      {
        this(context, attrs, 0);
      }
    
      public VisualizerView(Context context)
      {
        this(context, null, 0);
      }
    
      private void init() {
        mBytes = null;
        mFFTBytes = null;
    
        mFlashPaint.setColor(Color.argb(122, 255, 255, 255));
        mFadePaint.setColor(Color.argb(238, 255, 255, 255)); // Adjust alpha to change how quickly the image fades
        mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
    
        mRenderers = new HashSet<Renderer>();
      }
    
      /**
       * Links the visualizer to a player
       * @param player - MediaPlayer instance to link to
       */
      public void link(MediaPlayer player){
        if(player == null)
        {
          throw new NullPointerException("Cannot link to null MediaPlayer");
        }
    
        // Create the Visualizer object and attach it to our media player.
        mVisualizer = new Visualizer(player.getAudioSessionId());
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
    
        // Pass through Visualizer data to VisualizerView
        Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener(){
    
          //捕获波形数据
          @Override
          public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
              int samplingRate){
            updateVisualizer(bytes);
          }
    
          //捕获傅里叶数据
          @Override
          public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
              int samplingRate){
            updateVisualizerFFT(bytes);
          }
        };
    
        mVisualizer.setDataCaptureListener(captureListener,
            Visualizer.getMaxCaptureRate() / 2, true, true);
    
        // Enabled Visualizer and disable when we're done with the stream
        mVisualizer.setEnabled(true);
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
          @Override
          public void onCompletion(MediaPlayer mediaPlayer){
            mVisualizer.setEnabled(false);
          }
        });
      }
    
      public void addRenderer(Renderer renderer){
        if(renderer != null){
          mRenderers.add(renderer);
        }
      }
    
      public void clearRenderers()
      {
        mRenderers.clear();
      }
    
      /**
       * Call to release the resources used by VisualizerView. Like with the
       * MediaPlayer it is good practice to call this method
       */
      public void release()
      {
        mVisualizer.release();
      }
    
      /**
       * Pass data to the visualizer. Typically this will be obtained from the
       * Android Visualizer.OnDataCaptureListener call back. See
       * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture }
       * @param bytes
       */
      public void updateVisualizer(byte[] bytes) {
        mBytes = bytes;
        invalidate();
      }
    
      /**
       * Pass FFT data to the visualizer. Typically this will be obtained from the
       * Android Visualizer.OnDataCaptureListener call back. See
       * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
       * @param bytes
       */
      public void updateVisualizerFFT(byte[] bytes) {
        mFFTBytes = bytes;
        invalidate();
      }
    
      boolean mFlash = false;
    
      /**
       * Call this to make the visualizer flash. Useful for flashing at the start
       * of a song/loop etc...
       */
      public void flash() {
        mFlash = true;
        invalidate();
      }
    
      Bitmap mCanvasBitmap;
      Canvas mCanvas;
    
    
      @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        // Create canvas once we're ready to draw
        mRect.set(0, 0, getWidth(), getHeight());
    
        if(mCanvasBitmap == null){
          mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);
        }
        if(mCanvas == null){
          mCanvas = new Canvas(mCanvasBitmap);
        }
    
        if (mBytes != null) {
          // Render all audio renderers
          AudioData audioData = new AudioData(mBytes);
          for(Renderer r : mRenderers){
            r.render(mCanvas, audioData, mRect);
          }
        }
    
        if (mFFTBytes != null) {
          // Render all FFT renderers
          FFTData fftData = new FFTData(mFFTBytes);
          for(Renderer r : mRenderers){
            r.render(mCanvas, fftData, mRect);
          }
        }
        // 渐变产生的阴影的效果
        mCanvas.drawPaint(mFadePaint);
    
        if(mFlash){
          mFlash = false;
          mCanvas.drawPaint(mFlashPaint);
        }
    
        canvas.drawBitmap(mCanvasBitmap, new Matrix(), null);
      }
    }
    

    这个类很简单,对不同的展示界面进行了简单的封装,主要的绘制那肯定在onDraw方法体!而在获取到音频数据的时候,将Visualizer捕获到的音频数据(傅里叶),抽取出来进行invalidate();重新绘制,所以其他的我们可以跳过,直接看onDraw方法体,也包括怎样产生的阴影效果!

    抽取的父类处理:

    package com.tian.audio.wave.renderer;
    import android.graphics.Canvas;
    import android.graphics.Rect;
    
    import com.tian.audio.wave.dao.AudioData;
    import com.tian.audio.wave.dao.FFTData;
    
    
    abstract public class Renderer{
      // Have these as members, so we don't have to re-create them each time
      protected float[] mPoints;
      protected float[] mFFTPoints;
      public Renderer()
      {
      }
    
      // As the display of raw/FFT audio will usually look different, subclasses
      // will typically only implement one of the below methods
      /**
       * Implement this method to render the audio data onto the canvas
       * @param canvas - Canvas to draw on
       * @param data - Data to render
       * @param rect - Rect to render into
       */
      abstract public void onRender(Canvas canvas, AudioData data, Rect rect);
    
      /**
       * Implement this method to render the FFT audio data onto the canvas
       * @param canvas - Canvas to draw on
       * @param data - Data to render
       * @param rect - Rect to render into
       */
      abstract public void onRender(Canvas canvas, FFTData data, Rect rect);
    
    
      // These methods should actually be called for rendering
      /**
       * Render the audio data onto the canvas
       * @param canvas - Canvas to draw on
       * @param data - Data to render
       * @param rect - Rect to render into
       */
      final public void render(Canvas canvas, AudioData data, Rect rect)
      {
        if (mPoints == null || mPoints.length < data.bytes.length * 4) {
          mPoints = new float[data.bytes.length * 4];
        }
    
        onRender(canvas, data, rect);
      }
    
      /**
       * Render the FFT data onto the canvas
       * @param canvas - Canvas to draw on
       * @param data - Data to render
       * @param rect - Rect to render into
       */
      final public void render(Canvas canvas, FFTData data, Rect rect)
      {
        if (mFFTPoints == null || mFFTPoints.length < data.bytes.length * 4) {
          mFFTPoints = new float[data.bytes.length * 4];
        }
    
        onRender(canvas, data, rect);
      }
    }
    
    

    绘制数据类:

    /**
     * Copyright 2011, Felix Palmer
     *
     * Licensed under the MIT license:
     * http://creativecommons.org/licenses/MIT/
     */
    package com.tian.audio.wave.renderer;
    
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Rect;
    
    import com.tian.audio.wave.dao.AudioData;
    import com.tian.audio.wave.dao.FFTData;
    
    
    /**
     * 操作画笔进行各个bar的绘制工作
     */
    public class BarGraphRenderer extends Renderer{
      private int mDivisions;
      private Paint mPaint;
      private boolean mTop;
    
      /**
       * Renders the FFT data as a series of lines, in histogram form
       * @param divisions - must be a power of 2. Controls how many lines to draw
       * @param paint - Paint to draw lines with
       * @param top - whether to draw the lines at the top of the canvas, or the bottom
       */
      public BarGraphRenderer(int divisions,
                              Paint paint,
                              boolean top){
        super();
        mDivisions = divisions;
        mPaint = paint;
        mTop = top;
      }
    
      @Override
      public void onRender(Canvas canvas, AudioData data, Rect rect){
        // Do nothing, we only display FFT data
      }
    
      @Override
      public void onRender(Canvas canvas, FFTData data, Rect rect){
        for (int i = 0; i < data.bytes.length / mDivisions; i++) {
    
          mFFTPoints[i * 4] = i * 4 * mDivisions;
          mFFTPoints[i * 4 + 2] = i * 4 * mDivisions;
    
          byte rfk = data.bytes[mDivisions * i];//间隔倍数
          byte ifk = data.bytes[mDivisions * i + 1];
    
          float magnitude = (rfk * rfk + ifk * ifk);
          int dbValue = (int) (10 * Math.log10(magnitude));
    
          if(mTop){
            mFFTPoints[i * 4 + 1] = 0;
            mFFTPoints[i * 4 + 3] = (dbValue * 2 - 10);
          }else{
            mFFTPoints[i * 4 + 1] = rect.height();
            mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * 2 - 10);
          }
        }
    
        canvas.drawLines(mFFTPoints, mPaint);
      }
    }
    
    

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/bg"
        android:orientation="vertical" >
    
        <FrameLayout
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#000" >
    
            <com.tian.audio.wave.widget.VisualizerView
                android:id="@+id/visualizerView"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >
            </com.tian.audio.wave.widget.VisualizerView>
        </FrameLayout>
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0" >
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.25"
                android:onClick="barPressed"
                android:text="Bar" >
            </Button>
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.25"
                android:onClick="circlePressed"
                android:text="Circle" >
            </Button>
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.25"
                android:onClick="circleBarPressed"
                android:text="Circle Bar" >
            </Button>
                
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.25"
                android:onClick="linePressed"
                android:text="Line" >
            </Button>
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.25"
                android:onClick="clearPressed"
                android:text="Clear" >
            </Button>
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0" >
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.5"
                android:onClick="startPressed"
                android:text="Start" >
            </Button>
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_weight="0.5"
                android:onClick="stopPressed"
                android:text="Stop" >
            </Button>
        </LinearLayout>
    
    </LinearLayout>
    

    使用代码:

    // Methods for adding renderers to visualizer
        private void addBarGraphRenderers(){
            
            //底部柱状条
            Paint paint = new Paint();
            paint.setStrokeWidth(50f);
            paint.setAntiAlias(true);
            paint.setColor(Color.argb(200, 56, 138, 252));
            BarGraphRenderer barGraphRendererBottom = new BarGraphRenderer(16, paint, false);
            mVisualizerView.addRenderer(barGraphRendererBottom);
    
            //顶部柱状条
            Paint paint2 = new Paint();
            paint2.setStrokeWidth(12f);
            paint2.setAntiAlias(true);
            paint2.setColor(Color.argb(200, 181, 111, 233));
            BarGraphRenderer barGraphRendererTop = new BarGraphRenderer(4, paint2, true);
            mVisualizerView.addRenderer(barGraphRendererTop);
        }
    

    OK,不知道的,这个功能是很难做,知道后,就很简单了!哈哈。。

    github地址(大家下载的话,顺手给个star,也是对作者的鼓励!谢谢啦!):

    https://github.com/T-chuangxin/AudioWaveShow

    每天进步一点点,时间会让你成为巨人!加油!

    相关文章

      网友评论

        本文标题:android绘制播放音频的波形图

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