android绘制播放音频的波形图

之前做过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

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

  • 15
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值