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

本实例来自于《疯狂Android讲义》,要实现具体的功能,需要了解以下API:

  • MediaPlayer  媒体播放器
  • Visualizer 频谱
  • Equalizer 均衡器
  • BassBoost 重低音控制器
  • PresetReverb 预设音场控制器
  • Paint 绘图

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

竖状波形图

块状波形图

曲线波形图

调节均衡器、重低音

选择音场

下面来看具体的实现代码

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

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-27 07:11:39

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

我的Android进阶之旅------&gt;Android疯狂连连看游戏的实现之实现游戏逻辑(五)

在上一篇<我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)>中提到的两个类: GameConf:负责管理游戏的初始化设置信息. GameService:负责游戏的逻辑实现. 其中GameConf的代码如下:cn\oyp\link\utils\GameConf.java package cn.oyp.link.utils; import android.content.Context; /** * 保存游戏配置的对象

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

面试题:请说出Android SDK支持哪些方式显示富文本信息(不同颜色.大小.并包含图像的文本信息),并简要说明实现方法. 答案:Android SDK支持如下显示富文本信息的方式. 1.使用TextView组件可以显示富文本信息.在TextView组件中可以使用富文本标签来显示富文本信息,这种标签类似于HTML标签,但比HTML标签简单,支持有限的几种显示富文本的方式.如<font>标签用于设置字体和颜色,<b>用于设置粗体.包含这些标签的文本不能直接作为TextView.se

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

通过上一篇文章 我的Android进阶之旅------> Android在TextView中显示图片方法 (地址:http://blog.csdn.net/ouyang_peng/article/details/46916963) 我们学会了在TextView中显示图片的方法,现在我们来学习如何为TextView组件中显示的文本添加背景色.要求完成的样子如图所示: 首先来学习使用BackgroundColorSpan对象设置文字背景色,代码如下: TextView textView=(TextV

我的Android进阶之旅------&gt;Android利用Sensor(传感器)实现水平仪功能的小例

这里介绍的水平仪,指的是比较传统的气泡水平仪,在一个透明圆盘内充满液体,液体中留有一个气泡,当一端翘起时,该气泡就会浮向翘起的一端. 利用方向传感器返回的第一个参数,实现了一个指南针小应用. 我的Android进阶之旅------>Android利用Sensor(传感器)实现指南针功能 (地址:http://blog.csdn.net/ouyang_peng/article/details/8801204) 接下来,我们利用返回的第二.三个参数实现该水平仪.因为第二个参数,反映底部翘起的角度(当

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

刚刚在string字符串资源文件中,写了一个单引号,报错了,错误代码如下 error: Apostrophe not preceded by \ (in OuyangPeng's blog ) 资源文件如下: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="ouyang">OuyangPeng's blog </string

我的Android进阶之旅------&gt;Android二级ListView列表的实现

实现如下图所示的二级列表效果 首先是在布局文件中,布局两个ListView,代码如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height=&

我的Android进阶之旅------&gt;Android嵌入图像InsetDrawable的用法

面试题:为一个充满整个屏幕的LinearLayout布局指定背景图,是否可以让背景图不充满屏幕?请用代码描述实现过程. 解决此题,可以使用嵌入(Inset)图像资源来指定图像,然后像使用普通图像资源一样使用嵌入图像资源. 语法如下: <?xml version="1.0" encoding="utf-8"?> <inset xmlns:android="http://schemas.android.com/apk/res/android&

我的Android进阶之旅------&gt;Android使用AlarmManager全局定时器实现定时更换壁纸

该DEMO将会通过AlarmManager来周期的调用ChangeService,从而让系统实现定时更换壁纸的功能. 更换壁纸的API为android.app.WallpaperManager,它提供了clear()方法来清除壁纸,还提供了如下方法来设置壁纸. setResource(int resid)将壁纸设置为resid资源所代表的图片 setBitmap(Bitmap bitmap)将壁纸设置为bitmap所代表的位图 setStream(InputStream data)将壁纸设置为d

我的Android进阶之旅------&gt;Android中android:windowSoftInputMode的用法

面试题:如何在显示某个Activity时立即弹出软键盘? 答案:在AndroidManifest.xml文件中设置<activity>标签的android:windowSoftInputMode属性可以在显示Activity时立即弹出当前输入法的软键盘(不管是否有获得焦点的空间). 设置为:android:windowSoftInputMode="stateVisible|adjustPan"   代码如下: <activity android:name="