日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android通过数组,流播放声音的方法,音频实时传输

發布時間:2025/3/15 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 ;@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應用程序中管理音頻資源,用來記錄從平臺音頻輸入設備產生的數據。 通過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.AudioSourcepublic 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_STOPPEDRECORDSTATE_RECORDINGpublic int getSampleRate ()返回設置的音頻數據樣本采樣率,單位Hz。public int getState ()返回AudioRecord實例的狀態。 這點非常有用,用在AudioRecord 實例創建成功后,檢查初始化屬性。 它能肯定請求到了合適的硬件資源。參見STATE_INITIALIZEDSTATE_UNINITIALIZEDpublic 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的垃圾回收。

?

轉載于:https://www.cnblogs.com/niray/p/4251406.html

總結

以上是生活随笔為你收集整理的android通过数组,流播放声音的方法,音频实时传输的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。