本实例来自于《疯狂Android讲义》。要实现详细的功能,须要了解下面API:
  • MediaPlayer  媒体播放器
  • Visualizer 频谱
  • Equalizer 均衡器
  • BassBoost 重低音控制器
  • PresetReverb 预设音场控制器
  • Paint 画图

来看下效果示意图,例如以下所看到的

竖状波形图

块状波形图

曲线波形图


调节均衡器、重低音

选择音场

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast">


以下来看详细的实现代码    
MediaPlayerTest.java
package com.oyp.media;

import java.util.ArrayList;
import java.util.List; import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.PresetReverb;
import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView; public class MediaPlayerTest extends Activity
{
// 定义播放声音的MediaPlayer
private MediaPlayer mPlayer;
// 定义系统的频谱
private Visualizer mVisualizer;
// 定义系统的均衡器
private Equalizer mEqualizer;
// 定义系统的重低音控制器
private BassBoost mBass;
// 定义系统的预设音场控制器
private PresetReverb mPresetReverb;
private LinearLayout layout;
private List<Short> reverbNames = new ArrayList<Short>();
private List<String> reverbVals = new ArrayList<String>(); @Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//设置音频流 - STREAM_MUSIC:音乐回放即媒体音量
setVolumeControlStream(AudioManager.STREAM_MUSIC);
layout = new LinearLayout(this);//代码创建布局
layout.setOrientation(LinearLayout.VERTICAL);//设置为线性布局-上下排列
setContentView(layout);//将布局加入到 Activity
// 创建MediaPlayer对象,并加入音频
// 音频路径为 res/raw/beautiful.mp3
mPlayer = MediaPlayer.create(this, R.raw.beautiful);
// 初始化示波器
setupVisualizer();
// 初始化均衡控制器
setupEqualizer();
// 初始化重低音控制器
setupBassBoost();
// 初始化预设音场控制器
setupPresetReverb();
// 开发播放音乐
mPlayer.start();
}
/**
* 初始化频谱
*/
private void setupVisualizer()
{
// 创建MyVisualizerView组件,用于显示波形图
final MyVisualizerView mVisualizerView =
new MyVisualizerView(this);
mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
(int) (120f * getResources().getDisplayMetrics().density)));
// 将MyVisualizerView组件加入到layout容器中
layout.addView(mVisualizerView);
// 以MediaPlayer的AudioSessionId创建Visualizer
// 相当于设置Visualizer负责显示该MediaPlayer的音频数据
mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
//设置须要转换的音乐内容长度,专业的说这就是採样,该採样值一般为2的指数倍,如64,128,256,512,1024。
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
// 为mVisualizer设置监听器
/*
* Visualizer.setDataCaptureListener(OnDataCaptureListener listener, int rate, boolean waveform, boolean fft
*
* listener,表监听函数。匿名内部类实现该接口,该接口须要实现两个函数
rate, 表示採样的周期,即隔多久採样一次,联系前文就是隔多久採样128个数据
iswave,是波形信号
isfft。是FFT信号,表示是获取波形信号还是频域信号 */
mVisualizer.setDataCaptureListener(
new Visualizer.OnDataCaptureListener()
{
//这个回调应该採集的是高速傅里叶变换有关的数据
@Override
public void onFftDataCapture(Visualizer visualizer,
byte[] fft, int samplingRate)
{
}
//这个回调应该採集的是波形数据
@Override
public void onWaveFormDataCapture(Visualizer visualizer,
byte[] waveform, int samplingRate)
{
// 用waveform波形数据更新mVisualizerView组件
mVisualizerView.updateVisualizer(waveform);
}
}, Visualizer.getMaxCaptureRate() / 2, true, false);
mVisualizer.setEnabled(true);
} /**
* 初始化均衡控制器
*/
private void setupEqualizer()
{
// 以MediaPlayer的AudioSessionId创建Equalizer
// 相当于设置Equalizer负责控制该MediaPlayer
mEqualizer = new Equalizer(0, mPlayer.getAudioSessionId());
// 启用均衡控制效果
mEqualizer.setEnabled(true);
TextView eqTitle = new TextView(this);
eqTitle.setText("均衡器:");
layout.addView(eqTitle);
// 获取均衡控制器支持最小值和最大值
final short minEQLevel = mEqualizer.getBandLevelRange()[0];//第一个下标为最低的限度范围
short maxEQLevel = mEqualizer.getBandLevelRange()[1]; // 第二个下标为最高的限度范围
// 获取均衡控制器支持的全部频率
short brands = mEqualizer.getNumberOfBands();
for (short i = 0; i < brands; i++)
{
TextView eqTextView = new TextView(this);
// 创建一个TextView。用于显示频率
eqTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
// 设置该均衡控制器的频率
eqTextView.setText((mEqualizer.getCenterFreq(i) / 1000)
+ " Hz");
layout.addView(eqTextView);
// 创建一个水平排列组件的LinearLayout
LinearLayout tmpLayout = new LinearLayout(this);
tmpLayout.setOrientation(LinearLayout.HORIZONTAL);
// 创建显示均衡控制器最小值的TextView
TextView minDbTextView = new TextView(this);
minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// 显示均衡控制器的最小值
minDbTextView.setText((minEQLevel / 100) + " dB");
// 创建显示均衡控制器最大值的TextView
TextView maxDbTextView = new TextView(this);
maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// 显示均衡控制器的最大值
maxDbTextView.setText((maxEQLevel / 100) + " dB");
LinearLayout.LayoutParams layoutParams = new
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.weight = 1;
// 定义SeekBar做为调整工具
SeekBar bar = new SeekBar(this);
bar.setLayoutParams(layoutParams);
bar.setMax(maxEQLevel - minEQLevel);
bar.setProgress(mEqualizer.getBandLevel(i));
final short brand = i;
// 为SeekBar的拖动事件设置事件监听器
bar.setOnSeekBarChangeListener(new SeekBar
.OnSeekBarChangeListener()
{
@Override
public void onProgressChanged(SeekBar seekBar,
int progress, boolean fromUser)
{
// 设置该频率的均衡值
mEqualizer.setBandLevel(brand,
(short) (progress + minEQLevel));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar)
{
}
@Override
public void onStopTrackingTouch(SeekBar seekBar)
{
}
});
// 使用水平排列组件的LinearLayout“盛装”3个组件
tmpLayout.addView(minDbTextView);
tmpLayout.addView(bar);
tmpLayout.addView(maxDbTextView);
// 将水平排列组件的LinearLayout加入到myLayout容器中
layout.addView(tmpLayout);
}
} /**
* 初始化重低音控制器
*/
private void setupBassBoost()
{
// 以MediaPlayer的AudioSessionId创建BassBoost
// 相当于设置BassBoost负责控制该MediaPlayer
mBass = new BassBoost(0, mPlayer.getAudioSessionId());
// 设置启用重低音效果
mBass.setEnabled(true);
TextView bbTitle = new TextView(this);
bbTitle.setText("重低音:");
layout.addView(bbTitle);
// 使用SeekBar做为重低音的调整工具
SeekBar bar = new SeekBar(this);
// 重低音的范围为0~1000
bar.setMax(1000);
bar.setProgress(0);
// 为SeekBar的拖动事件设置事件监听器
bar.setOnSeekBarChangeListener(new SeekBar
.OnSeekBarChangeListener()
{
@Override
public void onProgressChanged(SeekBar seekBar
, int progress, boolean fromUser)
{
// 设置重低音的强度
mBass.setStrength((short) progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar)
{
}
@Override
public void onStopTrackingTouch(SeekBar seekBar)
{
}
});
layout.addView(bar);
} /**
* 初始化预设音场控制器
*/
private void setupPresetReverb()
{
// 以MediaPlayer的AudioSessionId创建PresetReverb
// 相当于设置PresetReverb负责控制该MediaPlayer
mPresetReverb = new PresetReverb(0,
mPlayer.getAudioSessionId());
// 设置启用预设音场控制
mPresetReverb.setEnabled(true);
TextView prTitle = new TextView(this);
prTitle.setText("音场");
layout.addView(prTitle);
// 获取系统支持的全部预设音场
for (short i = 0; i < mEqualizer.getNumberOfPresets(); i++)
{
reverbNames.add(i);
reverbVals.add(mEqualizer.getPresetName(i));
}
// 使用Spinner做为音场选择工具
Spinner sp = new Spinner(this);
sp.setAdapter(new ArrayAdapter<String>(MediaPlayerTest.this,
android.R.layout.simple_spinner_item, reverbVals));
// 为Spinner的列表项选中事件设置监听器
sp.setOnItemSelectedListener(new Spinner
.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> arg0
, View arg1, int arg2, long arg3)
{
// 设定音场
mPresetReverb.setPreset(reverbNames.get(arg2));
} @Override
public void onNothingSelected(AdapterView<?> arg0)
{
}
});
layout.addView(sp);
} @Override
protected void onPause()
{
super.onPause();
if (isFinishing() && mPlayer != null)
{
// 释放全部对象
mVisualizer.release();
mEqualizer.release();
mPresetReverb.release();
mBass.release();
mPlayer.release();
mPlayer = null;
}
}
/**
* 依据Visualizer传来的数据动态绘制波形效果,分别为:
* 块状波形、柱状波形、曲线波形
*/
private static class MyVisualizerView extends View
{
// bytes数组保存了波形抽样点的值
private byte[] bytes;
private float[] points;
private Paint paint = new Paint();
private Rect rect = new Rect();
private byte type = 0;
public MyVisualizerView(Context context)
{
super(context);
bytes = null;
// 设置画笔的属性
paint.setStrokeWidth(1f);
paint.setAntiAlias(true);//抗锯齿
paint.setColor(Color.YELLOW);//画笔颜色
paint.setStyle(Style.FILL);
} public void updateVisualizer(byte[] ftt)
{
bytes = ftt;
// 通知该组件重绘自己。
invalidate();
} @Override
public boolean onTouchEvent(MotionEvent me)
{
// 当用户触碰该组件时,切换波形类型
if(me.getAction() != MotionEvent.ACTION_DOWN)
{
return false;
}
type ++;
if(type >= 3)
{
type = 0;
}
return true;
} @Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (bytes == null)
{
return;
}
// 绘制白色背景
canvas.drawColor(Color.WHITE);
// 使用rect对象记录该组件的宽度和高度
rect.set(0,0,getWidth(),getHeight());
switch(type)
{
// -------绘制块状的波形图-------
case 0:
for (int i = 0; i < bytes.length - 1; i++)
{
float left = getWidth() * i / (bytes.length - 1);
// 依据波形值计算该矩形的高度
float top = rect.height()-(byte)(bytes[i+1]+128)
* rect.height() / 128;
float right = left + 1;
float bottom = rect.height();
canvas.drawRect(left, top, right, bottom, paint);
}
break;
// -------绘制柱状的波形图(每隔18个抽样点绘制一个矩形)-------
case 1:
for (int i = 0; i < bytes.length - 1; i += 18)
{
float left = rect.width()*i/(bytes.length - 1);
// 依据波形值计算该矩形的高度
float top = rect.height()-(byte)(bytes[i+1]+128)
* rect.height() / 128;
float right = left + 6;
float bottom = rect.height();
canvas.drawRect(left, top, right, bottom, paint);
}
break;
// -------绘制曲线波形图-------
case 2:
// 假设point数组还未初始化
if (points == null || points.length < bytes.length * 4)
{
points = new float[bytes.length * 4];
}
for (int i = 0; i < bytes.length - 1; i++)
{
// 计算第i个点的x坐标
points[i * 4] = rect.width()*i/(bytes.length - 1);
// 依据bytes[i]的值(波形点的值)计算第i个点的y坐标
points[i * 4 + 1] = (rect.height() / 2)
+ ((byte) (bytes[i] + 128)) * 128
/ (rect.height() / 2);
// 计算第i+1个点的x坐标
points[i * 4 + 2] = rect.width() * (i + 1)
/ (bytes.length - 1);
// 依据bytes[i+1]的值(波形点的值)计算第i+1个点的y坐标
points[i * 4 + 3] = (rect.height() / 2)
+ ((byte) (bytes[i + 1] + 128)) * 128
/ (rect.height() / 2);
}
// 绘制波形曲线
canvas.drawLines(points, paint);
break;
}
}
}
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.oyp.media"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="17"/>
<!-- 使用音场效果必要的权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MediaPlayerTest"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

PS:请在真机环境下执行此程序,假设在模拟器下执行。可能会报错:
java.lang.RuntimeException: Cannot initialize Visualizer engine, error: -4



                            ====================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

====================================================================================

 

我的Android进阶之旅------&gt;Android实现音乐示波器、均衡器、重低音和音场功能的更多相关文章

  1. 我的Android进阶之旅------>Android实现音乐示波器、均衡器、重低音和音场功能

    本实例来自于<疯狂Android讲义>,要实现具体的功能,需要了解以下API: MediaPlayer  媒体播放器 Visualizer 频谱 Equalizer 均衡器 BassBoo ...

  2. 我的Android进阶之旅------>关于android:layout_weight属性的详细解析

    关于androidlayout_weight属性的详细解析 效果一 效果二 图3的布局代码 图4的布局代码 效果三 图7代码 图8代码 效果四 效果五 版权声明:本文为[欧阳鹏]原创文章,欢迎转载,转 ...

  3. 我的Android进阶之旅------>关于android:layout_weight属性的一个面试题

    最近碰到一个面试题,按照下图,由Button和EditText组成的界面下厨布局代码,解决这题目需要使用android:layout_weight的知识. 首先分析上图所示的界面可以看成一下3个部分. ...

  4. 我的Android进阶之旅------&gt; Android在TextView中显示图片方法

    面试题:请说出Android SDK支持哪些方式显示富文本信息(不同颜色.大小.并包括图像的文本信息).并简要说明实现方法. 答案:Android SDK支持例如以下显示富文本信息的方式. 1.使用T ...

  5. 我的Android进阶之旅------&gt;Android字符串资源中的单引號问题error: Apostrophe not preceded by 的解决的方法

    刚刚在string字符串资源文件里,写了一个单引號.报错了,错误代码例如以下 error: Apostrophe not preceded by \ (in OuyangPeng's blog ) 资 ...

  6. 我的Android进阶之旅------&gt; Android为TextView组件中显示的文本加入背景色

    通过上一篇文章 我的Android进阶之旅------> Android在TextView中显示图片方法 (地址:http://blog.csdn.net/ouyang_peng/article ...

  7. 我的Android进阶之旅------&gt;Android系统设置默认来电铃声、闹钟铃声、通知铃声

    首先了解Android系统本身提供的默认铃声文件,这些文件都放在  /system/media/audio  文件夹下. /system/media/audio/ringtones   系统来电铃声 ...

  8. 【我的Android进阶之旅】Android 混淆文件资源分类整理

    之前将所有的混淆都配置在一个 proguard-rules.pro 这个Android Studio新建项目时自动生成的文件里面,而随着项目功能迭代越来越多,代码量越来越多,引用的第二方库.第三方库都 ...

  9. 我的Android进阶之旅------&gt;Android关于Activity管理的一个简单封装

    怎样管理当前的执行Activity栈,怎样彻底退出程序.本文封装了一个Activity管理类,能够方便随时退出程序. import java.util.Stack; import android.ap ...

随机推荐

  1. npm --save-dev --save 的区别【转载】

    源地址:http://blog.csdn.net/juzipchy/article/details/65653683 npm install 在安装 npm 包时,有两种命令参数可以把它们的信息写入 ...

  2. python模块安装路径

    Unix(Linux): prefix/lib/pythonX.Y/site-packages 默认路径:/usr/local/lib/pythonX.Y/site-packages 另外,在Unix ...

  3. PTA L2-002 链表去重 团体程序设计天梯赛-练习集

    L2-002 链表去重(25 分)   给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉.即对每个键值 K,只有第一个绝对值等于 K 的结点被保留.同时,所有被删除的结点须被保存在另 ...

  4. CodeForces 738C Road to Cinema

    二分答案. 油量越多,显然通过的时间越少.可以二分找到最小的油量,可以在$t$时间内到达电影院. 一个油箱容量为$v$的车通过长度为$L$的路程需要的最小时间为$max(L,3*L-v)$.计算过程如 ...

  5. 洛谷P3975 跳房子 [DP,单调队列优化,二分答案]

    题目传送门 跳房子 题目描述 跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一. 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一 ...

  6. 洛谷——P3908 异或之和

    P3908 异或之和 题目描述 求1 \bigoplus 2 \bigoplus\cdots\bigoplus N1⨁2⨁⋯⨁N 的值. A \bigoplus BA⨁B 即AA , BB 按位异或. ...

  7. Codeforces Beta Round #13 E. Holes (分块)

    E. Holes time limit per test 1 second memory limit per test 64 megabytes input standard input output ...

  8. hdu 6047 Maximum Sequence 贪心

    Description Steph is extremely obsessed with “sequence problems” that are usually seen on magazines: ...

  9. [NOIP2017]时间复杂度(模拟)

    sscanf读入数字,getline(cin,string)读一整行,其余暴力模拟即可. #include<cstdio> #include<string> #include& ...

  10. AGC 022 B - GCD Sequence

    题面在这里! 锻炼脑子的小构造题... 一开始被 a[]<=30000 且 序列 gcd = 1所困扰,但是发现这并没有什么,因为我接下来发现了一种总是能构造出 序列和是6的倍数的方案. 首先如 ...