Android Audio简述
         简单点MediaPlayer,复杂点……不会啊T^T,怎么办!
 
一、MediaPlayer

         在package android.media包内,MediaPlayer的API说明得很详细了^^。摘张图过来:

         另外,在其Valid and invalid states一节中列了张表格,详细的描述了MediaPlayer各方法在各状态下是否有效。(就不摘过来了^^)
 
二、音乐播放器
         MediaPlayer控制音乐文件播放的简易实现,就是控制上图的流程^^。
 
1)播放控制接口
         定义了播放器基本模式、状态及控制操作方法。
 
  1. public interface IPlayer { 
  2.  
  3.     // 播放模式 
  4.     enum PlayMode { 
  5.         ONCE, CYCLE 
  6.     } 
  7.  
  8.     // 播放状态 
  9.     enum Status { 
  10.         PLAYING, STOPPED, PAUSING 
  11.     }; 
  12.  
  13.     // 获得当前播放模式 
  14.     PlayMode getMode(); 
  15.  
  16.     // 设置当前播放模式 
  17.     void setMode(PlayMode mode); 
  18.  
  19.     // 获得当前播放状态 
  20.     Status getStatus(); 
  21.  
  22.     // 指向上一首音乐 
  23.     File prev(); 
  24.  
  25.     // 指向下一首音乐 
  26.     File next(); 
  27.  
  28.     // 播放当前音乐 
  29.     boolean play(); 
  30.  
  31.     // 暂停当前音乐 
  32.     boolean pause(); 
  33.  
  34.     // 恢复播放音乐 
  35.     boolean resume(); 
  36.  
  37.     // 停止当前音乐 
  38.     boolean stop(); 
  39.  
  40.     // 释放资源 
  41.     void release(); 
  42.  
 
2)文件列表控件
         自定义的ListView控件,主要实现功能如下:
         1. 异步搜索/mnt/sdcard/music/目录下所有mp3文件
         2. 以自定义适配器方式,使得选中的Item保持高亮背景色
 
3)文件列表播放器
         用MediaPlayer实现了IPlayer播放控制接口。
 
  1. public class FileListViewPlayer implements IPlayer, 
  2.         AdapterView.OnItemClickListener { 
  3.  
  4.     private MediaPlayer mMediaPlayer; // MediaPlayer对象 
  5.     private FileListView mFileListView; // FileListView组件 
  6.     private ArrayList<File> musicFileList; // 音乐文件列表 
  7.     private int index = 0// 当前索引 
  8.     private PlayMode mPlayMode = PlayMode.CYCLE; // 播放模式 
  9.     private Status mStatus = Status.STOPPED; // 播放状态 
  10.  
  11.     private OnMusicClickListener listener; // 音乐文件点击监听 
  12.  
  13.     // 音乐点击事件监听接口 
  14.     public interface OnMusicClickListener { 
  15.         // 返回true及时播放,false则不及时播放 
  16.         boolean onMusicClick(File musicFile); 
  17.     } 
  18.  
  19.     public FileListViewPlayer(FileListView fileListView) { 
  20.         this.mFileListView = fileListView; 
  21.         fileListView.setOnItemClickListener(this); // 设置Item点击时间监听 
  22.         mMediaPlayer = new MediaPlayer(); // 创建MediaPlayer对象 
  23.     } 
  24.  
  25.     // 是否有音乐 
  26.     private boolean hasMusic() { 
  27.         musicFileList = mFileListView.getMusicFileList(); 
  28.         return (null != musicFileList && musicFileList.size() >= 1); 
  29.     } 
  30.  
  31.     // 选中某项&设置索引 
  32.     private void setSelection(int position) { 
  33.         mFileListView.setSelectItem(position); // 选中position 
  34.         index = position; // 指向position 
  35.     } 
  36.  
  37.     @Override 
  38.     public PlayMode getMode() { 
  39.         return mPlayMode; 
  40.     } 
  41.  
  42.     @Override 
  43.     public void setMode(PlayMode mode) { 
  44.         this.mPlayMode = mode; 
  45.     } 
  46.  
  47.     @Override 
  48.     public Status getStatus() { 
  49.         return mStatus; 
  50.     } 
  51.  
  52.     @Override 
  53.     public File prev() { 
  54.         if (hasMusic()) { 
  55.             int location = index - 1 >= 0 ? index - 1 
  56.                     : musicFileList.size() - 1
  57.             setSelection(location); 
  58.             return musicFileList.get(location); 
  59.         } 
  60.         return null
  61.     } 
  62.  
  63.     @Override 
  64.     public File next() { 
  65.         if (hasMusic()) { 
  66.             int location = index + 1 < musicFileList.size() ? index + 1 : 0
  67.             setSelection(location); 
  68.             return musicFileList.get(location); 
  69.         } 
  70.         return null
  71.     } 
  72.  
  73.     @Override 
  74.     public boolean play() { 
  75.         if (mStatus != Status.STOPPED || !hasMusic()) { 
  76.             return false
  77.         } 
  78.         try { 
  79.             mMediaPlayer.reset(); 
  80.             mMediaPlayer.setDataSource(musicFileList.get(index).toString()); 
  81.             mMediaPlayer.prepare(); 
  82.             mMediaPlayer.start(); 
  83.             // 如果是顺序循环播放 
  84.             if (mPlayMode == PlayMode.CYCLE) { 
  85.                 mMediaPlayer 
  86.                         .setOnCompletionListener(new OnCompletionListener() { 
  87.                             @Override 
  88.                             public void onCompletion(MediaPlayer mp) { 
  89.                                 stop(); // 停止 
  90.                                 next(); // 下首 
  91.                                 play(); // 播放 
  92.                             } 
  93.                         }); 
  94.             } 
  95.             mStatus = Status.PLAYING; 
  96.             return true
  97.         } catch (Exception e) { 
  98.             e.printStackTrace(); 
  99.         } 
  100.         return false
  101.     } 
  102.  
  103.     @Override 
  104.     public boolean pause() { 
  105.         if (mStatus != Status.PLAYING) { 
  106.             return false
  107.         } 
  108.         mMediaPlayer.pause(); 
  109.         mStatus = Status.PAUSING; 
  110.         return true
  111.     } 
  112.  
  113.     @Override 
  114.     public boolean resume() { 
  115.         if (mStatus != Status.PAUSING) { 
  116.             return false
  117.         } 
  118.         mMediaPlayer.start(); 
  119.         mStatus = Status.PLAYING; 
  120.         return true
  121.     } 
  122.  
  123.     @Override 
  124.     public boolean stop() { 
  125.         if (mStatus != Status.STOPPED) { 
  126.             mMediaPlayer.stop(); 
  127.             mStatus = Status.STOPPED; 
  128.             return true
  129.         } 
  130.         return false
  131.     } 
  132.  
  133.     @Override 
  134.     public void release() { 
  135.         stop(); 
  136.         mMediaPlayer.release(); 
  137.         mMediaPlayer = null
  138.     } 
  139.  
  140.     @Override 
  141.     public void onItemClick(AdapterView<?> parent, View view, int position, 
  142.             long id) { 
  143.         setSelection(position); 
  144.         if (null != listener && hasMusic()) { 
  145.             if (listener.onMusicClick(musicFileList.get(position))) { 
  146.                 stop(); // 停止 
  147.                 play(); // 播放 
  148.             } 
  149.         } 
  150.     } 
  151.  
  152.     public void setOnMusicClickListener(OnMusicClickListener listener) { 
  153.         this.listener = listener; 
  154.     } 
  155.  
 
4)播放器活动
         界面上各点击事件与播放控制接口结合。
 
5)其他设置(样例中未写==
5.1)播放进度
         使用MediaPlayer提供了如下3个方法即可:

         1. getDuration():获得总持续时间(毫秒)

                   {Idle, Initialized, Error}状态时,该方法无效。

         2. getCurrentPosition():获得当前播放位置(毫秒)

                   {Error}状态时,该方法无效。

         3. seekTo(int msec):跳至指定的时间位置(毫秒)

                   {Idle, Initialized, Stopped, Error}状态时,该方法无效。

 
5.2)音量控制

         使用AudioManager的setStreamVolume(int streamType, int index, int flags)方法。第一个参数设置为AudioManager.STREAM_MUSIC,即为音乐音量。

 
三、其他音频类

         一样是在package android.media包内,还有好几个以Player后缀结束的类呢T^T。没用过,也不多作介绍了。

         这节呢,主要是想介绍下SoundPool,以及它与MediaPlayer的利弊及使用场合。
 
1)利弊及场合
         MediaPlayer资源占用多、延迟长、不支持多个音频同时播放。在快速连续播放音效时,尤其会感受到。而SoundPool则不同,占用少、延迟短、支持多音频播放。因为其限制最大只能申请1M的内存,也就意味着是用于播放音频片断的。
         总结就是,MediaPlayer播放长音乐,SoundPool播放短音效==。
 
2)SoundPool注意点
1. 音效文件不易过大(限制1M内存)
         如果音效文件过大而没有载入完成,调用play()可能会产生严重的后果。当然可以用SoundPool.OnLoadCompleteListener来判断是否载入完成。
2. pause&stop方法建议不轻易使用
         有时会使你程序莫名终止。也有反映不会立即终止,而是等缓冲区播放完,会多一秒。
3. 音频格式建议使用OGG格式。
         说是WAV在音效播放间隔较短的情况下会出现异常关闭的情况。另外说是,目前只对16位的WAV支持较好。(目前不知道指什么时候==)
 
         参考自网络,未亲自证实,尽量避免就行了^^。
 
3)SoundPool的使用
         SoundPool基本的播放控制方法。方法的详细说明都在注释了^^。
 
  1. public class SoundPoolActivity extends Activity implements 
  2.         SoundPool.OnLoadCompleteListener { 
  3.  
  4.     private static final int SOUND_BASE = 0
  5.     private static final int SOUND_THUNDER = SOUND_BASE + 1
  6.     private static final int SOUND_NIGHTINGALE = SOUND_BASE + 2
  7.  
  8.     private SoundPool mSoundPool; // SoundPool对象 
  9.     private HashMap<Integer, Integer> soundPoolMap; 
  10.  
  11.     @Override 
  12.     protected void onCreate(Bundle savedInstanceState) { 
  13.         super.onCreate(savedInstanceState); 
  14.         setContentView(R.layout.audio_pool); 
  15.         initSounds(); // 初始化SoundPool 
  16.     } 
  17.  
  18.     // 初始化SoundPool 
  19.     private void initSounds() { 
  20.         /* 
  21.          * SoundPool(int maxStreams, int streamType, int srcQuality) 
  22.          *  
  23.          * maxStreams:同时播放的流的最大数量 
  24.          * streamType:流的类型(AudioManager类中描述的)。例如:游戏应用一般使用STREAM_MUSIC 
  25.          * srcQuality:采样率转化质量。当前无效果,使用0作为默认值 
  26.          */ 
  27.         mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100); 
  28.         /* 
  29.          * Android 2.2(API 8及以上)才有这接口== 
  30.          */ 
  31.         mSoundPool.setOnLoadCompleteListener(this); 
  32.         soundPoolMap = new HashMap<Integer, Integer>(); 
  33.         /* 
  34.          * load()有四种方法,如下: 
  35.          *  
  36.          * 1)int load(Context context, int resId, int priority) 
  37.          *     从APK资源载入(一般在res/raw目录下) 
  38.          * 2)int load(FileDescriptor fd, long offset, long length, int priority) 
  39.          *     从FileDescriptor对象载入 
  40.          * 3)int load(AssetFileDescriptor afd, int priority) 
  41.          *     从Asset对象载入 
  42.          * 4)int load(String path, int priority) 
  43.          *     从完整文件路径名载入 
  44.          *  
  45.          * 最后priority参数为优先级,播放多文件时处理用。 
  46.          */ 
  47.         soundPoolMap 
  48.                 .put(SOUND_THUNDER, mSoundPool.load(this, R.raw.thunder, 1)); 
  49.         soundPoolMap.put(SOUND_NIGHTINGALE, 
  50.                 mSoundPool.load(this, R.raw.nightingale, 2)); 
  51.     } 
  52.  
  53.     // 雷声 
  54.     public void bird(View v) { 
  55.         /* 
  56.          * play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) 
  57.          *  
  58.          * soundID:load()方法返回的int值 
  59.          * leftVolume:左音量。范围=[0.0,1.0] 
  60.          * rightVolume:右音量。范围=[0.0,1.0] 
  61.          * priority:优先级。最低=0 
  62.          * loop:循环次数。不循环=0;永远循环=-1 
  63.          * rate:速率。正常=1;范围=[0.5,2.0] 
  64.          */ 
  65.         mSoundPool.play(soundPoolMap.get(SOUND_THUNDER), 11001); 
  66.     } 
  67.  
  68.     // 雷声+夜莺 
  69.     public void mix(View v) { 
  70.         mSoundPool.play(soundPoolMap.get(SOUND_THUNDER), 11001); 
  71.         mSoundPool.play(soundPoolMap.get(SOUND_NIGHTINGALE), 11001); 
  72.     } 
  73.  
  74.     @Override 
  75.     public void onBackPressed() { 
  76.         super.onBackPressed(); 
  77.         mSoundPool.release(); 
  78.     } 
  79.  
  80.     @Override 
  81.     public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { 
  82.         // Log.d("TAG", "==" + sampleId + "=="); 
  83.     } 
  84.  
 
四、MediaRecorder
         在Camera摄像里,MediaRecorder用来录制视频了。这里是介绍录制音频了^^。好吧,再摘张图过来(真心觉着看看API就好了,可以把我忽视==):

 
五、简易录音机
         简易的录音机,就是能录音和回放一下的那种==。
 
1)录音机对象
         录音机的简单实现。MediaRecorder控制录音、MediaPlayer用于播放,设置了其各个阶段状态。
 
  1. public class Recorder implements OnCompletionListener, OnErrorListener { 
  2.  
  3.     // 录音机状态 
  4.     public enum Status { 
  5.         IDLE, RECORDING, PLAYING 
  6.     } 
  7.  
  8.     // 当前状态 
  9.     private Status mStatus = Status.IDLE; 
  10.  
  11.     // 媒体录制对象 
  12.     private MediaRecorder mRecorder = null
  13.     // 媒体播放对象 
  14.     private MediaPlayer mPlayer = null
  15.     // 录制的文件 
  16.     File mSampleFile = null
  17.     // 录音或播放开始时间 
  18.     long mSampleStart = 0
  19.  
  20.     private OnRecorderListener listener; // 播放器接口 
  21.  
  22.     public interface OnRecorderListener { 
  23.         void error(); 
  24.  
  25.         void playOver(); 
  26.     } 
  27.  
  28.     // 返回录音机状态 
  29.     public Status getStatus() { 
  30.         return mStatus; 
  31.     } 
  32.  
  33.     // 是否录过音 
  34.     public boolean isRecorded() { 
  35.         return mSampleFile != null
  36.     } 
  37.  
  38.     // 返回时间进度(秒) 
  39.     public int progress() { 
  40.         if (mStatus == Status.RECORDING || mStatus == Status.PLAYING) 
  41.             return (int) ((System.currentTimeMillis() - mSampleStart) / 1000); 
  42.         return 0
  43.     } 
  44.  
  45.     // 开始录音 
  46.     public void startRecording() { 
  47.         stop(); // 停止 
  48.         if (mSampleFile == null) { 
  49.             // 创建文件,以createTempFile方式避免覆盖 
  50.             try { 
  51.                 File dir = new File(Environment.getExternalStorageDirectory() 
  52.                         + "/AndroidMedia/"); 
  53.                 if (!dir.exists()) { 
  54.                     dir.mkdirs(); 
  55.                 } 
  56.                 mSampleFile = File.createTempFile("join_"".3gpp", dir); 
  57.             } catch (IOException e) { 
  58.                 e.printStackTrace(); 
  59.                 return
  60.             } 
  61.         } 
  62.         mRecorder = new MediaRecorder(); // 创建MediaRecorder对象 
  63.         mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频信号源:麦克风 
  64.         mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); // 设置输出格式 
  65.         mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 设置音频编码器 
  66.         mRecorder.setOutputFile(mSampleFile.getAbsolutePath()); // 设置输出文件 
  67.         try { 
  68.             mRecorder.prepare(); // 准备录音 
  69.         } catch (Exception e) { 
  70.             mRecorder.reset(); // 重置空闲 
  71.             mRecorder.release(); // 释放资源 
  72.             mRecorder = null// 重置为null 
  73.             return// 返回 
  74.         } 
  75.         try { 
  76.             mRecorder.start(); // 开启录音 
  77.         } catch (RuntimeException e) { 
  78.             // 可能开启不了,如果是来电的话,可以如下判断: 
  79.             // AudioManager.getMode() == MODE_IN_CALL || MODE_IN_COMMUNICATION 
  80.             mRecorder.reset(); // 重置空闲 
  81.             mRecorder.release(); // 释放资源 
  82.             mRecorder = null// 重置为null 
  83.             return// 返回 
  84.         } 
  85.         mSampleStart = System.currentTimeMillis(); // 初始化开始时间 
  86.         mStatus = Status.RECORDING; // 设置录音状态 
  87.     } 
  88.  
  89.     // 停止录音 
  90.     public void stopRecording() { 
  91.         if (mRecorder == null
  92.             return
  93.         mRecorder.stop(); // 停止录音 
  94.         mRecorder.release(); // 释放资源 
  95.         mRecorder = null// 重置为null 
  96.         mStatus = Status.IDLE; // 设为空闲状态 
  97.     } 
  98.  
  99.     // 开始录音 
  100.     public void startPlayback() { 
  101.         stop(); // 停止 
  102.         mPlayer = new MediaPlayer(); // 创建MediaPlayer对象 
  103.         try { 
  104.             mPlayer.setDataSource(mSampleFile.getAbsolutePath()); // 设置数据资源路径 
  105.             mPlayer.setOnCompletionListener(this); // 设置完成监听接口 
  106.             mPlayer.setOnErrorListener(this); // 设置错误监听接口 
  107.             mPlayer.prepare(); // 准备 
  108.             mPlayer.start(); // 播放 
  109.         } catch (Exception e) { 
  110.             mPlayer = null// 重置为null 
  111.             return
  112.         } 
  113.         mSampleStart = System.currentTimeMillis(); // 初始化开始时间 
  114.         mStatus = Status.PLAYING; // 设置录音状态 
  115.     } 
  116.  
  117.     // 停止录音 
  118.     public void stopPlayback() { 
  119.         if (mPlayer == null
  120.             return
  121.         mPlayer.stop(); // 停止播放 
  122.         mPlayer.release(); // 释放资源 
  123.         mPlayer = null// 重置为null 
  124.         mStatus = Status.IDLE; // 设为空闲状态 
  125.     } 
  126.  
  127.     // 停止操作  
  128.     public void stop() { 
  129.         stopRecording(); 
  130.         stopPlayback(); 
  131.     } 
  132.  
  133.     @Override 
  134.     public boolean onError(MediaPlayer mp, int what, int extra) { 
  135.         stop(); // 停止 
  136.         if (null != listener) { 
  137.             listener.error(); 
  138.         } 
  139.         return true
  140.     } 
  141.  
  142.     @Override 
  143.     public void onCompletion(MediaPlayer mp) { 
  144.         stop(); // 停止 
  145.         if (null != listener) { 
  146.             listener.playOver(); 
  147.         } 
  148.     } 
  149.  
  150.     // 设置播放器接口 
  151.     public void setOnRecorderListener(OnRecorderListener listener) { 
  152.         this.listener = listener; 
  153.     } 
  154.  
 
2)录音机活动
         录音机控制界面。进行录音&播放控制,并显示有时间。
 
3)其他设置(样例中未写)
         MediaRecorder的getMaxAmplitude()可以获得音频资源的最大振幅,可以用于展示下录音时的波幅线条?
         增加设置文件大小限制或物体空间限制,提示剩余时间。物理空间可以用package android.os包内的StatFs类。StatFs是Unix statfs()的一个包装,用于检索文件系统空间的整体信息。在之前Camera摄像的CameraVideoActivity内稍带用了下的^^。
         当然,这些功能在系统自带的录音机里都有实现了的,直接可以用==。
 
六、后记
1)扩展内容
1.1)android获取多媒体信息之音频文件
 
1.2)RandomMusicPlayer:Service方式如何进行音乐播放控制(官方例子)
 
1.3)Jamendo:一款开源在线音乐播放器

         Google Code:http://code.google.com/p/jamendo/

         源码分析系列:http://blog.csdn.net/gaomatrix/article/category/900092
 
2)模块概览
2.1)Audio MediaPlayer

 
2.2)Audio SoundPool

 
2.3)Audio MeidaRecorder

Audio MeidaRecorder

 
3)运行效果
3.1)音乐播放器

Audio MeidaRecorder

 
3.2)SoundPool

Audio MeidaRecorder

 
3.3)简单录音机

Audio MeidaRecorder