android學習筆記17————–android 通過數組,流播放聲音的方法

AudioRecord和AudioTrack類是Android獲取和播放音頻流的重要類,放置在android.media包中。與該包中的MediaRecorder和MediaPlayer類不同,AudioRecord和AudioTrack類在獲取和播放音頻數據流時無需通過文件保存和文件讀取,可以動態地直接獲取和播放音頻流,在實時處理音頻數據流時非常有用。

    當然,如果用戶隻想錄音後寫入文件或從文件中取得音頻流進行播放,那麼直接使用MediaRecorder和MediaPlayer類是首選方案,因為這兩個類使用非常方便,而且成功率很高。而AudioRecord和AudioTrack類的使用卻比較復雜,我們發現很多人都不能成功地使用這兩個類,甚至認為Android的這兩個類是不能工作的。

    其實,AudioRecord和AudioTrack類的使用雖然比較復雜,但是可以工作,我們不僅可以很好地使用瞭這兩個類,而且還通過套接字(Socket)實現瞭音頻數據的網絡傳輸,做到瞭一端使用AudioRecord獲取音頻流然後通過套接字傳輸出去,而另一端通過套接字接收後使用AudioTrack類播放。

    下面是我們對AudioRecord和AudioTrack類在使用方面的經驗總結:

    (1)創建AudioRecord和AudioTrack類對象:創建這兩個類的對象比較復雜,通過對文檔的反復和仔細理解,並通過多次失敗的嘗試,並在北理工的某個Android大牛的網上的文章啟發下,我們也最終成功地創建瞭這兩個類的對象。創建AudioRecord和AudioTrack類對象的代碼如下:

AudioRecord類:

         m_in_buf_size =AudioRecord.getMinBufferSize(8000,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);
  
         m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
         8000,
         AudioFormat.CHANNEL_CONFIGURATION_MONO,
         AudioFormat.ENCODING_PCM_16BIT,
         m_in_buf_size) ;

AudioTrack類:

         m_out_buf_size = android.media.AudioTrack.getMinBufferSize(8000,
                          AudioFormat.CHANNEL_CONFIGURATION_MONO,
                          AudioFormat.ENCODING_PCM_16BIT);

         m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
                                       AudioFormat.CHANNEL_CONFIGURATION_MONO,
                                       AudioFormat.ENCODING_PCM_16BIT,
                                       m_out_buf_size,
                                       AudioTrack.MODE_STREAM);

    (2)關於AudioRecord和AudioTrack類的監聽函數,不用也行。

 

    (3)調試方面,包括初始化後看logcat信息,以確定類的工作狀態,初始化是否成功等。

     編寫好代碼,沒有語法錯誤,調用模擬器運行、調試代碼時,logcat發揮瞭很好的功用。剛調試時,經常會出現模擬器顯示出現異常,這時我們可以在代碼的一些關鍵語句後添加如Log.d("test1","OK");這樣的語句進行標識,出現異常時我們就可以在logcat窗口觀察代碼執行到哪裡出現異常,然後進行相應的修改、調試。模擬器不會出現異常時,又遇到瞭錄放音的問題。錄音方面,剛開始選擇將語音編碼數據存放在多個固定大小的文件中進行傳送,但是這種情況下會出現聲音斷續的現象,而且要反復的建立文件,比較麻煩,後來想到要進行網上傳輸,直接將語音編碼數據以數據流的形式傳送,經過驗證,這種方法可行並且使代碼更加簡潔。放音方面,將接收到的數據流存放在一個數組中,然後將數組中數據寫到AudioTrack中。剛開始隻是“嘟”幾聲,經過檢查發現隻是把數據寫一次,加入循環,讓數據反復寫到AudioTrack中,就可以聽到正常的語音瞭。接下來的工作主要是改善話音質量與話音延遲,在進行通話的過程中,觀察logcat窗口,發現向數組中寫數據時會出現Bufferflow的情況,於是把重心轉移到數組大小的影響上,經過試驗,發現 AudioRecord一次會讀640個數據,然後就對錄音和放音中有數組的地方進行實驗修改。AudioRecord和AudioTrack進行實例化時,參數中各有一個數組大小,經過試驗這個數組大小和AudioRecord和AudioTrack能正常實例化所需的最小Buffer大小(即上面實例化時的m_in_buf_size和m_out_buf_size參數)相等且服務器方進行緩存數據的數組尺寸是上述數值的2倍時,語音質量最好。由於錄音和放音的速度不一致,受到北理工大牛的啟發,在錄音方面,將存放錄音數據的數組放到LinkedList中,當LinkedList中數組個數達到2(這個也是經過試驗驗證話音質量最好時的數據)時,將先錄好的數組中數據傳送出去。經過上述反復試驗和修改,最終使雙方通話質量較好,且延時較短(大概有2秒鐘)。

    (4)通過套接字傳輸和接收數據

     數據傳送部分,使用的是套接字。通信雙方,通過不同的端口向服務器發送請求,與服務器連接上後,開始通話向服務器發送數據,服務器通過一個套接字接收到一方的數據後,先存在一個數組中,然後將該數組中數據以數據流的形式再通過另一個套接字傳送到另一方。這樣就實現瞭雙方數據的傳送。

     (5)代碼架構

      為避免反復錄入和讀取數據占用較多資源,使程序在進行錄放音時不能執行其他命令,故將錄音和放音各寫成一個線程類,然後在主程序中,通過MENU控制通話的開始、停止、結束。

      最後說明,AudioRecord和AudioTrack類可以用,隻是稍微復雜些。以下貼出雙方通信的源碼,希望對大傢有所幫助:

主程序Daudioclient:

package cn.Daudioclient;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class Daudioclient extends Activity {
 
    public static final int MENU_START_ID = Menu.FIRST ;
    public static final int MENU_STOP_ID = Menu.FIRST + 1 ;
    public static final int MENU_EXIT_ID = Menu.FIRST + 2 ;
 
    protected Saudioserver     m_player ;
    protected Saudioclient     m_recorder ;
 
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
  
    public boolean onCreateOptionsMenu(Menu aMenu)
    {
        boolean res = super.onCreateOptionsMenu(aMenu) ;

        aMenu.add(0, MENU_START_ID, 0, "START") ;
        aMenu.add(0, MENU_STOP_ID, 0, "STOP") ;
        aMenu.add(0, MENU_EXIT_ID, 0, "EXIT") ;

        return res ;
    }

  
    public boolean onOptionsItemSelected(MenuItem aMenuItem)
    {
        switch (aMenuItem.getItemId()) {
        case MENU_START_ID:
            {
             m_player = new Saudioserver() ;
                m_recorder = new Saudioclient() ;

                m_player.init() ;
                m_recorder.init() ;

                m_recorder.start() ;
                m_player.start() ;
              
            }
            break ;
        case MENU_STOP_ID:
            { 
             m_recorder.free() ;
                m_player.free() ;

                m_player = null ;
                m_recorder = null ;
            }
            break ;
        case MENU_EXIT_ID:
            {
                int pid = android.os.Process.myPid() ;
                android.os.Process.killProcess(pid) ;
            }
            break ;
        default:
            break ;
        }

        return super.onOptionsItemSelected(aMenuItem);
    }
}

錄音程序Saudioclient:

package cn.Daudioclient;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

public class Saudioclient extends Thread
{
 
    protected AudioRecord m_in_rec ;
    protected int         m_in_buf_size ;
    protected byte []     m_in_bytes ;
    protected boolean     m_keep_running ;
    protected Socket      s;
    protected DataOutputStream dout;
    protected LinkedList<byte[]>  m_in_q ;
 
    public void run()
 {
      try
      {
          byte [] bytes_pkg ;
             m_in_rec.startRecording() ;
             while(m_keep_running)
             {
                 m_in_rec.read(m_in_bytes, 0, m_in_buf_size) ;
                 bytes_pkg = m_in_bytes.clone() ;
                 if(m_in_q.size() >= 2)
                 {
                        dout.write(m_in_q.removeFirst() , 0, m_in_q.removeFirst() .length);
                    }
                    m_in_q.add(bytes_pkg) ;
             }
    
             m_in_rec.stop() ;
             m_in_rec = null ;
             m_in_bytes = null ;
       dout.close();
       
      }
      catch(Exception e)
      {
       e.printStackTrace();
      }
    }
  
    public void init()
    {
     m_in_buf_size =  AudioRecord.getMinBufferSize(8000,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);
  
  m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
  8000,
  AudioFormat.CHANNEL_CONFIGURATION_MONO,
  AudioFormat.ENCODING_PCM_16BIT,
  m_in_buf_size) ;
 
  m_in_bytes = new byte [m_in_buf_size] ;
 
  m_keep_running = true ;
  m_in_q=new LinkedList<byte[]>();
 
     try
     {
   s=new Socket("192.168.1.100",4332);
   dout=new DataOutputStream(s.getOutputStream());
   //new Thread(R1).start();
  }
     catch (UnknownHostException e)
     {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
     catch (IOException e)
     {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

    }
  
    public void free()
 {
  m_keep_running = false ;
        try {
            Thread.sleep(1000) ;
        } catch(Exception e) {
            Log.d("sleep exceptions…\n","") ;
        }
 }
}
放音程序Saudioserver:

package cn.Daudioclient;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;

public class Saudioserver extends Thread

    protected AudioTrack m_out_trk ;
    protected int        m_out_buf_size ;
    protected byte []    m_out_bytes ;
    protected boolean    m_keep_running ;
 private Socket s;
 private DataInputStream din;
 public void init()
 {
  try
     {
            s=new Socket("192.168.1.100",4331);
            din=new DataInputStream(s.getInputStream());
          
             m_keep_running = true ;
      
          
            m_out_buf_size = AudioTrack.getMinBufferSize(8000,
                             AudioFormat.CHANNEL_CONFIGURATION_MONO,
                             AudioFormat.ENCODING_PCM_16BIT);

            m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
                                       AudioFormat.CHANNEL_CONFIGURATION_MONO,
                                       AudioFormat.ENCODING_PCM_16BIT,
                                       m_out_buf_size,
                                       AudioTrack.MODE_STREAM);
        
            m_out_bytes=new byte[m_out_buf_size];
          
           // new Thread(R1).start();
          
     }
     catch(Exception e)
     {
      e.printStackTrace();
     }
 }
  
 public void free()
 {
  m_keep_running = false ;
        try {
            Thread.sleep(1000) ;
        } catch(Exception e) {
            Log.d("sleep exceptions…\n","") ;
        }
 }
 
  public void run()
  {
   byte [] bytes_pkg = null ;
         m_out_trk.play() ;
         while(m_keep_running) {
             try
             {
              din.read(m_out_bytes);
                 bytes_pkg = m_out_bytes.clone() ;
                 m_out_trk.write(bytes_pkg, 0, bytes_pkg.length) ;
             }
             catch(Exception e)
             {
              e.printStackTrace();
             }
           
         }
       
         m_out_trk.stop() ;
         m_out_trk = null ;
         try {
    din.close();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
}

 

AudioRecord
 
結構

繼承關系

public class AudioRecord extends Object

 

  java.lang.Object

android.media.AudioRecord

 

類概述

  AudioRecord類在Java應用程序中管理音頻資源,用來記錄從平臺音頻輸入設備產生的數據。 通過AudioRecord對象來完成"pulling"(讀取)數據。 應用通過以下幾個方法負責立即從AudioRecord對象讀取: read(byte[], int, int), read(short[], int, int)或read(ByteBuffer, int). 無論使用哪種音頻格式,使用AudioRecord是最方便的。

  在創建AudioRecord對象時,AudioRecord會初始化,並和音頻緩沖區連接,用來緩沖新的音頻數據。 根據構造時指定的緩沖區大小,來決定AudioRecord能夠記錄多長的數據。 從硬件設備讀取的數據,應小於整個記錄緩沖區。

 

內部類

interface          AudioRecord.OnRecordPositionUpdateListener

  接口定義為:當AudioRecord 收到一個由setNotificationMarkerPosition(int)設置的通知標志,或由 setPositionNotificationPeriod(int)設置的周期更新記錄的進度狀態時,回調此接口。

 

常量

    public static final int ERROR

  表示操作失敗。

                   常量值: -1 (0xffffffff)

 

  public static final int ERROR_BAD_VALUE

  表示使用瞭一個不合理的值導致的失敗。

        常量值: -2 (0xfffffffe)

 

  public static final int ERROR_INVALID_OPERATION

  表示不恰當的方法導致的失敗。

        常量值: -3 (0xfffffffd)

 

  public static final int RECORDSTATE_RECORDING

  指示AudioRecord錄制狀態為“正在錄制”。

        常量值: 3 (0x00000003)

 

  public static final int RECORDSTATE_STOPPED

  指示AudioRecord錄制狀態為“不在錄制”。

        常量值: 1 (0x00000001)

 

  public static final int STATE_INITIALIZED

  指示AudioRecord準備就緒。

        常量值: 1 (0x00000001)

 

  public static final int STATE_UNINITIALIZED

  指示AudioRecord狀態沒有初始化成功。

        常量值: 0 (0x00000000)

 

  public static final int SUCCESS

  表示操作成功。

        常量值: 0 (0x00000000)

 

構造函數

  public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

  類構造函數。

         參數

                   audioSource     錄制源。 請見MediaRecorder.AudioSource錄制源定義。

sampleRateInHz      默認采樣率,單位Hz。 44100Hz是當前唯一能保證在所有設備上工作的采樣率,在一些設備上還有22050, 16000或11025。

channelConfig          描述音頻通道設置。 請見CHANNEL_IN_MONO 和 CHANNEL_IN_STEREO。 CHANNEL_IN_MONO保證能在所有設備上工作。

audioFormat             音頻數據保證支持此格式。 請見ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。

bufferSizeInBytes    在錄制過程中,音頻數據寫入緩沖區的總數(字節)。 從緩沖區讀取的新音頻數據總會小於此值。 getMinBufferSize(int, int, int)返回AudioRecord 實例創建成功後的最小緩沖區。 設置的值比getMinBufferSize()還小則會導致初始化失敗。

                   異常

                            IllegalArgumentException

 

公共方法

    public int getAudioFormat ()

    返回設置的音頻數據格式。 請見ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。

 

    public int getAudioSource ()

    返回音頻錄制源。

      參見

                            MediaRecorder.AudioSource

 

    public int getChannelConfiguration ()

    返回設置的頻道設置。 請見CHANNEL_IN_MONO和CHANNEL_IN_STEREO。

 

    public int getChannelCount ()

    返回設置的頻道數目。

 

    public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)

    返回成功創建AudioRecord對象所需要的最小緩沖區大小。 註意:這個大小並不保證在負荷下的流暢錄制,應根據預期的頻率來選擇更高的值,AudioRecord實例在推送新數據時使用此值。

      參數

                            sampleRateInHz      默認采樣率,單位Hz。

                            channelConfig           描述音頻通道設置。

請見CHANNEL_IN_MONO和CHANNEL_IN_STEREO。

                            audioFormat             音頻數據保證支持此格式。參見ENCODING_PCM_16BIT。

      返回值

  如果硬件不支持錄制參數,或輸入瞭一個無效的參數,則返回ERROR_BAD_VALUE,如果硬件查詢到輸出屬性沒有實現,或最小緩沖區用byte表示,則返回ERROR。

      參見

                            更多信息請見有效的設置參數

 

    public int getNotificationMarkerPosition ()

    返回通知,標記框架中的位置。

 

    public int getPositionNotificationPeriod ()

    返回通知,更新框架中的時間位置。

 

    public int getRecordingState ()

    返回AudioRecord實例的錄制狀態。

      參見

                       RECORDSTATE_STOPPED

             RECORDSTATE_RECORDING

 

    public int getSampleRate ()

    返回設置的音頻數據樣本采樣率,單位Hz。

 

    public int getState ()

    返回AudioRecord實例的狀態。 這點非常有用,用在AudioRecord 實例創建成功後,檢查初始化屬性。 它能肯定請求到瞭合適的硬件資源。

      參見

                   STATE_INITIALIZED

             STATE_UNINITIALIZED

 

    public int read (short[] audioData, int offsetInShorts, int sizeInShorts)

    從音頻硬件錄制緩沖區讀取數據。

             參數

                            audioData        寫入的音頻錄制數據。

                            offsetInShorts           目標數組 audioData 的起始偏移量。

                            sizeInShorts              請求讀取的數據大小。

      返回值

  返回short型數據,表示讀取到的數據,如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數不能解析成有效的數據或索引,則返回ERROR_BAD_VALUE。 返回數值不會超過sizeInShorts。

 

    public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)

    從音頻硬件錄制緩沖區讀取數據。

                   參數

                            audioData        寫入的音頻錄制數據。

                            offsetInBytes            audioData的起始偏移值,單位byte。

                            sizeInBytes                讀取的最大字節數。

                   返回值

  讀入緩沖區的總byte數,如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數不能解析成有效的數據或索引,則返回ERROR_BAD_VALUE。 讀取的總byte數不會超過sizeInBytes。

 

    public int read (ByteBuffer audioBuffer, int sizeInBytes)

    從音頻硬件錄制緩沖區讀取數據,直接復制到指定緩沖區。 如果audioBuffer不是直接的緩沖區,此方法總是返回0。

                   參數

                            audioBuffer               存儲寫入音頻錄制數據的緩沖區。

                            sizeInBytes                請求的最大字節數。

                   返回值

  讀入緩沖區的總byte數,如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數不能解析成有效的數據或索引,則返回ERROR_BAD_VALUE。 讀取的總byte數不會超過sizeInBytes。

 

    public void release ()

    釋放本地AudioRecord資源。 對象不能經常使用此方法,而且在調用release()後,必須設置引用為null。

 

    public int setNotificationMarkerPosition (int markerInFrames)

    如果設置瞭setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),則通知監聽者設置位置標記。

                   參數

                            markerInFrames      在框架中快速標記位置。

                   返回值

                            返回錯誤或成功代碼,請見SUCCESS、ERROR_BAD_VALUE、ERROR_INVALID_OPERATION。

 

    public int setPositionNotificationPeriod (int periodInFrames)

    如果設置瞭setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),則通知監聽者設置時間標記。

                   參數

                            markerInFrames      在框架中快速更新時間標記。

                   返回值

                            返回錯誤或成功代碼,請見SUCCESS、ERROR_INVALID_OPERATION。

 

   public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener, Handler handler)

  當之前設置的標志已經成立,或者周期錄制位置更新時,設置處理監聽者。 使用此方法來將Handler 和別的線程聯系起來,來接收AudioRecord 事件,比創建AudioTrack 實例更好一些。

  參數

              handler    用來接收事件通知消息。

 

   public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener)

    當之前設置的標志已經成立,或者周期錄制位置更新時,設置處理監聽者。

 

    public void startRecording ()

    AudioRecord實例開始進行錄制。

                   異常

                            IllegalStateException

 

受保護方法

  protected void finalize ()

    通知VM回收此對象內存。 此方法隻能用在運行的應用程序沒有任何線程再使用此對象,來告訴垃圾回收器回收此對象。

  此方法用於釋放系統資源,由垃圾回收器清除此對象。 默認沒有實現,由VM來決定,但子類根據需要可重寫finalize()。 在執行期間,調用此方法可能會立即拋出未定義異常,但是可以忽略。

  註意:VM保證對象可以一次或多次調用finalize(),但並不保證finalize()會馬上執行。 例如,對象B的finalize()可能延遲執行,等待對象A的finalize()延遲回收A的內存。 為瞭安全起見,請看ReferenceQueue,它提供瞭更多地控制VM的垃圾回收。

 

摘自 奔跑的蝸牛

發佈留言