Android网络对讲机的实现

来源:转载

上个星期公司给出了一个项目需求,做一个基于socket通讯协议的网络对讲机。于是在项目开始前计划了一下基本的实现流程。

1、从手机麦中采集音频数据;2、将PCM音频数据编码压缩;3、将压缩好的音频通过无线网络发送出去;4、其他手机接收音频数据并解码;5、将音频数据写入到音轨中播放。项目虽然简单,但其中的一些小问题也折腾了我不少时间。

首先我们创建一个线程用来采集音频数据,通过android提供的AudioRecord可以实时采集音频流。AudioRecord类在Java应用程序中管理音频资源,用来记录从平台音频输入设备产生的数据。其实调用AudioRecord很简单,首先创建AudioRecord对象,AudioRecord会初始化并连接音频缓冲区,用来缓冲新的音频数据。根据指定的缓冲区的大小来决定AudioRecord能够记录多长的数据。

调用getMinBufferSize(int,int,int)返回最小的缓冲区大小。然后根据得到的最小缓冲区大小来创建AudioRecord对象:

inputMinSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);audioRec = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, inputMinSize);
参数 sampleRateInHz       默认采样率,单位Hz。 channelConfig         描述音频通道设置。 audioFormat             音频数据保证支持此格式。 

AudioRecord初始化工作完毕后启用录制线程,并且调用startRecording ()开始进行音频录制。调用read(short,int,int)方法从音频硬件录制缓冲区读取数据。拿到音频数据后,直接通过网络发送出去是不行的,我们在这里还要做一项工作就是实现音频压缩。在网上提供了很多音频的编码库,我们可以将源码导入到项目中通过android ndk编译成.so文件,最后通过jni来调用。我这里直接用sipdroid开源项目提供的SILK编解码库(下载编码库)。

本地方法 encode(short[] lin,int offset,byte[] encoded,int size)参数                   lin  源数据                   offset   源数组的起始偏移量                   encoded  编码后的数据       size     请求编码的数据大小返回值   编码后的数据大小 

调用encode(short[], int, byte[], int)压缩已经采集完毕的音频数据,我们就可以通过网络发送出去了。 

接下来,我们创建一个socket udp实例,为什么这里选择udp而不是tcp呢?从我们本身的项目需求出发,我们做的这个项目的通讯方式是相互收发数据的,属于手机与手机两“客户端“之间的通讯。并且,在这种音频通信过程中,我们要传输的数据量是比较庞大的,因此采用资源消耗少,处理速度快的UDP协议是合理的。指定发送的端口号,我们将数据封装成报文发送出去,整个采集发送的过程如下:

class RecordSoundThread extends Thread { private boolean flag = true; private DatagramSocket mSocket; private int inputBufSize = 160; short[] inputBytes = new short[1024]; byte[] encodeBytes = new byte[1024]; RecordSoundThread() throws SocketException { // TODO Auto-generated constructor stub mSocket = new DatagramSocket(); } @Override public void run() { if (mSocket == null) return; while (flag) { if (isSpeakMode) { try { int length = audioRec.read(inputBytes, 0, inputBufSize);// calc(inputBytes, 0, length); length = silk8.encode(inputBytes, 0, encodeBytes, length); DatagramPacket writePacket; if (inetAddress.length() > 0) { InetAddress inet = InetAddress .getByName(inetAddress); writePacket = new DatagramPacket(encodeBytes, length, inet, NETPORT); writePacket.setLength(length); mSocket.send(writePacket); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public void close() { flag = false; if (mSocket != null) { mSocket.close(); } } }

接下来我们要接收目标机器发送过来的音频数据了。同样,创建一个线程用来接收网络中的音频数据,并且对音频数据进行解码。

本地方法 decode(byte[] encoded, short[] lin, int size)参数       encoded 源数据       lin   解码后的数据       size 请求解码的数据大小返回值   解码后的数据大小 

得到解码后的PCM音频流,我们就可以使用AudioTrack将音频播放出来了。

AudioTrack类在java应用程序中管理和播放音频资源,将PCM音频数据写入到缓冲区来播放音频设备。首先创建AudioTrack对象,AudioTrack会初始化并连接音频缓冲区,根据指定的缓冲区大小来决定audioTrack能够播放多长的数据。调用getMinBufferSize(int,int,int)返回最小的缓冲区大小。然后根据得到的最小缓冲区大小来创建audioTrack对象:

outputMinSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);audioTrk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, outputMinSize, AudioTrack.MODE_STREAM);

AudioTrack初始化工作完毕后启用接收线程,并且调用play()开始播放。调用write(short[],int,int)方法将PCM音频数据写入到音频硬件中。

 

class RecevRecordThread extends Thread { private boolean flag = true; private DatagramSocket mSocket; short[] decodeBytes = new short[1024]; byte[] outputBytes = new byte[1024]; RecevRecordThread() throws SocketException { // TODO Auto-generated constructor stub mSocket = new DatagramSocket(NETPORT); } @Override public void run() { if (mSocket == null) return; audioTrk.play(); while (flag) { DatagramPacket recevPacket; try { recevPacket = new DatagramPacket(outputBytes, 0, outputBytes.length); mSocket.receive(recevPacket); int length = recevPacket.getLength(); length = silk8.decode(outputBytes, decodeBytes, length);// calc2(decodeBytes, 0, length); audioTrk.write(decodeBytes, 0, length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } audioTrk.stop(); } public void close() { flag = false; if (mSocket != null) { mSocket.close(); } } }

  最后,退出应用后别忘了释放资源。

public void onDestroy() { silk8.close(); recevThread.close(); recordThread.close(); audioRec.release(); audioTrk.release();}

好了,网络对讲机的实现过程差不多就是这个样子了,马上动手试一下效果吧^_^

 

版权声明:

访问者可将本主页(http://www.cnblogs.com/canf963/p/4875228.html)提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本主页及相关权利人的合法权利。转载前务必署名本文作者并以超链接形式注明内容来自本主页,以免带来不必要的麻烦。

分享给朋友:
您可能感兴趣的文章:
随机阅读: