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


 BY http://blog.csdn.net/ranxiedao 谢绝转载!

上一文章中提到:

发送端有三个主要的类: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;
	}
}

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

                           Android ilbc 语音对话示范(四)发送方代码_第1张图片


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

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

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

  BY http://blog.csdn.net/ranxiedao 谢绝转载!




你可能感兴趣的:(thread,android,String,服务器,Class,byte)