本实例来自于《疯狂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. Visual Studio for Mac 安装时无法连接到网络等问题

    问题: 1.下载 vs for mac 离线安装包 离线下载地址https://download.microsoft.com/download/3/d/4/3d42f40f-4f0a-4613-920 ...

  2. Ace Admin 学习笔记

    1. jqGrid  提交编辑数据,控制台报:Synchronous XMLHttpRequest on the main thread... jqGrid的选项设置async: true选项: aj ...

  3. PHP策略设计模式

    <?php /** 抽象策略角色,以接口实现 */ interface Strategy { /** 算法接口 */ public function algorithmInterface(); ...

  4. HDU 2665.Kth number-可持久化线段树(无修改区间第K小)模板 (POJ 2104.K-th Number 、洛谷 P3834 【模板】可持久化线段树 1(主席树)只是输入格式不一样,其他几乎都一样的)

    Kth number Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  5. POJ 1860 Currency Exchange【SPFA判环】

    Several currency exchange points are working in our city. Let us suppose that each point specializes ...

  6. 深度学习应用系列(二) | 如何使用keras进行迁移学习,以训练和识别自己的图片集

    本文的keras后台为tensorflow,介绍如何利用预编译的模型进行迁移学习,以训练和识别自己的图片集. 官网 https://keras.io/applications/ 已经介绍了各个基于Im ...

  7. Knockout.js(二):监控数组属性(Observables Arrays)

    如果想发现并响应一个对象的变化,就应该使用监控属性(observables),如果想发现并响应一个集合的变化,就需要使用监控属性数组(observableArray).在很多情况下,它都非常有用,比如 ...

  8. Hive 默认分区

    在hive里面表可以创建成分区表,但是当分区字段的值是'' 或者 null时 hive会自动将分区命名为默认分区名称. 默认情况下,默认分区的名称为__HIVE_DEFAULT_PARTITION__ ...

  9. HDU5742 It's All In The Mind 数学思维题

    Problem Description Professor Zhang has a number sequence a1,a2,...,an. However, the sequence is not ...

  10. POJ2975 Nim 博弈论 尼姆博弈

    http://poj.org/problem?id=2975 题目始终是ac的最大阻碍. 问只取一堆有多少方案可以使当前局面为先手必败. 显然由尼姆博弈的性质可以知道需要取石子使所有堆石子数异或和为0 ...