Android ilbc 話音對話示範(五)接收端處理

tags:    時間:2013-12-15 15:22:02
Android ilbc 語音對話示範(五)接收端處理

此系列文章拖了N久,有好多人發郵件來詢問我第五次的文章為什麼沒有寫,其實非常抱歉,本人學生一個,暑假一直

去公司實習,最近又忙著各種招聘找工作,沒有時間好好寫,現在抽空把最後一篇補上,水平有限,如過有不對的,請

各位指正~

    前四篇文章分別介紹了 「代碼結構」,「程序流程」,以及」發送方的處理」,現在就把接收方的處理流程做個介紹;

           

    如上圖所示,接收方的操作有三個類:AudioDecoder(負責解碼),AudioPlayer(負責播放解碼后的音頻),

AudioReceiver(負責從伺服器接收音頻數據包),這三個類的流程在第三篇中有詳細的介紹。

1.AudioReceiver代碼:

   AudioReceiver使用UDP方式從服務端接收音頻數據,其過程比較簡單,直接上代碼:

 

package xmu.swordbearer.audio.receiver;  import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;  import xmu.swordbearer.audio.MyConfig; import android.util.Log;  public class AudioReceiver implements Runnable { 	String LOG = "NET Reciever "; 	int port = MyConfig.CLIENT_PORT;// 接收的埠 	DatagramSocket socket; 	DatagramPacket packet; 	boolean isRunning = false;  	private byte[] packetBuf = new byte[1024]; 	private int packetSize = 1024;  	/* 	 * 開始接收數據 	 */ 	public void startRecieving() { 		if (socket == null) { 			try { 				socket = new DatagramSocket(port); 				packet = new DatagramPacket(packetBuf, packetSize); 			} catch (SocketException e) { 			} 		} 		new Thread(this).start(); 	}  	/* 	 * 停止接收數據 	 */ 	public void stopRecieving() { 		isRunning = false; 	}  	/* 	 * 釋放資源 	 */ 	private void release() { 		if (packet != null) { 			packet = null; 		} 		if (socket != null) { 			socket.close(); 			socket = null; 		} 	}  	public void run() { 		// 在接收前,要先啟動解碼器 		AudioDecoder decoder = AudioDecoder.getInstance(); 		decoder.startDecoding();  		isRunning = true; 		try { 			while (isRunning) { 				socket.receive(packet); 				// 每接收一個UDP包,就交給解碼器,等待解碼 				decoder.addData(packet.getData(), packet.getLength()); 			}  		} catch (IOException e) { 			Log.e(LOG, LOG + "RECIEVE ERROR!"); 		} 		// 接收完成,停止解碼器,釋放資源 		decoder.stopDecoding(); 		release(); 		Log.e(LOG, LOG + "stop recieving"); 	}  }


2.AudioDecoder代碼:

解碼的過程也很簡單,由於接收端接收到了音頻數據,然後就把數據交給解碼器,所以解碼的主要工作就是把接收端的數

據取出來進行解碼,如果解碼正確,就將解碼后的數據再轉交給AudioPlayer去播放,這三個類之間是依次傳遞的 :

    AudioReceiver---->AudioDecoder--->AudioPlayer

下面代碼中有個List變數 private List<AudioData> dataList = null;這個就是用來存放數據的,每次解碼時,dataList.remove(0),

從最前端取出數據進行解碼:

package xmu.swordbearer.audio.receiver;  import java.util.Collections; import java.util.LinkedList; import java.util.List;  import xmu.swordbearer.audio.AudioCodec; import xmu.swordbearer.audio.data.AudioData; import android.util.Log;  public class AudioDecoder implements Runnable {  	String LOG = "CODEC Decoder "; 	private static AudioDecoder decoder;  	private static final int MAX_BUFFER_SIZE = 2048;  	private byte[] decodedData = new byte[1024];// data of decoded 	private boolean isDecoding = false; 	private List<AudioData> dataList = null;  	public static AudioDecoder getInstance() { 		if (decoder == null) { 			decoder = new AudioDecoder(); 		} 		return decoder; 	}  	private AudioDecoder() { 		this.dataList = Collections 				.synchronizedList(new LinkedList<AudioData>()); 	}  	/* 	 * add Data to be decoded 	 *  	 * @ data:the data recieved from server 	 *  	 * @ size:data size 	 */ 	public void addData(byte[] data, int size) { 		AudioData adata = new AudioData(); 		adata.setSize(size); 		byte[] tempData = new byte[size]; 		System.arraycopy(data, 0, tempData, 0, size); 		adata.setRealData(tempData); 		dataList.add(adata); 		System.out.println(LOG + "add data once");  	}  	/* 	 * start decode AMR data 	 */ 	public void startDecoding() { 		System.out.println(LOG + "start decoder"); 		if (isDecoding) { 			return; 		} 		new Thread(this).start(); 	}  	public void run() { 		// start player first 		AudioPlayer player = AudioPlayer.getInstance(); 		player.startPlaying(); 		// 		this.isDecoding = true; 		// init ILBC parameter:30 ,20, 15 		AudioCodec.audio_codec_init(30);  		Log.d(LOG, LOG + "initialized decoder"); 		int decodeSize = 0; 		while (isDecoding) { 			while (dataList.size() > 0) { 				AudioData encodedData = dataList.remove(0); 				decodedData = new byte[MAX_BUFFER_SIZE];  				byte[] data = encodedData.getRealData(); 				// 				decodeSize = AudioCodec.audio_decode(data, 0, 						encodedData.getSize(), decodedData, 0); 				if (decodeSize > 0) { 					// add decoded audio to player 					player.addData(decodedData, decodeSize); 					// clear data 					decodedData = new byte[decodedData.length]; 				} 			} 		} 		System.out.println(LOG + "stop decoder"); 		// stop playback audio 		player.stopPlaying(); 	}  	public void stopDecoding() { 		this.isDecoding = false; 	} }

3.AudioPlayer代碼:

播放器的工作流程其實和解碼器一模一樣,都是啟動一個線程,然後不斷從自己的 dataList中提取數據。

不過要注意,播放器的一些參數配置非常的關鍵;

播放聲音時,使用了Android自帶的 AudioTrack 這個類,它有這個方法:

public int write(byte[] audioData,int offsetInBytes, int sizeInBytes)可以直接播放;

所有播放器的代碼如下:

package xmu.swordbearer.audio.receiver;  import java.util.Collections; import java.util.LinkedList; import java.util.List;  import xmu.swordbearer.audio.data.AudioData; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.util.Log;  public class AudioPlayer implements Runnable { 	String LOG = "AudioPlayer "; 	private static AudioPlayer player;  	private List<AudioData> dataList = null; 	private AudioData playData; 	private boolean isPlaying = false;  	private AudioTrack audioTrack;  	private static final int sampleRate = 8000; 	// 注意:參數配置 	private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; 	private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;  	private AudioPlayer() { 		dataList = Collections.synchronizedList(new LinkedList<AudioData>()); 	}  	public static AudioPlayer getInstance() { 		if (player == null) { 			player = new AudioPlayer(); 		} 		return player; 	}  	public void addData(byte[] rawData, int size) { 		AudioData decodedData = new AudioData(); 		decodedData.setSize(size);  		byte[] tempData = new byte[size]; 		System.arraycopy(rawData, 0, tempData, 0, size); 		decodedData.setRealData(tempData); 		dataList.add(decodedData); 	}  	/* 	 * init Player parameters 	 */ 	private boolean initAudioTrack() { 		int bufferSize = AudioRecord.getMinBufferSize(sampleRate, 				channelConfig, audioFormat); 		if (bufferSize < 0) { 			Log.e(LOG, LOG + "initialize error!"); 			return false; 		} 		audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 				channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM); 		// set volume:設置播放音量 		audioTrack.setStereoVolume(1.0f, 1.0f); 		audioTrack.play(); 		return true; 	}  	private void playFromList() { 		while (dataList.size() > 0 && isPlaying) { 			playData = dataList.remove(0); 			audioTrack.write(playData.getRealData(), 0, playData.getSize()); 		} 	}  	public void startPlaying() { 		if (isPlaying) { 			return; 		} 		new Thread(this).start(); 	}  	public void run() { 		this.isPlaying = true; 		 		if (!initAudioTrack()) { 			Log.e(LOG, LOG + "initialized player error!"); 			return; 		} 		while (isPlaying) { 			if (dataList.size() > 0) { 				playFromList(); 			} else { 				try { 					Thread.sleep(20); 				} catch (InterruptedException e) { 				} 			} 		} 		if (this.audioTrack != null) { 			if (this.audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 				this.audioTrack.stop(); 				this.audioTrack.release(); 			} 		} 		Log.d(LOG, LOG + "end playing"); 	}  	public void stopPlaying() { 		this.isPlaying = false; 	} } 


4.簡易服務端:

為了方便測試,我自己用Java 寫了一個UDP的伺服器,其功能非常的弱,就是接收,然後轉發給另一方:

import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException;  public class AudioServer implements Runnable {  	DatagramSocket socket; 	DatagramPacket packet;// 從客戶端接收到的UDP包 	DatagramPacket sendPkt;// 轉發給另一個客戶端的UDP包  	byte[] pktBuffer = new byte[1024]; 	int bufferSize = 1024; 	boolean isRunning = false; 	int myport = 5656;  	// /////////// 	String clientIpStr = "192.168.1.104"; 	InetAddress clientIp; 	int clientPort = 5757;  	public AudioServer() { 		try { 			clientIp = InetAddress.getByName(clientIpStr); 		} catch (UnknownHostException e1) { 			e1.printStackTrace(); 		} 		try { 			socket = new DatagramSocket(myport); 			packet = new DatagramPacket(pktBuffer, bufferSize); 		} catch (SocketException e) { 			e.printStackTrace(); 		} 		System.out.println("伺服器初始化完成"); 	}  	public void startServer() { 		this.isRunning = true; 		new Thread(this).start(); 	}  	public void run() { 		try { 			while (isRunning) { 				socket.receive(packet); 				sendPkt = new DatagramPacket(packet.getData(), 						packet.getLength(), packet.getAddress(), clientPort); 				socket.send(sendPkt); 				try { 					Thread.sleep(20); 				} catch (InterruptedException e) { 					e.printStackTrace(); 				} 			} 		} catch (IOException e) { 		} 	}  	// main 	public static void main(String[] args) { 		new AudioServer().startServer(); 	} } 


5.結語:

Android使用 ILBC 進行語音通話的大致過程就講述完了,此系列只是做一個ILBC 使用原理的介紹,距離真正的語音

通話還有很多工作要做,缺點還是很多的:

   1. 文章中介紹的只是單方通話,如果要做成雙方互相通話或者一對多的通話,就需要增加更多的流程處理,其服務端

也要做很多工作;

   2. 實時性:本程序在區域網中使用時,實時性還是較高的,但是再廣域網中,效果可能會有所下降,除此之外,本

程序還缺少時間戳的處理,如果網路狀況不理想,或者數據延遲,就會導致語音播放前後混亂;

   3. 伺服器很弱:真正的流媒體伺服器,需要很強的功能,來對數據進行處理,我是為了方便,就寫了一個簡單的,

最近打算移植live555,用來做專門的流媒體伺服器,用RTP協議對數據進行封裝,這樣效果應該會好很多。




推薦閱讀文章

Bookmark the permalink ,來源:互聯網