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

来源:互联网 发布:基础网络 腾讯云 编辑:程序博客网 时间:2024/04/30 08:06


 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];// 初始化recorderif (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;}// runpublic 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;}// runpublic 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";// 服务器的IPpublic 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包。

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

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