上一文章中提到:
发送端有三个主要的类: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包。
接收端的原理已经在本系列文章的第三篇中讲述清楚,下一篇将贴出代码实现过程,有写的不好的地方,欢迎各位指点!