`

Android开发:使用AudioTrack播放PCM音频数据【附源码】

 
阅读更多

http://www.linuxidc.com/Linux/2012-06/63186.htm

 

众所周知,Android 的 MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用 MediaPlayer实现的。MediaPlayer在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个 MediaPlayer程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。但是该类只能对完整的音 频文件进行操作,而不能直接对纯PCM音频数据操作。假如我们通过解码得到PCM数据源,又当如何将它们播放?没错,就是用AudioTrack这个类 (MediaPlayer内部也是调用该类进行真正的播放音频流操作)下面这个DEMO演示了如何使用AudioTrack来播放PCM音频数据。

废话不多说,先上效果图:


 

工程代码结构也较为简单:


简单说下思路,先把PCM音频数据从指定的路径文件读到内存,然后给AudioPlayer设置数据源,音频参数等,最后执行播放,暂停,停止等操作

贴上部分类代码片段:

  1. public   class  AudioParam {  
  2.   
  3.      int  mFrequency;                  // 采样率   
  4.       
  5.      int  mChannel;                    // 声道   
  6.       
  7.      int  mSampBit;                    // 采样精度   
  8.       
  9. }  

 

  1. public   interface  PlayState {  
  2.       
  3.      public   static   final   int  MPS_UNINIT =  0 ;              // 未就绪   
  4.       
  5.      public   static   final   int  MPS_PREPARE =  1 ;             // 准备就绪(停止)   
  6.       
  7.      public   static   final   int  MPS_PLAYING =  2 ;             // 播放中   
  8.       
  9.      public   static   final   int  MPS_PAUSE =  3 ;               // 暂停   

 

AudioPlayer代码片段如下:

  1. public   class  AudioPlayer  implements  IPlayComplete{  
  2.   
  3.      private   final   static  String TAG =  "AudioPlayer" ;  
  4.       
  5.      public   final   static   int     STATE_MSG_ID =  0x0010 ;  
  6.       
  7.      private  Handler    mHandler;  
  8.       
  9.      private  AudioParam mAudioParam;                          // 音频参数   
  10.       
  11.      private   byte []     mData;                                // 音频数据   
  12.       
  13.      private  AudioTrack mAudioTrack;                          // AudioTrack对象   
  14.       
  15.      private   boolean     mBReady =  false ;                      // 播放源是否就绪   
  16.       
  17.      private  PlayAudioThread mPlayAudioThread;                // 播放线程   
  18.       
  19.      public  AudioPlayer(Handler handler)  
  20.     {  
  21.         mHandler = handler;  
  22.     }  
  23.       
  24.      public  AudioPlayer(Handler handler,AudioParam audioParam)  
  25.     {  
  26.         mHandler = handler;  
  27.         setAudioParam(audioParam);    
  28.     }  
  29.       
  30.      /*  
  31.      * 设置音频参数  
  32.      */   
  33.      public   void  setAudioParam(AudioParam audioParam)  
  34.     {  
  35.         mAudioParam = audioParam;  
  36.     }  
  37.       
  38.      /*  
  39.      * 设置音频源  
  40.      */   
  41.      public   void  setDataSource( byte [] data)  
  42.     {  
  43.         mData = data;         
  44.     }  
  45.       
  46.       
  47.      /*  
  48.      *  就绪播放源  
  49.      */   
  50.      public   boolean  prepare()  
  51.     {  
  52.          if  (mData ==  null  || mAudioParam ==  null )  
  53.         {  
  54.              return   false ;  
  55.         }  
  56.           
  57.          if  (mBReady ==  true )  
  58.         {  
  59.              return   true ;  
  60.         }  
  61.           
  62.          try  {  
  63.             createAudioTrack();  
  64.         }  catch  (Exception e) {  
  65.              // TODO Auto-generated catch block   
  66.             e.printStackTrace();  
  67.              return   false ;  
  68.         }  
  69.                   
  70.           
  71.         mBReady =  true ;  
  72.           
  73.         setPlayState(PlayState.MPS_PREPARE);  
  74.           
  75.          return   true ;  
  76.     } 

 

  1. private   boolean  mThreadExitFlag =  false ;                         // 线程退出标志   
  2.       
  3.      private   int      mPrimePlaySize =  0 ;                              // 较优播放块大小   
  4.       
  5.      private   int      mPlayOffset =  0 ;                                 // 当前播放位置   
  6.       
  7.      private   int      mPlayState =  0 ;                                  // 当前播放状态   
  8.       
  9.       
  10.      /*  
  11.      *  播放音频的线程  
  12.      */   
  13.      class  PlayAudioThread  extends  Thread  
  14.     {  
  15.   
  16.           
  17.          @Override   
  18.          public   void  run() {  
  19.              // TODO Auto-generated method stub   
  20.   
  21.               
  22.             Log.d(TAG,  "PlayAudioThread run mPlayOffset = "  + mPlayOffset);  
  23.               
  24.             mAudioTrack.play();   
  25.               
  26.                  while ( true )  
  27.                 {                                             
  28.                       
  29.                      if  (mThreadExitFlag ==  true )  
  30.                     {  
  31.                          break ;  
  32.                     }                     
  33.                       
  34.                      try  {  
  35.                           
  36.                          int  size = mAudioTrack.write(mData, mPlayOffset, mPrimePlaySize);                 
  37.                         mPlayOffset += mPrimePlaySize;        
  38.                           
  39.                     }  catch  (Exception e) {  
  40.                          // TODO: handle exception   
  41.                         e.printStackTrace();  
  42.                         AudioPlayer. this .onPlayComplete();  
  43.                          break ;  
  44.                     }  
  45.               
  46.                      if  (mPlayOffset >= mData.length)  
  47.                     {             
  48.                         AudioPlayer. this .onPlayComplete();  
  49.                          break ;  
  50.                     }  
  51.                       
  52.                       
  53.                 }  
  54.                   
  55.             mAudioTrack.stop();   
  56.                   
  57.             Log.d(TAG,  "PlayAudioThread complete..." );                        
  58.         }  
  59.       
  60.           
  61.           
  62.     }

下面来剖析以下如何使用AudioTrack来播放PCM音频数据

首先要构建一个AudioTrack对象:(需要采样率,声道,采样精度参数)

  1. private   void  createAudioTrack()  throws  Exception  
  2.     {  
  3.           
  4.          // 获得构建对象的最小缓冲区大小   
  5.          int  minBufSize = AudioTrack.getMinBufferSize(mAudioParam.mFrequency,   
  6.                                                     mAudioParam.mChannel,  
  7.                                                     mAudioParam.mSampBit);  
  8.           
  9.           
  10.         mPrimePlaySize = minBufSize *  2 ;  
  11.         Log.d(TAG,  "mPrimePlaySize = "  + mPrimePlaySize);  
  12.           
  13. //               STREAM_ALARM:警告声   
  14. //               STREAM_MUSCI:音乐声,例如music等   
  15. //               STREAM_RING:铃声   
  16. //               STREAM_SYSTEM:系统声音   
  17. //               STREAM_VOCIE_CALL:电话声音   
  18.         mAudioTrack =  new  AudioTrack(AudioManager.STREAM_MUSIC,  
  19.                                     mAudioParam.mFrequency,   
  20.                                     mAudioParam.mChannel,   
  21.                                     mAudioParam.mSampBit,   
  22.                                     minBufSize,  
  23.                                     AudioTrack.MODE_STREAM);  
  24. //              AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。   
  25. //              STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。   
  26. //              这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。   
  27. //              这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。   
  28. //              而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,   
  29. //              后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。   
  30. //              这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。   
  31.           
  32.           
  33.   
  34.     }  

然后开一个子线程从缓存区里分块取数据然后写入硬件设备进行播放

  1.   
  2. private   void  startThread()  
  3. {  
  4.      if  (mPlayAudioThread ==  null )  
  5.     {  
  6.         mThreadExitFlag =  false ;  
  7.         mPlayAudioThread =  new  PlayAudioThread();  
  8.         mPlayAudioThread.start();  
  9.     }  

AudioTrack里有三个重要方法:

void play()

int write(byte[] audioData, int offsetInBytes, int sizeInBytes) (该方法是阻塞的)

void stop()

从前面那个线程代码可以看出,我们在写数据之前需要先执行 play(),然后才能进行write操作,当数据播放完毕或是线程被外部终止的时候最后调用stop()停止写数据;若执行了play操作但后面却没有 执行write操作的话,或是write操作结束后没有调用stop,观察logcat会不断打印提示信息,这是提示我们对以上三个方法的调用要规范

只要大家设置的音频参数和音频数据都是正确的,就能顺畅的播放出声音,本例已经附带了用于测试的音频文件以及参数说明(已测试通过),具体看工程里 音频数据这个文件夹下的readme.txt即可.网上有些童鞋反应说audiotrack播放音频不顺畅,如果数据源没问题的话估计是他们的demo里 没有连续地执行write操作而导致的,其它的不多说了,觉得有用的童鞋自己写代码看吧。。。喜欢就顶一下吧!

  • 大小: 38.8 KB
  • 大小: 30.6 KB
分享到:
评论
2 楼 u011250186 2015-01-23  
多谢楼主分享,感激不尽
1 楼 nicky12 2013-04-01  
这个真的不错   谢了   linux公社那个 ftp上面没有了

相关推荐

Global site tag (gtag.js) - Google Analytics