Android 播放音频文件

MainActivity

package org.wp.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

/**
 * 使用MediaPlayer来播放音频文件存在一些不足: 
 * 例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。
 * 这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,
 * 例如在对时间精准度要求相对较高的游戏开发中。
 * 游戏里面同时播放多个音效是常有的事,用过MediaPlayer的朋友都该知道,
 * 它是不支持实时播放多个声音的,会出现或多或少的延迟,
 * 而且这个延迟是无法让人忍受的,
 * 尤其是在快速连续播放声音(比如连续猛点按钮)时,会非常明显,
 * 长的时候会出现3~5秒的延迟,
 * 【使用MediaPlayer.seekTo()这个方法来解决此问题】;
 * 
 * 
 * 
 * 相对于使用SoundPool存在的一些问题:
 * 1. SoundPool最大只能申请1M的内存空间,
 * 这就意味着我们只能使用一些很短的声音片段,
 * 而不是用它来播放歌曲或者游戏背景音乐
 * (背景音乐可以考虑使用JetPlayer来播放)。
 * 
 * 2. SoundPool提供了pause和stop方法,
 * 但这些方法建议最好不要轻易使用,
 * 因为有些时候它们可能会使你的程序莫名其妙的终止。
 * 还有些朋友反映它们不会立即中止播放声音,
 * 而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。
 * 
 * 3.音频格式建议使用OGG格式。
 * 使用WAV格式的音频文件存放游戏音效,经过反复测试,
 * 在音效播放间隔较短的情况下会出现异常关闭的情况
 * (有说法是SoundPool目前只对16bit的WAV文件有较好的支持)。
 * 后来将文件转成OGG格式,问题得到了解决。
 * 
 * 4.在使用SoundPool播放音频的时候,
 * 如果在初始化中就调用播放函数进行播放音乐那么根本没有声音,
 * 不是因为没有执行,而是SoundPool需要一准备时间!
 * 囧。当然这个准备时间也很短,不会影响使用,
 * 只是程序一运行就播放会没有声音罢了,
 * 所以我把SoundPool播放写在了按键中处理了
 * 
 * 
 * 怎么才知道一首歌曲播放完了,那么这里给说下:
 * PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,
 * 并会触发OnCompletionListener的onCompletion()方法。
 * 此时可以调用start()方法重新从头播放文件,
 * 也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。
 * 注意:1、 别忘记绑定操作! mp.setOnCompletionListener(this);
 * 2、如果你设置了循环播放  mp.setLooping(true); 的话,
 * 那么永远都不会监听到播放完成的状态!!!!这里一定要注意!
 * 
 * @author wp
 * 
 */
public class MainActivity extends Activity {
	public static Activity INSTANCE;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		INSTANCE = this;
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(new MySurfaceView(this));
	}
}

 

MySurfaceView

package org.wp.activity;

import java.util.HashMap;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * ==============================================================
 * AudioManager
 * ==============================================================
 * AudioManager provides access to volume and ringer mode control. 
 * Use Context.getSystemService(Context.AUDIO_SERVICE) 
 * to get an instance of this class.
 * 
 * AudioManager 类提供访问音量和振铃模式的控制。
 * 用Context.getSystemService(Context.AUDIO_SERVICE)得到这个类的实例。
 * 
 * 
 * ==============================================================
 * getStreamMaxVolume
 * ==============================================================
 * public int getStreamMaxVolume (int streamType) 
 * Since: API Level 1 Returns the maximum volume index for a particular stream. 
 * Parameters 
 * streamType The stream type whose maximum volume index is returned. 
 * Returns The maximum valid volume index for the stream.
 * 
 * public int getStreamMaxVolume (int streamType) 
 * 返回特定流的最大音量索引。 
 * 参数 
 * streamType 返回最大音量索引的流类型。 
 * 返回值 流的最大有效音量索引。
 * 
 * 
 * ==============================================================
 * STREAM_MUSIC
 * ==============================================================
 * public static final int STREAM_MUSIC 
 * Since: API Level 1 The audio stream for music playback
 * 
 * public static final int STREAM_MUSIC 用于音乐回放的音频流。
 * 
 * ==============================================================
 * SoundPool
 * ==============================================================
 * The SoundPool class manages and plays audio resources for applications. 
 * 
 * public SoundPool (int maxStreams, int streamType, int srcQuality) 
 * Parameters
 * maxStreams  the maximum number of simultaneous streams for this SoundPool object 
 * streamType  the audio stream type as described in AudioManager For example, 
 *             game applications will normally use STREAM_MUSIC. 
 * srcQuality  the sample-rate converter quality. Currently has no effect. 
 *             Use 0 for the default. 
 * Returns
 * a SoundPool object, or null if creation failed 
 * 
 * public SoundPool (int maxStream, int streamType, int srcQuality) 
 * maxStream —— 同时播放的流的最大数量
 * streamType —— 流的类型,一般为STREAM_MUSIC(具体在AudioManager类中列出)
 * srcQuality —— 采样率转化质量,当前无效果,使用0作为默认值
 * 
 * 
 * ==============================================================
 * load
 * ==============================================================
 * public int load (Context context, int resId, int priority) 
 * Since: API Level 1 Load the sound from the specified APK resource. 
 * Note that the extension is dropped. For example, if you want to load a sound 
 * from the raw resource file "explosion.mp3", you would specify "R.raw.explosion" 
 * as the resource ID. Note that this means you cannot have both an "explosion.wav" 
 * and an "explosion.mp3" in the res/raw directory.
 * 
 * Parameters
 * context  the application context 
 * resId  the resource ID 
 * priority  the priority of the sound. Currently has no effect. 
 * Use a value of 1 for future compatibility.
 * Returns
 * a sound ID. This value can be used to play or unload the sound.
 *  
 * priority  流的优先级,值越大优先级高,标识优先考虑的声音
 *           影响当同时播放数量超出了最大支持数时SoundPool对该流的处理
 *           这个参数目前没有效果,建议设置为1
 *           使用了也只是对未来的兼容性价值。
 * 
 * @author wp
 * 
 */
public class MySurfaceView extends SurfaceView implements
		SurfaceHolder.Callback, Runnable {
	private SurfaceHolder sfh;
	private Paint paint;
	private Canvas canvas;
	private AudioManager am;
	private int currentVol, maxVol;
	private MediaPlayer player;
	private SoundPool soundPool;
	private HashMap<Integer, Integer> soundPoolMap;
	private int loadId;
	private boolean ON = true;

	public MySurfaceView(Context context) {
		super(context);
		this.setKeepScreenOn(true);
		this.setFocusable(true);
		sfh = this.getHolder();
		sfh.addCallback(this);
		paint = new Paint();
		paint.setAntiAlias(true);
		paint.setColor(Color.RED);
		am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
		// 获取最大音量值(15最大,不是100!)
		maxVol = am.getStreamVolume(AudioManager.STREAM_MUSIC);
		/** static MediaPlayer create(Context context, int resid)
		           根据给定的资源id创建MediaPlayer对象. **/
		player = MediaPlayer.create(context, R.raw.wp);
		player.setLooping(true);
		soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 100);
		/*
		 * 我这里使用hashmap主要是为了存入多个音频的ID,播放的时候可以同时播放多个音频。
		 * SoundPool可以支持多个音频同时播放,
		 * 而且SoundPool在播放的时候调用的这个方法
		 * soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f); 
		 * 第一个参数指的就是之前的loadId
		 * 是通过soundPool.load(context, R.raw.himi_ogg, 1);方法取出来的
		 */
		soundPoolMap = new HashMap<Integer, Integer>();
		soundPoolMap.put(1, soundPool.load(context, R.raw.wp_ogg, 1));
		loadId = soundPool.load(context, R.raw.wp_ogg, 1);
	}
	
	/**
	 * ==============================================================
	 * setVolumeControlStream
	 * ==============================================================
	 * public final void setVolumeControlStream (int streamType) 
	 * Since: API Level 1 Suggests an audio stream whose volume 
	 * should be changed by the hardware volume controls. 
	 * The suggested audio stream will be tied to the window of this Activity. 
	 * If the Activity is switched, the stream set here is no longer the suggested stream. 
	 * The client does not need to save and restore the old suggested 
	 * stream value in onPause and onResume.
	 * Parameters
	 * streamType  The type of the audio stream whose volume 
	 * should be changed by the hardware volume controls. 
	 * It is not guaranteed that the hardware volume controls will always change 
	 * this stream's volume 
	 * (for example, if a call is in progress, its stream's volume may be changed instead). 
	 * To reset back to the default, use USE_DEFAULT_STREAM_TYPE.  
	 * 
	 * Android OS中,如果你去按手机上的调节音量的按钮,会分两种情况,
	 * 一种是调整手机本身的铃声音量,一种是调整游戏,软件,音乐播放的音量
	 * 当我们在游戏中的时候 ,总是想调整游戏的音量而不是手机的铃声音量,
	 * 可是烦人的问题又来了,我在开发中发现,只有游戏中有声音在播放的时候,
	 * 你才能去调整游戏的音量,否则就是手机的音量
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// 设定调整音量为媒体音量,当暂停播放的时候调整音量就不会再默认调整铃声音量了
		MainActivity.INSTANCE.setVolumeControlStream(AudioManager.STREAM_MUSIC);
		player.start(); // 开始播放音乐
		new Thread(this).start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}
	
	@Override
	public void run() {
		while (true) {
			draw();
			logic();
			try {
				Thread.sleep(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	private void draw() {
		canvas = sfh.lockCanvas();
		canvas.drawColor(Color.WHITE);
		canvas.drawText("当前音量: " + currentVol, 100, 100, paint);
		canvas.drawText("当前播放的时间: " + player.getCurrentPosition() + "毫秒", 100, 130, paint);
		canvas.drawText("方向键中间按钮切换 暂停/开始", 100, 160, paint);
		canvas.drawText("方向键←键快退5秒 ", 100, 190, paint);
		canvas.drawText("方向键→键快进5秒 ", 100, 220, paint);
		canvas.drawText("方向键↑键增加音量 ", 100, 250, paint);
		canvas.drawText("方向键↓键减小音量", 100, 280, paint);
		sfh.unlockCanvasAndPost(canvas);
	}

	/**
	 * ==============================================================
	 * getStreamVolume
	 * ==============================================================
	 * public int getStreamVolume (int streamType) 
	 * Since: API Level 1 Returns the current volume index for a particular stream. 
	 * Parameters 
	 * streamType The stream type whose volume index is returned. 
	 * Returns 
	 * The current volume index for the stream.
	 * 
	 * public int getStreamVolume (int streamType) 
	 * 返回特定类的当前音量索引。 
	 * 参数 
	 * streamType 返回音量索引的流类型。 
	 * 返回值 
	 * 流的当前音量索引。
	 */
	private void logic() {
		currentVol = am.getStreamVolume(AudioManager.STREAM_MUSIC); // 不停获得当前音量大小
	}

	/**
	 * ==============================================================
	 * setStreamVolume
	 * ==============================================================
	 * public void setStreamVolume (int streamType, int index, int flags) 
	 * Since: API Level 1 Sets the volume index for a particular stream.
	 * Parameters
	 * streamType  The stream whose volume index should be set. 
	 * index The volume index to set. 
	 *       See getStreamMaxVolume(int) for the largest valid value. 
	 * flags One or more flags. 
	 * 
	 * public void setStreamVolume (int streamType, int index, int flags) 
	 * 设置特定流的音量索引。
	 * 参数
	 * streamType 欲设置音量索引的流。 
	 * index 欲设置的音量索引。
	 *       参照getStreamMaxVolume(int) 获得最大有效值。
	 * flags 一个或多个标志。
	 * 
	 * 
	 * ==============================================================
	 * FLAG_PLAY_SOUND
	 * ==============================================================
	 * public static final int FLAG_PLAY_SOUND 
	 * Since: API Level 1 Whether to play a sound when changing the volume. 
	 * If this is given to adjustVolume(int, int) or adjustSuggestedStreamVolume(int, int, int), 
	 * it may be ignored in some cases (for example, the decided stream type is not STREAM_RING, 
	 * or the volume is being adjusted downward).
	 * 
	 * public static final int FLAG_PLAY_SOUND
	 * 当改变音量时,是否播放声音。
	 * 若由adjustVolume(int, int)或adjustSuggestedStreamVolume(int, int, int)给出,
	 * 则在某些情况下会被忽略(例如,采用的流类型不是STREAM_RING,或正在向下调整音量)。
	 * 
	 * ==============================================================
	 * seekTo
	 * ==============================================================
	 * public void seekTo (int msec) 
	 * Since: API Level 1 Seeks to specified time position.
	 * Parameters
	 * msec  the offset in milliseconds from the start to seek to 
	 * Throws
	 * IllegalStateException  if the internal player engine has not been initialized
	 * 
	 * 
	 * ==============================================================
	 * play
	 * ==============================================================
	 * public final int play (int soundID, float leftVolume, float rightVolume, 
	 * 							int priority, int loop, float rate) 
	 * Since: API Level 1 Play a sound from a sound ID. 
	 * Play the sound specified by the soundID. 
	 * This is the value returned by the load() function. 
	 * Returns a non-zero streamID if successful, zero if it fails. 
	 * The streamID can be used to further control playback. 
	 * Note that calling play() may cause another sound to stop playing if 
	 * the maximum number of active streams is exceeded. 
	 * 
	 * A loop value of -1 means loop forever, 
	 * a value of 0 means don't loop, other values indicate the number of repeats, 
	 * e.g. a value of 1 plays the audio twice. 
	 * The playback rate allows the application 
	 * to vary the playback rate (pitch) of the sound. 
	 * A value of 1.0 means play back at the original frequency. 
	 * A value of 2.0 means play back twice as fast, 
	 * and a value of 0.5 means playback at half speed.
	 * 
	 * Parameters
	 * soundID  a soundID returned by the load() function 
	 * leftVolume  left volume value (range = 0.0 to 1.0) 
	 * rightVolume  right volume value (range = 0.0 to 1.0) 
	 * priority  stream priority (0 = lowest priority) 
	 * loop  loop mode (0 = no loop, -1 = loop forever) 
	 * rate  playback rate (1.0 = normal playback, range 0.5 to 2.0) 
	 * Returns
	 * non-zero streamID if successful, zero if failed 
	 * 
	 * public final int play (int soundID, float leftVolume, float rightVolume, 
	 * 								int priority, int loop, float rate)
	 * 播放指定音频的音效,并返回一个streamID 。
	 * 
	 * play()函数传递的是一个load()返回的soundID——指向一个被记载的音频资源 ,
	 * 如果播放成功则返回一个非0的streamID——指向一个成功播放的流 ;
	 * 同一个soundID 可以通过多次调用play()而获得多个不同的streamID (只要不超出同时播放的最大数量);
	 * 
	 * play()中的priority参数,
	 * 只在同时播放的流的数量超过了预先设定的最大数量是起作用,
	 * 管理器将自动终止优先级低的播放流。
	 * 
	 * 如果存在多个同样优先级的流,再进一步根据其创建事件来处理,
	 * 新创建的流的年龄是最小的,将被终止;
	 * 无论如何,程序退出时,手动终止播放并释放资源是必要的。
	 * eg.
	 * 
	 * //这里对soundID1的音效进行播放——优先级为0(最低),无限循环,正常速率。
	 * int streamID = soundPool.play(soundID1 , 1.0, 1.0, 0, -1, 1.0);
	 * if(streamID ==0){
	 * 		// 播放失败
	 * }else{
	 * 		// 播放成功
	 * }
	 * // 暂停soundID1的播放
	 * soundPool.pause(streamID );
	 * // 恢复soundID1的播放
	 * soundPool.resume(streamID );
	 * // 终止播放,记住循环为-1时必须手动停止
	 * soundPool.stop(streamID );
	 * 
	 * API中指出,即使使用无效的soundID /streamID 
	 * (操作失败或指向无效的资源)来调用相关函数也不会导致错误,这样能减轻逻辑的处理。
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
			ON = !ON;
			if (ON)
				player.start();
			else
				player.pause();
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			currentVol += 1;
			if (currentVol > maxVol)
				currentVol = 100;
			am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol, AudioManager.FLAG_PLAY_SOUND);
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			currentVol -= 1;
			if (currentVol < 0)
				currentVol = 0;
			am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol, AudioManager.FLAG_PLAY_SOUND);
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			if (player.getCurrentPosition() < 5000)
				player.seekTo(0);
			else
				player.seekTo(player.getCurrentPosition() - 5000);
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			player.seekTo(player.getCurrentPosition() + 5000);
		}
		/*
		 * 第一个参数是通过SoundPool.load()方法返回的音频对应id,
		 * 第二个第三个参数表示左右声道大小,
		 * 第四个参数是优先级,第五个参数是循环次数,
		 * 最后一个是播放速率(1.0 =正常播放,范围是0.5至2.0)
		 */
		soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f);
		return super.onKeyDown(keyCode, event);
	}
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值