登录 立即注册
安币:

查看: 76964|回复: 221

根据 音频频谱 绘制能量柱 Visualizer - MediaPlayer 控制(详细注释)

  [复制链接]

21

主题

256

帖子

14

安币

初级码农

Rank: 1

QQ达人

发表于 2014-9-30 17:48:21 | 显示全部楼层 |阅读模式
工作中需要做一个根据音频动态绘制能量柱的功能,(非原创-有改动-增加倒影效果)废话补多少,看效果-有美女-详细注释




好,源码贴出(看好了):

视图界面:
  1. package com.example.Code_Test;

  2. import android.app.Activity;
  3. import android.graphics.Color;
  4. import android.media.AudioManager;
  5. import android.media.MediaPlayer;
  6. import android.media.audiofx.Equalizer;
  7. import android.media.audiofx.Visualizer;
  8. import android.os.Bundle;
  9. import android.view.*;
  10. import android.widget.*;

  11. public class MyActivity extends Activity {

  12.     private static final float VISUALIZER_HEIGHT_DIP = 100f;//频谱View高度

  13.     private MediaPlayer mMediaPlayer;//音频
  14.     private Visualizer mVisualizer;//频谱器
  15.     private Equalizer mEqualizer; //均衡器

  16.     private LinearLayout mLayout;//代码布局
  17.     VisualizerView mBaseVisualizerView;

  18.     ImageButton play;

  19.     public void onCreate(Bundle savedInstanceState) {
  20.         super.onCreate(savedInstanceState);

  21.         this.requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
  22.         this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//去掉信息栏

  23.         setVolumeControlStream(AudioManager.STREAM_MUSIC);//设置音频流 - STREAM_MUSIC:音乐回放即媒体音量


  24.         mLayout = new LinearLayout(this);//代码创建布局
  25.         mLayout.setOrientation(LinearLayout.VERTICAL);//设置为线性布局-上下排列
  26.         mLayout.setBackgroundResource(R.drawable.ac88o);//设置界面背景
  27.         mLayout.setGravity(Gravity.CENTER);
  28.         setContentView(mLayout);//将布局添加到 Activity

  29.         mMediaPlayer = MediaPlayer.create(this, R.raw.aaaass);//实例化 MediaPlayer 并添加音频

  30.         setupVisualizerFxAndUi();//添加频谱到界面
  31.         setupEqualizeFxAndUi();//添加均衡器到界面
  32.         setupPlayButton();//添加按钮到界面


  33.         mVisualizer.setEnabled(true);//false 则不显示
  34.         mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

  35.             @Override
  36.             public void onCompletion(MediaPlayer mp) {
  37.             }
  38.         });

  39.         mMediaPlayer.start();//开始播放
  40.         mMediaPlayer.setLooping(true);//循环播放

  41.     }

  42.     /**
  43.      * 通过mMediaPlayer返回的AudioSessionId创建一个优先级为0均衡器对象 并且通过频谱生成相应的UI和对应的事件
  44.      */
  45.     private void setupEqualizeFxAndUi() {

  46.         TextView kongge = new TextView(this);
  47.         kongge.setText("");
  48.         kongge.setTextSize(10);
  49.         mLayout.addView(kongge);

  50.         mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());
  51.         mEqualizer.setEnabled(true);// 启用均衡器

  52.         // 通过均衡器得到其支持的频谱引擎
  53.         short bands = mEqualizer.getNumberOfBands();

  54.         // getBandLevelRange 是一个数组,返回一组频谱等级数组,
  55.         // 第一个下标为最低的限度范围
  56.         // 第二个下标为最大的上限,依次取出
  57.         final short minEqualizer = mEqualizer.getBandLevelRange()[0];
  58.         final short maxEqualizer = mEqualizer.getBandLevelRange()[1];

  59.         for (short i = 0; i < bands; i++) {
  60.             final short band = i;

  61.             TextView freqTextView = new TextView(this);
  62.             freqTextView.setTextColor(Color.WHITE);
  63.             freqTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  64.             freqTextView.setGravity(Gravity.CENTER_HORIZONTAL);

  65.             // 取出中心频率
  66.             freqTextView.setText((mEqualizer.getCenterFreq(band) / 1000) + "HZ");
  67.             mLayout.addView(freqTextView);

  68.             LinearLayout row = new LinearLayout(this);
  69.             row.setOrientation(LinearLayout.HORIZONTAL);

  70.             TextView minDbTextView = new TextView(this);
  71.             minDbTextView.setTextColor(Color.WHITE);
  72.             minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  73.             minDbTextView.setText((minEqualizer / 100) + " dB");

  74.             TextView maxDbTextView = new TextView(this);
  75.             maxDbTextView.setTextColor(Color.WHITE);
  76.             maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  77.             maxDbTextView.setText((maxEqualizer / 100) + " dB");

  78.             LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 40);


  79.             layoutParams.weight = 1;
  80.             SeekBar seekbar = new SeekBar(this);
  81.             seekbar.setLayoutParams(layoutParams);
  82.             seekbar.setPadding(15, 0, 15, 0);
  83.             seekbar.setThumb(getResources().getDrawable(R.drawable.seek_bar_dian_selector));
  84.             seekbar.setThumbOffset(20);
  85.             seekbar.setMax(maxEqualizer - minEqualizer);
  86.             seekbar.setProgress(mEqualizer.getBandLevel(band));


  87.             seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

  88.                 @Override
  89.                 public void onStopTrackingTouch(SeekBar seekBar) {
  90.                 }

  91.                 @Override
  92.                 public void onStartTrackingTouch(SeekBar seekBar) {
  93.                 }

  94.                 @Override
  95.                 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  96.                     mEqualizer.setBandLevel(band, (short) (progress + minEqualizer));
  97.                 }
  98.             });
  99.             row.addView(minDbTextView);
  100.             row.addView(seekbar);
  101.             row.addView(maxDbTextView);

  102.             mLayout.addView(row);

  103.         }
  104.         TextView eqTextView = new TextView(this);
  105.         eqTextView.setTextColor(Color.WHITE);
  106.         eqTextView.setText("均衡器");
  107.         eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
  108.         eqTextView.setTextSize(20);

  109.         mLayout.addView(eqTextView);

  110.     }

  111.     /**
  112.      * 生成一个VisualizerView对象,使音频频谱的波段能够反映到 VisualizerView上
  113.      */
  114.     private void setupVisualizerFxAndUi() {
  115.         mBaseVisualizerView = new VisualizerView(this);

  116.         mBaseVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
  117.                 ViewGroup.LayoutParams.FILL_PARENT,//宽度
  118.                 (int) (VISUALIZER_HEIGHT_DIP * getResources().getDisplayMetrics().density)//高度
  119.         ));
  120.         //将频谱View添加到布局
  121.         mLayout.addView(mBaseVisualizerView);
  122.         //实例化Visualizer,参数SessionId可以通过MediaPlayer的对象获得
  123.         mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
  124.         //采样 - 参数内必须是2的位数 - 如64,128,256,512,1024
  125.         mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
  126.         //设置允许波形表示,并且捕获它
  127.         mBaseVisualizerView.setVisualizer(mVisualizer);
  128.     }

  129.     //播放按钮
  130.     private void setupPlayButton() {
  131.         play = new ImageButton(this);
  132.         play.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  133.         play.setBackgroundResource(R.drawable.new_main_activity_stop_up);
  134.         play.setOnClickListener(new View.OnClickListener() {
  135.             @Override
  136.             public void onClick(View view) {
  137.                 if (mMediaPlayer.isPlaying()) {
  138.                     play.setBackgroundResource(R.drawable.new_main_activity_play_up);
  139.                     mMediaPlayer.pause();
  140.                 } else {
  141.                     play.setBackgroundResource(R.drawable.new_main_activity_stop_up);
  142.                     mMediaPlayer.start();
  143.                 }
  144.             }
  145.         });

  146.         mLayout.addView(play);
  147.     }

  148.     @Override
  149.     protected void onPause() {
  150.         super.onPause();
  151.         if (isFinishing() && mMediaPlayer != null) {
  152.             mVisualizer.release();
  153.             mMediaPlayer.release();
  154.             mEqualizer.release();
  155.             mMediaPlayer = null;
  156.         }
  157.     }

  158. }
复制代码



频谱View类:
  1. package com.example.Code_Test;

  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.graphics.Paint.Cap;
  7. import android.graphics.Paint.Join;
  8. import android.media.audiofx.Visualizer;
  9. import android.util.Log;
  10. import android.view.View;

  11. public class VisualizerView extends View implements Visualizer.OnDataCaptureListener {

  12.     private static final int DN_W = 470;//view宽度与单个音频块占比 - 正常480 需微调
  13.     private static final int DN_H = 180;//view高度与单个音频块占比
  14.     private static final int DN_SL = 15;//单个音频块宽度
  15.     private static final int DN_SW = 5;//单个音频块高度

  16.     private int hgap = 0;
  17.     private int vgap = 0;
  18.     private int levelStep = 0;
  19.     private float strokeWidth = 0;
  20.     private float strokeLength = 0;

  21.     protected final static int MAX_LEVEL = 15;//音量柱·音频块 - 最大个数

  22.     protected final static int CYLINDER_NUM = 25;//音量柱 - 最大个数

  23.     protected Visualizer mVisualizer = null;//频谱器

  24.     protected Paint mPaint = null;//画笔

  25.     protected byte[] mData = new byte[CYLINDER_NUM];//音量柱 数组

  26.     boolean mDataEn = true;

  27.     //构造函数初始化画笔
  28.     public VisualizerView(Context context) {
  29.         super(context);

  30.         mPaint = new Paint();//初始化画笔工具
  31.         mPaint.setAntiAlias(true);//抗锯齿
  32.         mPaint.setColor(Color.WHITE);//画笔颜色

  33. //        mPaint.setStrokeJoin(Join.ROUND); //频块圆角
  34. //        mPaint.setStrokeCap(Cap.ROUND); //频块圆角
  35.     }

  36.     //执行 Layout 操作
  37.     @Override
  38.     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  39.         super.onLayout(changed, left, top, right, bottom);

  40.         float w, h, xr, yr;

  41.         w = right - left;
  42.         h = bottom - top;
  43.         xr = w / (float) DN_W;
  44.         yr = h / (float) DN_H;

  45.         strokeWidth = DN_SW * yr;
  46.         strokeLength = DN_SL * xr;
  47.         hgap = (int) ((w - strokeLength * CYLINDER_NUM) / (CYLINDER_NUM + 1));
  48.         vgap = (int) (h / (MAX_LEVEL + 2));

  49.         mPaint.setStrokeWidth(strokeWidth); //设置频谱块宽度
  50.     }

  51.     //绘制频谱块和倒影
  52.     protected void drawCylinder(Canvas canvas, float x, byte value) {
  53.         if (value <= 0) value = 1;//最少有一个频谱块

  54.         for (int i = 0; i < value; i++) { //每个能量柱绘制value个能量块
  55.             float y = (getHeight() - i * vgap - vgap) - 40;//计算y轴坐标

  56.             //绘制频谱块
  57.             mPaint.setColor(Color.WHITE);//画笔颜色
  58.             canvas.drawLine(x, y, (x + strokeLength), y, mPaint);//绘制频谱块

  59.             //绘制音量柱倒影
  60.             if (i <= 6 && value > 0) {
  61.                 mPaint.setColor(Color.WHITE);//画笔颜色
  62.                 mPaint.setAlpha(100 - (100 / 6 * i));//倒影颜色
  63.                 canvas.drawLine(x, -y + 210, (x + strokeLength), -y + 210, mPaint);//绘制频谱块
  64.             }
  65.         }
  66.     }

  67.     @Override
  68.     public void onDraw(Canvas canvas) {
  69.         for (int i = 0; i < CYLINDER_NUM; i++) { //绘制25个能量柱
  70.             drawCylinder(canvas, strokeWidth / 2 + hgap + i * (hgap + strokeLength), mData[i]);
  71.         }
  72.     }

  73.     /**
  74.      * It sets the visualizer of the view. DO set the viaulizer to null when exit the program.
  75.      *
  76.      * @parma visualizer It is the visualizer to set.
  77.      */
  78.     public void setVisualizer(Visualizer visualizer) {
  79.         if (visualizer != null) {
  80.             if (!visualizer.getEnabled()) {
  81.                 visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
  82.             }
  83.             levelStep = 128 / MAX_LEVEL;
  84.             visualizer.setDataCaptureListener(this, Visualizer.getMaxCaptureRate() / 2, false, true);

  85.         } else {

  86.             if (mVisualizer != null) {
  87.                 mVisualizer.setEnabled(false);
  88.                 mVisualizer.release();
  89.             }
  90.         }
  91.         mVisualizer = visualizer;
  92.     }

  93.     //这个回调应该采集的是快速傅里叶变换有关的数据
  94.     @Override
  95.     public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
  96.         byte[] model = new byte[fft.length / 2 + 1];
  97.         if (mDataEn) {
  98.             model[0] = (byte) Math.abs(fft[1]);
  99.             int j = 1;
  100.             for (int i = 2; i < fft.length; ) {
  101.                 model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
  102.                 i += 2;
  103.                 j++;
  104.             }
  105.         } else {
  106.             for (int i = 0; i < CYLINDER_NUM; i++) {
  107.                 model[i] = 0;
  108.             }
  109.         }
  110.         for (int i = 0; i < CYLINDER_NUM; i++) {
  111.             final byte a = (byte) (Math.abs(model[CYLINDER_NUM - i]) / levelStep);

  112.             final byte b = mData[i];
  113.             if (a > b) {
  114.                 mData[i] = a;
  115.             } else {
  116.                 if (b > 0) {
  117.                     mData[i]--;
  118.                 }
  119.             }
  120.         }
  121.         postInvalidate();//刷新界面
  122.     }

  123.     //这个回调应该采集的是波形数据
  124.     @Override
  125.     public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
  126.         // Do nothing...
  127.     }
  128. }
复制代码
配置文件中需要添加权限:
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>

OK,搞定,源码放出

游客,如果您要查看本帖隐藏内容请回复



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

32

主题

282

帖子

1120

安币

Android大神

Rank: 6Rank: 6

QQ达人

发表于 2014-10-11 10:03:07 | 显示全部楼层
很不错啊,学习下

0

主题

7

帖子

3

安币

初级码农

Rank: 1

发表于 2014-10-11 11:17:58 | 显示全部楼层
效果真的不错

3

主题

391

帖子

21

安币

初级码农

Rank: 1

发表于 2014-10-11 13:33:07 | 显示全部楼层
{:4_137:}{:4_137:}

主题

帖子

安币

游客

发表于 2014-10-15 10:00:20 | 显示全部楼层
效果真的不错,zhi qi xia

0

主题

59

帖子

43

安币

初级码农

Rank: 1

QQ达人

发表于 2014-10-18 19:50:30 来自手机 | 显示全部楼层
感谢分享

1

主题

36

帖子

38

安币

初级码农

Rank: 1

发表于 2014-10-21 14:25:22 | 显示全部楼层
不错。。。 看看效果

0

主题

0

帖子

0

安币

初级码农

Rank: 1

发表于 2014-10-22 10:23:14 | 显示全部楼层
不错不错,参考下
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区

联系我们
关闭
合作电话:
15618560077
Email:
805941275@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表