android通过数组,流播放声音的方法,音频实时传输
AudioRecord和AudioTrack類是Android獲取和播放音頻流的重要類,放置在android.media包中。與該包中 的MediaRecorder和MediaPlayer類不同,AudioRecord和AudioTrack類在獲取和播放音頻數(shù)據(jù)流時無需通過文件保 存和文件讀取,可以動態(tài)地直接獲取和播放音頻流,在實時處理音頻數(shù)據(jù)流時非常有用。
??? 當然,如果用戶只想錄音后寫入文件或從文件中取得音頻流進行播放,那么直接使用MediaRecorder和MediaPlayer類是首選方案,因為這 兩個類使用非常方便,而且成功率很高。而AudioRecord和AudioTrack類的使用卻比較復雜,我們發(fā)現(xiàn)很多人都不能成功地使用這兩個類,甚 至認為Android的這兩個類是不能工作的。
??? 其實,AudioRecord和AudioTrack類的使用雖然比較復雜,但是可以工作,我們不僅可以很好地使用了這兩個類,而且還通過套接字 (Socket)實現(xiàn)了音頻數(shù)據(jù)的網(wǎng)絡傳輸,做到了一端使用AudioRecord獲取音頻流然后通過套接字傳輸出去,而另一端通過套接字接收后使用 AudioTrack類播放。
??? 下面是我們對AudioRecord和AudioTrack類在使用方面的經(jīng)驗總結:
??? (1)創(chuàng)建AudioRecord和AudioTrack類對象:創(chuàng)建這兩個類的對象比較復雜,通過對文檔的反復和仔細理解,并通過多次失敗的嘗試,并在 北理工的某個Android大牛的網(wǎng)上的文章啟發(fā)下,我們也最終成功地創(chuàng)建了這兩個類的對象。
創(chuàng)建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類的監(jiān)聽函數(shù),不用也行。
?
??? (3)調(diào)試方面,包括初始化后看logcat信息,以確定類的工作狀態(tài),初始化是否成功等。
???? 編寫好代碼,沒有語法錯誤,調(diào)用模擬器運行、調(diào)試代碼時,logcat發(fā)揮了很好的功用。剛調(diào)試時,經(jīng)常會出現(xiàn)模擬器顯示出現(xiàn)異常,這時我們可以在代碼的 一些關鍵語句后添加如Log.d("test1","OK");這樣的語句進行標識,出現(xiàn)異常時我們就可以在logcat窗口觀察代碼執(zhí)行到哪里出現(xiàn)異 常,然后進行相應的修改、調(diào)試。模擬器不會出現(xiàn)異常時,又遇到了錄放音的問題。錄音方面,剛開始選擇將語音編碼數(shù)據(jù)存放在多個固定大小的文件中進行傳送, 但是這種情況下會出現(xiàn)聲音斷續(xù)的現(xiàn)象,而且要反復的建立文件,比較麻煩,后來想到要進行網(wǎng)上傳輸,直接將語音編碼數(shù)據(jù)以數(shù)據(jù)流的形式傳送,經(jīng)過驗證,這種 方法可行并且使代碼更加簡潔。放音方面,將接收到的數(shù)據(jù)流存放在一個數(shù)組中,然后將數(shù)組中數(shù)據(jù)寫到AudioTrack中。剛開始只是“嘟”幾聲,經(jīng)過檢 查發(fā)現(xiàn)只是把數(shù)據(jù)寫一次,加入循環(huán),讓數(shù)據(jù)反復寫到AudioTrack中,就可以聽到正常的語音了。接下來的工作主要是改善話音質(zhì)量與話音延遲,在進行 通話的過程中,觀察logcat窗口,發(fā)現(xiàn)向數(shù)組中寫數(shù)據(jù)時會出現(xiàn)Bufferflow的情況,于是把重心轉移到數(shù)組大小的影響上,經(jīng)過試驗,發(fā)現(xiàn) AudioRecord一次會讀640個數(shù)據(jù),然后就對錄音和放音中有數(shù)組的地方進行實驗修改。AudioRecord和AudioTrack進行實例化 時,參數(shù)中各有一個數(shù)組大小,經(jīng)過試驗這個數(shù)組大小和AudioRecord和AudioTrack能正常實例化所需的最小Buffer大小(即上面實例 化時的m_in_buf_size和m_out_buf_size參數(shù))相等且服務器方進行緩存數(shù)據(jù)的數(shù)組尺寸是上述數(shù)值的2倍時,語音質(zhì)量最好。由于錄 音和放音的速度不一致,受到北理工大牛的啟發(fā),在錄音方面,將存放錄音數(shù)據(jù)的數(shù)組放到LinkedList中,當LinkedList中數(shù)組個數(shù)達到 2(這個也是經(jīng)過試驗驗證話音質(zhì)量最好時的數(shù)據(jù))時,將先錄好的數(shù)組中數(shù)據(jù)傳送出去。經(jīng)過上述反復試驗和修改,最終使雙方通話質(zhì)量較好,且延時較短(大概 有2秒鐘)。
??? (4)通過套接字傳輸和接收數(shù)據(jù)
???? 數(shù)據(jù)傳送部分,使用的是套接字。通信雙方,通過不同的端口向服務器發(fā)送請求,與服務器連接上后,開始通話向服務器發(fā)送數(shù)據(jù),服務器通過一個套接字接收到一 方的數(shù)據(jù)后,先存在一個數(shù)組中,然后將該數(shù)組中數(shù)據(jù)以數(shù)據(jù)流的形式再通過另一個套接字傳送到另一方。這樣就實現(xiàn)了雙方數(shù)據(jù)的傳送。
???? (5)代碼架構
????? 為避免反復錄入和讀取數(shù)據(jù)占用較多資源,使程序在進行錄放音時不能執(zhí)行其他命令,故將錄音和放音各寫成一個線程類,然后在主程序中,通過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 ;@Overridepublic 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 Objectjava.lang.Objectandroid.media.AudioRecord類概述AudioRecord類在Java應用程序中管理音頻資源,用來記錄從平臺音頻輸入設備產(chǎn)生的數(shù)據(jù)。 通過AudioRecord對象來完成"pulling"(讀取)數(shù)據(jù)。 應用通過以下幾個方法負責立即從AudioRecord對象讀取: read(byte[], int, int), read(short[], int, int)或read(ByteBuffer, int). 無論使用哪種音頻格式,使用AudioRecord是最方便的。在創(chuàng)建AudioRecord對象時,AudioRecord會初始化,并和音頻緩沖區(qū)連接,用來緩沖新的音頻數(shù)據(jù)。 根據(jù)構造時指定的緩沖區(qū)大小,來決定AudioRecord能夠記錄多長的數(shù)據(jù)。 從硬件設備讀取的數(shù)據(jù),應小于整個記錄緩沖區(qū)。內(nèi)部類interface AudioRecord.OnRecordPositionUpdateListener接口定義為:當AudioRecord 收到一個由setNotificationMarkerPosition(int)設置的通知標志,或由 setPositionNotificationPeriod(int)設置的周期更新記錄的進度狀態(tài)時,回調(diào)此接口。常量public static final int ERROR表示操作失敗。常量值: -1 (0xffffffff)public static final int ERROR_BAD_VALUE表示使用了一個不合理的值導致的失敗。常量值: -2 (0xfffffffe)public static final int ERROR_INVALID_OPERATION表示不恰當?shù)姆椒▽е碌氖 3A恐? -3 (0xfffffffd)public static final int RECORDSTATE_RECORDING指示AudioRecord錄制狀態(tài)為“正在錄制”。常量值: 3 (0x00000003)public static final int RECORDSTATE_STOPPED指示AudioRecord錄制狀態(tài)為“不在錄制”。常量值: 1 (0x00000001)public static final int STATE_INITIALIZED指示AudioRecord準備就緒。常量值: 1 (0x00000001)public static final int STATE_UNINITIALIZED指示AudioRecord狀態(tài)沒有初始化成功。常量值: 0 (0x00000000)public static final int SUCCESS表示操作成功。常量值: 0 (0x00000000)構造函數(shù)public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)類構造函數(shù)。參數(shù)audioSource 錄制源。 請見MediaRecorder.AudioSource錄制源定義。sampleRateInHz 默認采樣率,單位Hz。 44100Hz是當前唯一能保證在所有設備上工作的采樣率,在一些設備上還有22050, 16000或11025。channelConfig 描述音頻通道設置。 請見CHANNEL_IN_MONO 和 CHANNEL_IN_STEREO。 CHANNEL_IN_MONO保證能在所有設備上工作。audioFormat 音頻數(shù)據(jù)保證支持此格式。 請見ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。bufferSizeInBytes 在錄制過程中,音頻數(shù)據(jù)寫入緩沖區(qū)的總數(shù)(字節(jié))。 從緩沖區(qū)讀取的新音頻數(shù)據(jù)總會小于此值。 getMinBufferSize(int, int, int)返回AudioRecord 實例創(chuàng)建成功后的最小緩沖區(qū)。 設置的值比getMinBufferSize()還小則會導致初始化失敗。異常IllegalArgumentException公共方法public int getAudioFormat ()返回設置的音頻數(shù)據(jù)格式。 請見ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。public int getAudioSource ()返回音頻錄制源。參見MediaRecorder.AudioSourcepublic int getChannelConfiguration ()返回設置的頻道設置。 請見CHANNEL_IN_MONO和CHANNEL_IN_STEREO。public int getChannelCount ()返回設置的頻道數(shù)目。public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)返回成功創(chuàng)建AudioRecord對象所需要的最小緩沖區(qū)大小。 注意:這個大小并不保證在負荷下的流暢錄制,應根據(jù)預期的頻率來選擇更高的值,AudioRecord實例在推送新數(shù)據(jù)時使用此值。參數(shù)sampleRateInHz 默認采樣率,單位Hz。channelConfig 描述音頻通道設置。請見CHANNEL_IN_MONO和CHANNEL_IN_STEREO。audioFormat 音頻數(shù)據(jù)保證支持此格式。參見ENCODING_PCM_16BIT。返回值如果硬件不支持錄制參數(shù),或輸入了一個無效的參數(shù),則返回ERROR_BAD_VALUE,如果硬件查詢到輸出屬性沒有實現(xiàn),或最小緩沖區(qū)用byte表示,則返回ERROR。參見更多信息請見有效的設置參數(shù)public int getNotificationMarkerPosition ()返回通知,標記框架中的位置。public int getPositionNotificationPeriod ()返回通知,更新框架中的時間位置。public int getRecordingState ()返回AudioRecord實例的錄制狀態(tài)。參見RECORDSTATE_STOPPEDRECORDSTATE_RECORDINGpublic int getSampleRate ()返回設置的音頻數(shù)據(jù)樣本采樣率,單位Hz。public int getState ()返回AudioRecord實例的狀態(tài)。 這點非常有用,用在AudioRecord 實例創(chuàng)建成功后,檢查初始化屬性。 它能肯定請求到了合適的硬件資源。參見STATE_INITIALIZEDSTATE_UNINITIALIZEDpublic int read (short[] audioData, int offsetInShorts, int sizeInShorts)從音頻硬件錄制緩沖區(qū)讀取數(shù)據(jù)。參數(shù)audioData 寫入的音頻錄制數(shù)據(jù)。offsetInShorts 目標數(shù)組 audioData 的起始偏移量。sizeInShorts 請求讀取的數(shù)據(jù)大小。返回值返回short型數(shù)據(jù),表示讀取到的數(shù)據(jù),如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數(shù)不能解析成有效的數(shù)據(jù)或索引,則返回ERROR_BAD_VALUE。 返回數(shù)值不會超過sizeInShorts。public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)從音頻硬件錄制緩沖區(qū)讀取數(shù)據(jù)。參數(shù)audioData 寫入的音頻錄制數(shù)據(jù)。offsetInBytes audioData的起始偏移值,單位byte。sizeInBytes 讀取的最大字節(jié)數(shù)。返回值讀入緩沖區(qū)的總byte數(shù),如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數(shù)不能解析成有效的數(shù)據(jù)或索引,則返回ERROR_BAD_VALUE。 讀取的總byte數(shù)不會超過sizeInBytes。public int read (ByteBuffer audioBuffer, int sizeInBytes)從音頻硬件錄制緩沖區(qū)讀取數(shù)據(jù),直接復制到指定緩沖區(qū)。 如果audioBuffer不是直接的緩沖區(qū),此方法總是返回0。參數(shù)audioBuffer 存儲寫入音頻錄制數(shù)據(jù)的緩沖區(qū)。sizeInBytes 請求的最大字節(jié)數(shù)。返回值讀入緩沖區(qū)的總byte數(shù),如果對象屬性沒有初始化,則返回ERROR_INVALID_OPERATION,如果參數(shù)不能解析成有效的數(shù)據(jù)或索引,則返回ERROR_BAD_VALUE。 讀取的總byte數(shù)不會超過sizeInBytes。public void release ()釋放本地AudioRecord資源。 對象不能經(jīng)常使用此方法,而且在調(diào)用release()后,必須設置引用為null。public int setNotificationMarkerPosition (int markerInFrames)如果設置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),則通知監(jiān)聽者設置位置標記。參數(shù)markerInFrames 在框架中快速標記位置。返回值返回錯誤或成功代碼,請見SUCCESS、ERROR_BAD_VALUE、ERROR_INVALID_OPERATION。public int setPositionNotificationPeriod (int periodInFrames)如果設置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),則通知監(jiān)聽者設置時間標記。參數(shù)markerInFrames 在框架中快速更新時間標記。返回值返回錯誤或成功代碼,請見SUCCESS、ERROR_INVALID_OPERATION。public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener, Handler handler)當之前設置的標志已經(jīng)成立,或者周期錄制位置更新時,設置處理監(jiān)聽者。 使用此方法來將Handler 和別的線程聯(lián)系起來,來接收AudioRecord 事件,比創(chuàng)建AudioTrack 實例更好一些。參數(shù)handler 用來接收事件通知消息。public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener)當之前設置的標志已經(jīng)成立,或者周期錄制位置更新時,設置處理監(jiān)聽者。public void startRecording ()AudioRecord實例開始進行錄制。異常IllegalStateException受保護方法protected void finalize ()通知VM回收此對象內(nèi)存。 此方法只能用在運行的應用程序沒有任何線程再使用此對象,來告訴垃圾回收器回收此對象。此方法用于釋放系統(tǒng)資源,由垃圾回收器清除此對象。 默認沒有實現(xiàn),由VM來決定,但子類根據(jù)需要可重寫finalize()。 在執(zhí)行期間,調(diào)用此方法可能會立即拋出未定義異常,但是可以忽略。注意:VM保證對象可以一次或多次調(diào)用finalize(),但并不保證finalize()會馬上執(zhí)行。 例如,對象B的finalize()可能延遲執(zhí)行,等待對象A的finalize()延遲回收A的內(nèi)存。 為了安全起見,請看ReferenceQueue,它提供了更多地控制VM的垃圾回收。?
轉載于:https://www.cnblogs.com/niray/p/4251406.html
總結
以上是生活随笔為你收集整理的android通过数组,流播放声音的方法,音频实时传输的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机网络之数据链路层:13、令牌传递协
- 下一篇: 操作系统之计算机系统概述:7、操作系统的