上個星期公司給出了一個項目需求,做一個基於socket通訊協議的網絡對講機。於是在項目開始前計划了一下基本的實現流程。
1、從手機麥中采集音頻數據;2、將PCM音頻數據編碼壓縮;3、將壓縮好的音頻通過無線網絡發送出去;4、其他手機接收音頻數據並解碼;5、將音頻數據寫入到音軌中播放。項目雖然簡單,但其中的一些小問題也折騰了我不少時間。
首先我們創建一個線程用來采集音頻數據,通過android提供的AudioRecord可以實時采集音頻流。AudioRecord類在Java應用程序中管理音頻資源,用來記錄從平台音頻輸入設備產生的數據。其實調用AudioRecord很簡單,首先創建AudioRecord對象,AudioRecord會初始化並連接音頻緩沖區,用來緩沖新的音頻數據。根據指定的緩沖區的大小來決定AudioRecord能夠記錄多長的數據。
調用getMinBufferSize(int,int,int)返回最小的緩沖區大小。然后根據得到的最小緩沖區大小來創建AudioRecord對象:
inputMinSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); audioRec = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, inputMinSize);
AudioRecord初始化工作完畢后啟用錄制線程,並且調用startRecording ()開始進行音頻錄制。調用read(short,int,int)方法從音頻硬件錄制緩沖區讀取數據。拿到音頻數據后,直接通過網絡發送出去是不行的,我們在這里還要做一項工作就是實現音頻壓縮。在網上提供了很多音頻的編碼庫,我們可以將源碼導入到項目中通過android ndk編譯成.so文件,最后通過jni來調用。我這里直接用sipdroid開源項目提供的SILK編解碼庫(下載編碼庫)。
本地方法 encode(short[] lin,int offset,byte[] encoded,int size) 參數 lin 源數據 offset 源數組的起始偏移量 encoded 編碼后的數據 size 請求編碼的數據大小返回值 編碼后的數據大小調用encode(short[], int, byte[], int)壓縮已經采集完畢的音頻數據,我們就可以通過網絡發送出去了。
接下來,我們創建一個socket udp實例,為什么這里選擇udp而不是tcp呢?從我們本身的項目需求出發,我們做的這個項目的通訊方式是相互收發數據的,屬於手機與手機兩“客戶端“之間的通訊。並且,在這種音頻通信過程中,我們要傳輸的數據量是比較龐大的,因此采用資源消耗少,處理速度快的UDP協議是合理的。指定發送的端口號,我們將數據封裝成報文發送出去,整個采集發送的過程如下:

class RecordSoundThread extends Thread { private boolean flag = true; private DatagramSocket mSocket; private int inputBufSize = 160; short[] inputBytes = new short[1024]; byte[] encodeBytes = new byte[1024]; RecordSoundThread() throws SocketException { // TODO Auto-generated constructor stub mSocket = new DatagramSocket(); } @Override public void run() { if (mSocket == null) return; while (flag) { if (isSpeakMode) { try { int length = audioRec.read(inputBytes, 0, inputBufSize); // calc(inputBytes, 0, length); length = silk8.encode(inputBytes, 0, encodeBytes, length); DatagramPacket writePacket; if (inetAddress.length() > 0) { InetAddress inet = InetAddress .getByName(inetAddress); writePacket = new DatagramPacket(encodeBytes, length, inet, NETPORT); writePacket.setLength(length); mSocket.send(writePacket); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public void close() { flag = false; if (mSocket != null) { mSocket.close(); } } }
接下來我們要接收目標機器發送過來的音頻數據了。同樣,創建一個線程用來接收網絡中的音頻數據,並且對音頻數據進行解碼。
本地方法 decode(byte[] encoded, short[] lin, int size) 參數 encoded 源數據 lin 解碼后的數據 size 請求解碼的數據大小 返回值 解碼后的數據大小得到解碼后的PCM音頻流,我們就可以使用AudioTrack將音頻播放出來了。
AudioTrack類在java應用程序中管理和播放音頻資源,將PCM音頻數據寫入到緩沖區來播放音頻設備。首先創建AudioTrack對象,AudioTrack會初始化並連接音頻緩沖區,根據指定的緩沖區大小來決定audioTrack能夠播放多長的數據。調用getMinBufferSize(int,int,int)返回最小的緩沖區大小。然后根據得到的最小緩沖區大小來創建audioTrack對象:
outputMinSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); audioTrk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, outputMinSize, AudioTrack.MODE_STREAM);
AudioTrack初始化工作完畢后啟用接收線程,並且調用play()開始播放。調用write(short[],int,int)方法將PCM音頻數據寫入到音頻硬件中。

class RecevRecordThread extends Thread { private boolean flag = true; private DatagramSocket mSocket; short[] decodeBytes = new short[1024]; byte[] outputBytes = new byte[1024]; RecevRecordThread() throws SocketException { // TODO Auto-generated constructor stub mSocket = new DatagramSocket(NETPORT); } @Override public void run() { if (mSocket == null) return; audioTrk.play(); while (flag) { DatagramPacket recevPacket; try { recevPacket = new DatagramPacket(outputBytes, 0, outputBytes.length); mSocket.receive(recevPacket); int length = recevPacket.getLength(); length = silk8.decode(outputBytes, decodeBytes, length); // calc2(decodeBytes, 0, length); audioTrk.write(decodeBytes, 0, length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } audioTrk.stop(); } public void close() { flag = false; if (mSocket != null) { mSocket.close(); } } }
最后,退出應用后別忘了釋放資源。
public void onDestroy() { silk8.close(); recevThread.close(); recordThread.close(); audioRec.release(); audioTrk.release(); }
好了,網絡對講機的實現過程差不多就是這個樣子了,馬上動手試一下效果吧^_^
版權聲明:
訪問者可將本主頁(http://www.cnblogs.com/canf963/p/4875228.html)提供的內容或服務用於個人學習、研究或欣賞,以及其他非商業性或非盈利性用途,但同時應遵守著作權法及其他相關法律的規定,不得侵犯本主頁及相關權利人的合法權利。轉載前務必署名本文作者並以超鏈接形式注明內容來自本主頁,以免帶來不必要的麻煩。