之前做過(guò)android的錄音油吭,編輯(裁剪和合成(WAV格式))雕憔,思路大概是從麥克風(fēng)獲取音頻的詳細(xì)數(shù)據(jù)填充到list集合中逾雄,再將這些數(shù)據(jù)經(jīng)過(guò)計(jì)算畫(huà)到屏幕上含懊,算是實(shí)時(shí)錄制的波形圖!之后有一段時(shí)間沒(méi)碰過(guò)那個(gè)項(xiàng)目了筐摘,雖然功能是做出來(lái)了卒茬,但是還不算是完整的,那要是播放的時(shí)候呢咖熟?播放的時(shí)候怎么實(shí)時(shí)動(dòng)態(tài)的獲取音頻數(shù)據(jù)來(lái)繪制呢圃酵?思考良久,在逛github的時(shí)候馍管,發(fā)現(xiàn)了這個(gè)功能郭赐!在這里做個(gè)記錄,也給沒(méi)有這方面知識(shí)的朋友們做個(gè)補(bǔ)充确沸,分享一下捌锭!
OK,先看效果圖吧罗捎!
這個(gè)效果圖是線性和圓形的音頻傅里葉數(shù)據(jù)圖形观谦,當(dāng)然還有柱狀的效果圖,這里并沒(méi)有展示桨菜,整完這篇博客后豁状,大家可以自己下載demo自己運(yùn)行看看效果。
獲取音頻播放的實(shí)時(shí)數(shù)據(jù)并繪制雷激,涉及到android提供的一個(gè)類替蔬,Visualizer,這個(gè)類可以捕獲使用MediaPlayer的時(shí)候音頻數(shù)據(jù)屎暇,主要返回兩種類型的數(shù)據(jù),一種是音頻的波形數(shù)據(jù)驻粟,一種是傅里葉數(shù)據(jù)(未考究)根悼,android系統(tǒng)中關(guān)于這個(gè)類的描述幾乎為0凶异,并不像其它類會(huì)有大把英文注釋!很煩挤巡。剩彬。
代碼使用方法如下:
`
很簡(jiǎn)單吧!只需要實(shí)現(xiàn)一個(gè)捕獲監(jiān)聽(tīng)即可矿卑!
OK喉恋,我們?cè)賮?lái)看看柱狀圖。
效果圖如下:
界面的效果有點(diǎn)糙母廷?不急轻黑,我們理解了原理,之后慢慢改唄琴昆!哪有什么東西都是現(xiàn)成的氓鄙?
先看自定義的界面展示的代碼:
/**
* 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(){
//捕獲波形數(shù)據(jù)
@Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
int samplingRate){
updateVisualizer(bytes);
}
//捕獲傅里葉數(shù)據(jù)
@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);
}
}
// 漸變產(chǎn)生的陰影的效果
mCanvas.drawPaint(mFadePaint);
if(mFlash){
mFlash = false;
mCanvas.drawPaint(mFlashPaint);
}
canvas.drawBitmap(mCanvasBitmap, new Matrix(), null);
}
}
這個(gè)類很簡(jiǎn)單,對(duì)不同的展示界面進(jìn)行了簡(jiǎn)單的封裝业舍,主要的繪制那肯定在onDraw方法體抖拦!而在獲取到音頻數(shù)據(jù)的時(shí)候,將Visualizer捕獲到的音頻數(shù)據(jù)(傅里葉)舷暮,抽取出來(lái)進(jìn)行invalidate();重新繪制态罪,所以其他的我們可以跳過(guò),直接看onDraw方法體下面,也包括怎樣產(chǎn)生的陰影效果向臀!
抽取的父類處理:
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);
}
}
繪制數(shù)據(jù)類:
/**
* 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;
/**
* 操作畫(huà)筆進(jìn)行各個(gè)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];//間隔倍數(shù)
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,不知道的诸狭,這個(gè)功能是很難做券膀,知道后,就很簡(jiǎn)單了驯遇!哈哈芹彬。。
github地址(大家下載的話叉庐,順手給個(gè)star舒帮,也是對(duì)作者的鼓勵(lì)!謝謝啦6傅):
https://github.com/T-chuangxin/AudioWaveShow
每天進(jìn)步一點(diǎn)點(diǎn)玩郊,時(shí)間會(huì)讓你成為巨人!加油枉阵!