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

来看下效果示意图,如下所示

竖状波形图

块状波形图

曲线波形图


调节均衡器、重低音

选择音场


下面来看具体的实现代码    
MediaPlayerTest.java
  1. package com.oyp.media;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. import android.app.Activity;
  7. import android.content.Context;
  8. import android.graphics.Canvas;
  9. import android.graphics.Color;
  10. import android.graphics.Paint;
  11. import android.graphics.Paint.Style;
  12. import android.graphics.Rect;
  13. import android.media.AudioManager;
  14. import android.media.MediaPlayer;
  15. import android.media.audiofx.BassBoost;
  16. import android.media.audiofx.Equalizer;
  17. import android.media.audiofx.PresetReverb;
  18. import android.media.audiofx.Visualizer;
  19. import android.os.Bundle;
  20. import android.view.Gravity;
  21. import android.view.MotionEvent;
  22. import android.view.View;
  23. import android.view.ViewGroup;
  24. import android.widget.AdapterView;
  25. import android.widget.ArrayAdapter;
  26. import android.widget.LinearLayout;
  27. import android.widget.SeekBar;
  28. import android.widget.Spinner;
  29. import android.widget.TextView;
  30.  
  31. public class MediaPlayerTest extends Activity
  32. {
  33. // 定义播放声音的MediaPlayer
  34. private MediaPlayer mPlayer;
  35. // 定义系统的频谱
  36. private Visualizer mVisualizer;
  37. // 定义系统的均衡器
  38. private Equalizer mEqualizer;
  39. // 定义系统的重低音控制器
  40. private BassBoost mBass;
  41. // 定义系统的预设音场控制器
  42. private PresetReverb mPresetReverb;
  43. private LinearLayout layout;
  44. private List<Short> reverbNames = new ArrayList<Short>();
  45. private List<String> reverbVals = new ArrayList<String>();
  46.  
  47. @Override
  48. public void onCreate(Bundle savedInstanceState)
  49. {
  50. super.onCreate(savedInstanceState);
  51. //设置音频流 - STREAM_MUSIC:音乐回放即媒体音量
  52. setVolumeControlStream(AudioManager.STREAM_MUSIC);
  53. layout = new LinearLayout(this);//代码创建布局
  54. layout.setOrientation(LinearLayout.VERTICAL);//设置为线性布局-上下排列
  55. setContentView(layout);//将布局添加到 Activity
  56. // 创建MediaPlayer对象,并添加音频
  57. // 音频路径为 res/raw/beautiful.mp3
  58. mPlayer = MediaPlayer.create(this, R.raw.beautiful);
  59. // 初始化示波器
  60. setupVisualizer();
  61. // 初始化均衡控制器
  62. setupEqualizer();
  63. // 初始化重低音控制器
  64. setupBassBoost();
  65. // 初始化预设音场控制器
  66. setupPresetReverb();
  67. // 开发播放音乐
  68. mPlayer.start();
  69. }
  70. /**
  71. * 初始化频谱
  72. */
  73. private void setupVisualizer()
  74. {
  75. // 创建MyVisualizerView组件,用于显示波形图
  76. final MyVisualizerView mVisualizerView =
  77. new MyVisualizerView(this);
  78. mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
  79. ViewGroup.LayoutParams.MATCH_PARENT,
  80. (int) (120f * getResources().getDisplayMetrics().density)));
  81. // 将MyVisualizerView组件添加到layout容器中
  82. layout.addView(mVisualizerView);
  83. // 以MediaPlayer的AudioSessionId创建Visualizer
  84. // 相当于设置Visualizer负责显示该MediaPlayer的音频数据
  85. mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
  86. //设置需要转换的音乐内容长度,专业的说这就是采样,该采样值一般为2的指数倍,如64,128,256,512,1024。
  87. mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
  88. // 为mVisualizer设置监听器
  89. /*
  90. * Visualizer.setDataCaptureListener(OnDataCaptureListener listener, int rate, boolean waveform, boolean fft
  91. *
  92. * listener,表监听函数,匿名内部类实现该接口,该接口需要实现两个函数
  93. rate, 表示采样的周期,即隔多久采样一次,联系前文就是隔多久采样128个数据
  94. iswave,是波形信号
  95. isfft,是FFT信号,表示是获取波形信号还是频域信号
  96.  
  97. */
  98. mVisualizer.setDataCaptureListener(
  99. new Visualizer.OnDataCaptureListener()
  100. {
  101. //这个回调应该采集的是快速傅里叶变换有关的数据
  102. @Override
  103. public void onFftDataCapture(Visualizer visualizer,
  104. byte[] fft, int samplingRate)
  105. {
  106. }
  107. //这个回调应该采集的是波形数据
  108. @Override
  109. public void onWaveFormDataCapture(Visualizer visualizer,
  110. byte[] waveform, int samplingRate)
  111. {
  112. // 用waveform波形数据更新mVisualizerView组件
  113. mVisualizerView.updateVisualizer(waveform);
  114. }
  115. }, Visualizer.getMaxCaptureRate() / 2, true, false);
  116. mVisualizer.setEnabled(true);
  117. }
  118.  
  119. /**
  120. * 初始化均衡控制器
  121. */
  122. private void setupEqualizer()
  123. {
  124. // 以MediaPlayer的AudioSessionId创建Equalizer
  125. // 相当于设置Equalizer负责控制该MediaPlayer
  126. mEqualizer = new Equalizer(0, mPlayer.getAudioSessionId());
  127. // 启用均衡控制效果
  128. mEqualizer.setEnabled(true);
  129. TextView eqTitle = new TextView(this);
  130. eqTitle.setText("均衡器:");
  131. layout.addView(eqTitle);
  132. // 获取均衡控制器支持最小值和最大值
  133. final short minEQLevel = mEqualizer.getBandLevelRange()[0];//第一个下标为最低的限度范围
  134. short maxEQLevel = mEqualizer.getBandLevelRange()[1]; // 第二个下标为最高的限度范围
  135. // 获取均衡控制器支持的所有频率
  136. short brands = mEqualizer.getNumberOfBands();
  137. for (short i = 0; i < brands; i++)
  138. {
  139. TextView eqTextView = new TextView(this);
  140. // 创建一个TextView,用于显示频率
  141. eqTextView.setLayoutParams(new ViewGroup.LayoutParams(
  142. ViewGroup.LayoutParams.MATCH_PARENT,
  143. ViewGroup.LayoutParams.WRAP_CONTENT));
  144. eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
  145. // 设置该均衡控制器的频率
  146. eqTextView.setText((mEqualizer.getCenterFreq(i) / 1000)
  147. + " Hz");
  148. layout.addView(eqTextView);
  149. // 创建一个水平排列组件的LinearLayout
  150. LinearLayout tmpLayout = new LinearLayout(this);
  151. tmpLayout.setOrientation(LinearLayout.HORIZONTAL);
  152. // 创建显示均衡控制器最小值的TextView
  153. TextView minDbTextView = new TextView(this);
  154. minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
  155. ViewGroup.LayoutParams.WRAP_CONTENT,
  156. ViewGroup.LayoutParams.WRAP_CONTENT));
  157. // 显示均衡控制器的最小值
  158. minDbTextView.setText((minEQLevel / 100) + " dB");
  159. // 创建显示均衡控制器最大值的TextView
  160. TextView maxDbTextView = new TextView(this);
  161. maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
  162. ViewGroup.LayoutParams.WRAP_CONTENT,
  163. ViewGroup.LayoutParams.WRAP_CONTENT));
  164. // 显示均衡控制器的最大值
  165. maxDbTextView.setText((maxEQLevel / 100) + " dB");
  166. LinearLayout.LayoutParams layoutParams = new
  167. LinearLayout.LayoutParams(
  168. ViewGroup.LayoutParams.MATCH_PARENT,
  169. ViewGroup.LayoutParams.WRAP_CONTENT);
  170. layoutParams.weight = 1;
  171. // 定义SeekBar做为调整工具
  172. SeekBar bar = new SeekBar(this);
  173. bar.setLayoutParams(layoutParams);
  174. bar.setMax(maxEQLevel - minEQLevel);
  175. bar.setProgress(mEqualizer.getBandLevel(i));
  176. final short brand = i;
  177. // 为SeekBar的拖动事件设置事件监听器
  178. bar.setOnSeekBarChangeListener(new SeekBar
  179. .OnSeekBarChangeListener()
  180. {
  181. @Override
  182. public void onProgressChanged(SeekBar seekBar,
  183. int progress, boolean fromUser)
  184. {
  185. // 设置该频率的均衡值
  186. mEqualizer.setBandLevel(brand,
  187. (short) (progress + minEQLevel));
  188. }
  189. @Override
  190. public void onStartTrackingTouch(SeekBar seekBar)
  191. {
  192. }
  193. @Override
  194. public void onStopTrackingTouch(SeekBar seekBar)
  195. {
  196. }
  197. });
  198. // 使用水平排列组件的LinearLayout“盛装”3个组件
  199. tmpLayout.addView(minDbTextView);
  200. tmpLayout.addView(bar);
  201. tmpLayout.addView(maxDbTextView);
  202. // 将水平排列组件的LinearLayout添加到myLayout容器中
  203. layout.addView(tmpLayout);
  204. }
  205. }
  206.  
  207. /**
  208. * 初始化重低音控制器
  209. */
  210. private void setupBassBoost()
  211. {
  212. // 以MediaPlayer的AudioSessionId创建BassBoost
  213. // 相当于设置BassBoost负责控制该MediaPlayer
  214. mBass = new BassBoost(0, mPlayer.getAudioSessionId());
  215. // 设置启用重低音效果
  216. mBass.setEnabled(true);
  217. TextView bbTitle = new TextView(this);
  218. bbTitle.setText("重低音:");
  219. layout.addView(bbTitle);
  220. // 使用SeekBar做为重低音的调整工具
  221. SeekBar bar = new SeekBar(this);
  222. // 重低音的范围为0~1000
  223. bar.setMax(1000);
  224. bar.setProgress(0);
  225. // 为SeekBar的拖动事件设置事件监听器
  226. bar.setOnSeekBarChangeListener(new SeekBar
  227. .OnSeekBarChangeListener()
  228. {
  229. @Override
  230. public void onProgressChanged(SeekBar seekBar
  231. , int progress, boolean fromUser)
  232. {
  233. // 设置重低音的强度
  234. mBass.setStrength((short) progress);
  235. }
  236. @Override
  237. public void onStartTrackingTouch(SeekBar seekBar)
  238. {
  239. }
  240. @Override
  241. public void onStopTrackingTouch(SeekBar seekBar)
  242. {
  243. }
  244. });
  245. layout.addView(bar);
  246. }
  247.  
  248. /**
  249. * 初始化预设音场控制器
  250. */
  251. private void setupPresetReverb()
  252. {
  253. // 以MediaPlayer的AudioSessionId创建PresetReverb
  254. // 相当于设置PresetReverb负责控制该MediaPlayer
  255. mPresetReverb = new PresetReverb(0,
  256. mPlayer.getAudioSessionId());
  257. // 设置启用预设音场控制
  258. mPresetReverb.setEnabled(true);
  259. TextView prTitle = new TextView(this);
  260. prTitle.setText("音场");
  261. layout.addView(prTitle);
  262. // 获取系统支持的所有预设音场
  263. for (short i = 0; i < mEqualizer.getNumberOfPresets(); i++)
  264. {
  265. reverbNames.add(i);
  266. reverbVals.add(mEqualizer.getPresetName(i));
  267. }
  268. // 使用Spinner做为音场选择工具
  269. Spinner sp = new Spinner(this);
  270. sp.setAdapter(new ArrayAdapter<String>(MediaPlayerTest.this,
  271. android.R.layout.simple_spinner_item, reverbVals));
  272. // 为Spinner的列表项选中事件设置监听器
  273. sp.setOnItemSelectedListener(new Spinner
  274. .OnItemSelectedListener()
  275. {
  276. @Override
  277. public void onItemSelected(AdapterView<?> arg0
  278. , View arg1, int arg2, long arg3)
  279. {
  280. // 设定音场
  281. mPresetReverb.setPreset(reverbNames.get(arg2));
  282. }
  283.  
  284. @Override
  285. public void onNothingSelected(AdapterView<?> arg0)
  286. {
  287. }
  288. });
  289. layout.addView(sp);
  290. }
  291.  
  292. @Override
  293. protected void onPause()
  294. {
  295. super.onPause();
  296. if (isFinishing() && mPlayer != null)
  297. {
  298. // 释放所有对象
  299. mVisualizer.release();
  300. mEqualizer.release();
  301. mPresetReverb.release();
  302. mBass.release();
  303. mPlayer.release();
  304. mPlayer = null;
  305. }
  306. }
  307. /**
  308. * 根据Visualizer传来的数据动态绘制波形效果,分别为:
  309. * 块状波形、柱状波形、曲线波形
  310. */
  311. private static class MyVisualizerView extends View
  312. {
  313. // bytes数组保存了波形抽样点的值
  314. private byte[] bytes;
  315. private float[] points;
  316. private Paint paint = new Paint();
  317. private Rect rect = new Rect();
  318. private byte type = 0;
  319. public MyVisualizerView(Context context)
  320. {
  321. super(context);
  322. bytes = null;
  323. // 设置画笔的属性
  324. paint.setStrokeWidth(1f);
  325. paint.setAntiAlias(true);//抗锯齿
  326. paint.setColor(Color.YELLOW);//画笔颜色
  327. paint.setStyle(Style.FILL);
  328. }
  329.  
  330. public void updateVisualizer(byte[] ftt)
  331. {
  332. bytes = ftt;
  333. // 通知该组件重绘自己。
  334. invalidate();
  335. }
  336.  
  337. @Override
  338. public boolean onTouchEvent(MotionEvent me)
  339. {
  340. // 当用户触碰该组件时,切换波形类型
  341. if(me.getAction() != MotionEvent.ACTION_DOWN)
  342. {
  343. return false;
  344. }
  345. type ++;
  346. if(type >= 3)
  347. {
  348. type = 0;
  349. }
  350. return true;
  351. }
  352.  
  353. @Override
  354. protected void onDraw(Canvas canvas)
  355. {
  356. super.onDraw(canvas);
  357. if (bytes == null)
  358. {
  359. return;
  360. }
  361. // 绘制白色背景
  362. canvas.drawColor(Color.WHITE);
  363. // 使用rect对象记录该组件的宽度和高度
  364. rect.set(0,0,getWidth(),getHeight());
  365. switch(type)
  366. {
  367. // -------绘制块状的波形图-------
  368. case 0:
  369. for (int i = 0; i < bytes.length - 1; i++)
  370. {
  371. float left = getWidth() * i / (bytes.length - 1);
  372. // 根据波形值计算该矩形的高度
  373. float top = rect.height()-(byte)(bytes[i+1]+128)
  374. * rect.height() / 128;
  375. float right = left + 1;
  376. float bottom = rect.height();
  377. canvas.drawRect(left, top, right, bottom, paint);
  378. }
  379. break;
  380. // -------绘制柱状的波形图(每隔18个抽样点绘制一个矩形)-------
  381. case 1:
  382. for (int i = 0; i < bytes.length - 1; i += 18)
  383. {
  384. float left = rect.width()*i/(bytes.length - 1);
  385. // 根据波形值计算该矩形的高度
  386. float top = rect.height()-(byte)(bytes[i+1]+128)
  387. * rect.height() / 128;
  388. float right = left + 6;
  389. float bottom = rect.height();
  390. canvas.drawRect(left, top, right, bottom, paint);
  391. }
  392. break;
  393. // -------绘制曲线波形图-------
  394. case 2:
  395. // 如果point数组还未初始化
  396. if (points == null || points.length < bytes.length * 4)
  397. {
  398. points = new float[bytes.length * 4];
  399. }
  400. for (int i = 0; i < bytes.length - 1; i++)
  401. {
  402. // 计算第i个点的x坐标
  403. points[i * 4] = rect.width()*i/(bytes.length - 1);
  404. // 根据bytes[i]的值(波形点的值)计算第i个点的y坐标
  405. points[i * 4 + 1] = (rect.height() / 2)
  406. + ((byte) (bytes[i] + 128)) * 128
  407. / (rect.height() / 2);
  408. // 计算第i+1个点的x坐标
  409. points[i * 4 + 2] = rect.width() * (i + 1)
  410. / (bytes.length - 1);
  411. // 根据bytes[i+1]的值(波形点的值)计算第i+1个点的y坐标
  412. points[i * 4 + 3] = (rect.height() / 2)
  413. + ((byte) (bytes[i + 1] + 128)) * 128
  414. / (rect.height() / 2);
  415. }
  416. // 绘制波形曲线
  417. canvas.drawLines(points, paint);
  418. break;
  419. }
  420. }
  421. }
  422. }

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. package="com.oyp.media"
  5. android:versionCode="1"
  6. android:versionName="1.0">
  7. <uses-sdk android:minSdkVersion="10"
  8. android:targetSdkVersion="17"/>
  9. <!-- 使用音场效果必要的权限 -->
  10. <uses-permission android:name="android.permission.RECORD_AUDIO" />
  11. <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  12.  
  13. <application
  14. android:icon="@drawable/ic_launcher"
  15. android:label="@string/app_name">
  16. <activity
  17. android:name=".MediaPlayerTest"
  18. android:label="@string/app_name">
  19. <intent-filter>
  20. <action android:name="android.intent.action.MAIN" />
  21. <category android:name="android.intent.category.LAUNCHER" />
  22. </intent-filter>
  23. </activity>
  24. </application>
  25. </manifest>

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



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

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

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

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

 

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

  1. 我的Android进阶之旅------&gt;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. 【转】java8中谨慎使用实数作为HashMap的key!

    java8中谨慎使用实数作为HashMap的key! java8中一个hashCode()函数引发的血案java8中一个hashCode()函数引发的血案1.起因2.实数的hashCode()3.总结 ...

  2. 【mob】Android短信验证+源码

    在很多的应用当中,都涉及到了短信验证的功能,比如在注册或者找回密码的时候,那么我们如何通过第三方的平台来完成这个功能呢? 本面博文就实现短信验证,来做一个小的栗子. 第一步-下载开发包 第二步-将SD ...

  3. Python操作sqlite数据库小节

    学习了Python操作sqlite数据库,做一个小结,以备后用. import sqlite3import os# 进行数据库操作时,主要是参数如何传输try:# 链接数据库conn=sqlite3. ...

  4. Linux 设备驱动模型

    Linux系统将设备和驱动归一到设备驱动模型中了来管理 设备驱动程序功能: 1,对硬件设备初始化和释放 2,对设备进行管理,包括实参设置,以及提供对设备的统一操作接口 3,读取应用程序传递给设备文件的 ...

  5. fiddler使用心得记录

    fiddler是一款非常好用的软件,通过监听8888端口来修改http,https等请求和响应,是抓包神器. 最近正在学习如何使用,现在记录下学习的一些技巧 如何支持https 点击菜单项tools, ...

  6. MFC中的CDC详细教程

    参考:  MFC中的CDC详细教程1,2,3 StretchDIBits用法

  7. [转]MySQL的简单使用和JDBC示例

    MySql简单操作 //启动mysql net start mysql //登陆 mysql -u root -p //创建建数据库 create database mydb; create data ...

  8. 转:java工程师成神之路

    转自: http://www.hollischuang.com/archives/489 一.基础篇 1.1 JVM 1.1.1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 htt ...

  9. Linux下快速安装Mysql及使用

    1.安装 查看有没有安装过: yum list installed mysql* rpm -qa | grep mysql* 查看有没有安装包: yum list mysql* 安装mysql客户端: ...

  10. 《linux 内核全然剖析》 mktime.c

    tm结构体的定义在time.h里面 struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_y ...