当前位置: 代码迷 >> Android >> Android ilbc 话音对话示范(四)发送方代码
  详细解决方案

Android ilbc 话音对话示范(四)发送方代码

热度:172   发布时间:2016-05-01 17:33:31.0
Android ilbc 语音对话示范(四)发送方代码

上一文章中提到:

发送端有三个主要的类:AudioRecorder(负责音频采集),AudioEncoder(负责音频编码),AudioSender(负责 将编码后的数

发送出去); 这三个类中各有一个线程,录制开始后,这三个线程一起运行,分别执行各自的任务, AudioRecorder采集音频后,添加到

AudioEncoder 的音频数据的List中,而AudioEncoder 的编码线程不断从List头部取出数据,调用 ilbc 的底层\函数进行编码,编码

后的数据则又添加到下一级的AudioSender的 List中,AudioSender又不断从头部取出数据,然后发送出去;


1. 先建立一个 AudioData的类,代表一段音频数据:


public class AudioData {	int size;	byte[] realData;	//long timestamp;	public int getSize() {		return size;	}	public void setSize(int size) {		this.size = size;	}	public byte[] getRealData() {		return realData;	}	public void setRealData(byte[] realData) {		this.realData = realData;	}	//public long getTimestamp() {	// return timestamp;	// }	//	// public void setTimestamp(long timestamp) {	// this.timestamp = timestamp;	// }}

2.AudioRecorder 类,使用Android系统自带的AudioRecord来采集音频,每采集一次,就交给编码器编码。


public class AudioRecorder implements Runnable {	String LOG = "Recorder ";	private boolean isRecording = false;	private AudioRecord audioRecord;	private static final int audioSource = MediaRecorder.AudioSource.MIC;	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 static final int BUFFER_FRAME_SIZE =960;	private int audioBufSize = 0;	//	private byte[] samples;// 缓冲区	private int bufferRead = 0;// 从recorder中读取的samples的大小	private int bufferSize = 0;// samples的大小	// 开始录制	public void startRecording() {		bufferSize = BUFFER_FRAME_SIZE;		audioBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig,				audioFormat);		if (audioBufSize == AudioRecord.ERROR_BAD_VALUE) {			Log.e(LOG, "audioBufSize error");			return;		}		samples = new byte[audioBufSize];		// 初始化recorder		if (null == audioRecord) {			audioRecord = new AudioRecord(audioSource, sampleRate,					channelConfig, audioFormat, audioBufSize);		}		new Thread(this).start();	}	// 停止录制	public void stopRecording() {		this.isRecording = false;	}	public boolean isRecording() {		return isRecording;	}	// run	public void run() {		// 录制前,先启动解码器		AudioEncoder encoder = AudioEncoder.getInstance();		encoder.startEncoding();		System.out.println(LOG + "audioRecord startRecording()");		audioRecord.startRecording();		this.isRecording = true;		while (isRecording) {			bufferRead = audioRecord.read(samples, 0, bufferSize);			if (bufferRead > 0) {				// 将数据添加给解码器				encoder.addData(samples, bufferRead);			}			try {				Thread.sleep(20);			} catch (InterruptedException e) {				e.printStackTrace();			}		}		System.out.println(LOG + "录制结束");		audioRecord.stop();		encoder.stopEncoding();	}}

3. AudioEncoder,负责调用NDK 方法实现音频的编码,每编码一次,就交给AudioSender 去发送:

public class AudioEncoder implements Runnable {	String LOG = "AudioEncoder";	private static AudioEncoder encoder;	private boolean isEncoding = false;	private List<AudioData> dataList = null;// 存放数据	public static AudioEncoder getInstance() {		if (encoder == null) {			encoder = new AudioEncoder();		}		return encoder;	}	private AudioEncoder() {		dataList = Collections.synchronizedList(new LinkedList<AudioData>());	}	public void addData(byte[] data, int size) {		AudioData rawData = new AudioData();		rawData.setSize(size);		byte[] tempData = new byte[size];		System.arraycopy(data, 0, tempData, 0, size);		rawData.setRealData(tempData);		dataList.add(rawData);	}	// 开始编码	public void startEncoding() {		System.out.println(LOG + "解码线程启动");		if (isEncoding) {			Log.e(LOG, "编码器已经启动,不能再次启动");			return;		}		new Thread(this).start();	}	// 结束	public void stopEncoding() {		this.isEncoding = false;	}	public void run() {		// 先启动发送端		AudioSender sender = new AudioSender();		sender.startSending();		int encodeSize = 0;		byte[] encodedData = new byte[256];		// 初始化编码器		AudioCodec.audio_codec_init(30);		isEncoding = true;		while (isEncoding) {			if (dataList.size() == 0) {				try {					Thread.sleep(20);				} catch (InterruptedException e) {					e.printStackTrace();				}				continue;			}			if (isEncoding) {				AudioData rawData = dataList.remove(0);				encodedData = new byte[rawData.getSize()];				//				encodeSize = AudioCodec.audio_encode(rawData.getRealData(), 0,						rawData.getSize(), encodedData, 0);				System.out.println();				if (encodeSize > 0) {					sender.addData(encodedData, encodeSize);					// 清空数据					encodedData = new byte[encodedData.length];				}			}		}		System.out.println(LOG + "编码结束");		sender.stopSending();	}}

4. AudioSender类,负责音频数据的发送,使用UDP协议将编码后的AMR音频数据发送到服务器端,这个类功能简单:


public class AudioSender implements Runnable {	String LOG = "AudioSender ";	private boolean isSendering = false;	private List<AudioData> dataList;	DatagramSocket socket;	DatagramPacket dataPacket;	private InetAddress ip;	private int port;	public AudioSender() {		dataList = Collections.synchronizedList(new LinkedList<AudioData>());		try {			try {				ip = InetAddress.getByName(MyConfig.SERVER_HOST);				this.port = MyConfig.SERVER_PORT;				socket = new DatagramSocket();			} catch (UnknownHostException e) {				e.printStackTrace();			}		} catch (SocketException e) {			e.printStackTrace();		}	}	// 添加数据	public void addData(byte[] data, int size) {		AudioData encodedData = new AudioData();		encodedData.setSize(size);		byte[] tempData = new byte[size];		System.arraycopy(data, 0, tempData, 0, size);		encodedData.setRealData(tempData);		dataList.add(encodedData);	}	// 发送数据	private void sendData(byte[] data, int size) {		try {			dataPacket = new DatagramPacket(data, size, ip, port);			dataPacket.setData(data);			socket.send(dataPacket);		} catch (IOException e) {			e.printStackTrace();		}	}	// 开始发送	public void startSending() {		System.out.println(LOG + "发送线程启动");		new Thread(this).start();	}	// 停止发送	public void stopSending() {		this.isSendering = false;	}	// run	public void run() {		this.isSendering = true;		System.out.println(LOG + "开始发送数据");		while (isSendering) {			if (dataList.size() > 0) {				AudioData encodedData = dataList.remove(0);				sendData(encodedData.getRealData(), encodedData.getSize());			}		}		System.out.println(LOG + "发送结束");	}}

5. 另外,上述类中有一个 MyConfig 类,主要放一些 配置参数:


public class MyConfig {	public static String SERVER_HOST = "192.168.1.130";// 服务器的IP	public static final int SERVER_PORT = 5656;// 服务器的监听端口	public static final int CLIENT_PORT = 5757;//	public static final int AUDIO_STATUS_RECORDING = 0;//手机端的状态:录音 or 播放	public static final int AUDIO_STATUS_LISTENING = 1;	public static void setServerHost(String ip) {		System.out.println("修改后的服务器网址为  " + ip);		SERVER_HOST = ip;	}}

上述代码实现了发送端的功能,现在代码结构如下:

                           


      本实例中对音频没有添加时间戳处理,实际测试中没有太大的影响,可以听的清楚双方的语音对话,如果想要添加

  时间戳的话,就在音频录制 AudioRecord的 read方法出得到时间戳,然后附加给UDP包。

 接收端的原理已经在本系列文章的第三篇中讲述清楚,下一篇将贴出代码实现过程,有写的不好的地方,欢迎各位指点!






  相关解决方案