最近做了一个简单的仿微信聊天功能。目的是为了熟悉一下UDP/TCP的使用。
转载请说明出处:https://mp.csdn.net/console/editor/html/105584620
源码已上传,地址:https://download.csdn.net/download/checkchen99/12334651
首先上个图
这个就是简单的聊天界面,可发送文字,语音。
接下来上重点的代码。
init(); initAudioRecord(); initAudioTracker(); startReceiveThread();
init主要初始化UI,设置IP,绑定事件listener。
之后是初始化AudioRecord和AuidoTracker参数
private void initAudioRecord(){ //播放的采样频率 和录制的采样频率一样 int sampleRate = 44100; //和录制的一样的 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //录音用输入单声道 播放用输出单声道 int channelConfig=AudioFormat.CHANNEL_IN_MONO; minBufferSize=AudioRecord.getMinBufferSize( sampleRate, channelConfig,AudioFormat.ENCODING_PCM_16BIT); System.out.println("****RecordMinBufferSize = "+minBufferSize); audioRec=new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,channelConfig, audioFormat,minBufferSize); buffer=new byte[minBufferSize]; if(audioRec==null){ Log.d("chenshulin","audiRec = null"); return; } //声学回声消除器 AcousticEchoCanceler 消除了从远程捕捉到音频信号上的信号的作用 // if(AcousticEchoCanceler.isAvailable()){ // aec=AcousticEchoCanceler.create(audioRec.getAudioSessionId()); // if(aec!=null){ // aec.setEnabled(true); // } // } //自动增益控制 AutomaticGainControl 自动恢复正常捕获的信号输出 if(AutomaticGainControl.isAvailable()){ agc=AutomaticGainControl.create(audioRec.getAudioSessionId()); if(agc!=null){ agc.setEnabled(true); } } //噪声抑制器 NoiseSuppressor 可以消除被捕获信号的背景噪音 if(NoiseSuppressor.isAvailable()){ nc=NoiseSuppressor.create(audioRec.getAudioSessionId()); if(nc!=null){ nc.setEnabled(true); } } mRecordQueue=new LinkedList<byte[]>(); }private void initAudioTracker() { //扬声器播放 int streamType = AudioManager.STREAM_MUSIC; //播放的采样频率 和录制的采样频率一样 int sampleRate = 44100;//和录制的一样的 int audioFormat= AudioFormat.ENCODING_PCM_16BIT; //流模式 int mode = AudioTrack.MODE_STREAM; //录音用输入单声道 播放用输出单声道 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; int recBufSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); System.out.println("****playRecBufSize = "+recBufSize); audioTrk = new AudioTrack(streamType, sampleRate, channelConfig, audioFormat, recBufSize,mode); audioTrk.setStereoVolume(AudioTrack.getMaxVolume(), AudioTrack.getMaxVolume()); bufferRece=new byte[recBufSize]; return; }
初始完后开始启动接收线程
void startReceiveThread(){ new Thread(new Runnable() { @Override public void run() { receiveMsg(); } }).start(); new Thread(new Runnable() { @Override public void run() { ReceiveVoiceMsg(); } }).start(); }
这里启动了2个线程,一个用来接收文字,一个接收语音。先来看发送的代码,可以用TCP,也可以用使用UDP。作为实时发送用UDP会多一些。效率高一些。这里先将TCP。上代码:
void TcpSendMsg() throws UnknownHostException { InetAddress inetAddress = InetAddress.getByName(ClientIp);//得到IP地址,注意这个是接收端的ip。 try { Socket tcpSocket = new Socket(inetAddress, 8850);//新建socket,绑定接收端的ip和端口 OutputStream outputData = tcpSocket.getOutputStream();//创建输出流 outputData.write(tvServiceMsg.getText().toString().getBytes());//将消息装入输出流 outputData.close(); tcpSocket.close(); Log.d("csl","发送完成"); }catch (Exception e) { e.getMessage(); Log.d("csl","发送异常"); Log.d("csl","e.getMessage()="+e.getMessage()); } }
看看TCP的接收端,也叫客户端。
String tcpRecSting =""; public void TcpReceiveMsg() { try { if (server == null) server = new ServerSocket(8850);//创建socket,绑定端口,这个端口跟发送端是同一个端口号。 } catch (Exception e) { e.printStackTrace(); Log.d("csl", "ServerSocket error" + e.getMessage()); } while (true) { try { Log.d("chenshulin", "开始接收"); final byte[] buffer = new byte[1024];//创建接收缓冲区 Socket data = server.accept(); InputStream dataStream = data.getInputStream(); final int len = dataStream.read(buffer);//读取数据 tcpRecSting = new String(buffer,0,len);//转化成String mHandler.sendEmptyMessage(TCP_MSG); } catch (Exception e) { Log.d("csl", "接收错误=" + e.getMessage()); } } }
接下来看看语音数据的发送:
首先还是先要录音
View.OnTouchListener touchListener = new View.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (v.getId() == R.id.btn_voice_send ){ switch (event.getAction()) { case MotionEvent.ACTION_DOWN: btn_voice_send.setText("正在说话"); Log.d("csl","正在说话"); try { if (voiceSendSocket == null) { voiceSendSocket = new DatagramSocket(sendPort); } } catch (SocketException e) { e.printStackTrace(); } mStartRecorderTime = System.currentTimeMillis(); createRecordFileName(mStartRecorderTime+""); if (audioRecordThread == null) { audioRecordThread = new RecordThread(); } audioRecordThread.setFlag(true); new Thread(audioRecordThread).start(); break; case MotionEvent.ACTION_UP: btn_voice_send.setText("按住说话"); audioRecordThread.setFlag(false); voiceSendSocket.close(); voiceSendSocket= null; try { SaveRecordFile(); } catch (IOException e) { e.printStackTrace(); } mEndRecorderTime = System.currentTimeMillis(); long recTime = (mEndRecorderTime-mStartRecorderTime)/1000; creatMessageBean(recTime+"s", true,mAudioFile.toString()); new Thread(new Runnable() { @Override public void run() { sendVoiceMsg(mAudioFile.getPath()); } }).start(); break; } } return true; } };public void run() { super.run(); if (voiceSendSocket == null) return; try{ audioRec.startRecording();//开始录音 while (flag) { try { byte[] bytes_pkg = buffer.clone(); if (mRecordQueue.size() >= 2) { int length = audioRec.read(buffer, 0, minBufferSize); mFileOutputStream.write(buffer,0,length); } mRecordQueue.add(bytes_pkg); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }catch (Exception e){ e.printStackTrace(); } }private void sendVoiceMsg(String path){ try { Log.d("csl","sendVoiceMsg"); InetAddress inetAddress = InetAddress.getByName(ClientIp); Socket data = new Socket(inetAddress, sendVoicePort); OutputStream outputData = data.getOutputStream(); FileInputStream fileInput = new FileInputStream(path);//获取录制好的音频文件 int size = -1; byte[] buffer = new byte[1024]; while ((size = fileInput.read(buffer, 0, 1024)) != -1) {//发送音频 outputData.write(buffer, 0, size); } outputData.close(); fileInput.close(); data.close(); Log.d("csl","发送完成"); } catch (Exception e) { e.getMessage(); Log.d("csl","发送异常"); Log.d("csl","e.getMessage()="+e.getMessage()); } }
接收音频文件:
private ServerSocket server = null; String savePath; // 文件接收方法 public String ReceiveVoiceMsg() { Log.d("csl","开始接收"); try { if(server == null) server = new ServerSocket(sendVoicePort); } catch (Exception e) { e.printStackTrace(); Log.d("chenshulin","ServerSocket error" + e.getMessage()); } if (server != null) { while (true) { try { // 接收文件名 Socket name = server.accept(); InputStream nameStream = name.getInputStream(); InputStreamReader streamReader = new InputStreamReader(nameStream); BufferedReader br = new BufferedReader(streamReader); String fileName = br.readLine(); br.close(); streamReader.close(); nameStream.close(); name.close(); // 接收文件数据 Socket data = server.accept(); InputStream dataStream = data.getInputStream(); // File dir = new File("/sdcard/MyMusic"); // 创建文件的存储路径 File dir =new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/UdpPtthDemo/"+System.currentTimeMillis()+".pcm"); // if (!dir.exists()) { //dir.mkdirs(); // } savePath = dir.getPath(); // 定义完整的存储路径 FileOutputStream file = new FileOutputStream(savePath, false); byte[] buffer = new byte[1024*8]; int size = -1; while ((size = dataStream.read(buffer)) != -1) { Log.d("csl","received size = "+size); file.write(buffer, 0, size); } file.close(); dataStream.close(); data.close(); Log.d("chenshulin","接收完成"); // creatMessageBean("收到消息",false,savePath); mHandler.sendEmptyMessage(RECE_VOICE_MSG); return fileName + " 接收完成"; } catch (Exception e) { Log.d("csl","接收错误="+e.getMessage()); return "接收错误:\n" + e.getMessage(); } } } return "接收完成" ; }
播放语音消息。将收到的消息list每个item绑定录音文件名, 点击是播放音频。
void playRecordFile(File audioFile) throws IOException { FileInputStream inputStream=null; inputStream=new FileInputStream(audioFile); int read; audioTrk.play(); byte[] mBuffer = new byte[2048]; while((read=inputStream.read(mBuffer))>0){ int ret=audioTrk.write(mBuffer,0,read); //检查write返回值,错误处理 switch (ret){ case AudioTrack.ERROR_INVALID_OPERATION: case AudioTrack.ERROR_BAD_VALUE: case AudioManager.ERROR_DEAD_OBJECT: // playFail(); return; default: break; } } }
这里参考了别的同仁博客https://blog.csdn.net/stormxiaofeng/article/details/80513947。大家可以看看。