基于Socket的Android手机视频实时传输

首先,简单介绍一下原理。主要是在手机客户端(Android)通过实现Camera.PreviewCallback接口,在其onPreviewFrame重载函数里面获取摄像头当前图像数据,然后通过Socket将图像数据和相关的用户名、命令等数据传输到服务器程序中。服务器端(PC端)采用C#编写,通过监听相应的端口,在获取数据后进行相应的命令解析和图像数据还原,然后将图像数据传递至PictureBox控件中用于显示,这样就实现了手机摄像头的视频数据实时传输到服务器上。如果需要将这些视频进行转发,通过服务器再将这些数据复制转发即可。效果如下:

对于Android客户端上主要有几个地方需要注意,第一个就是Socket通信。Socket通信可以通过Socket类来实现,直接结合PrintWriter来写入命令,如下定义的一个专门用于发送命令的线程类,当要连接到服务器和与服务器断开时,都需要发送命令通知服务器,此外在进行其他文字传输时也可以采用该方法,具体代码如下:

1. /**发送命令线程*/  
2.   class MySendCommondThread extends Thread{  
3.     private String commond;  
4.     public MySendCommondThread(String commond){  
5.         this.commond=commond;  
6.     }  
7.     public void run(){  
8.         //实例化Socket    
9.           try {  
10.            Socket socket=new Socket(serverUrl,serverPort);  
11.            PrintWriter out = new PrintWriter(socket.getOutputStream());  
12.            out.println(commond);  
13.            out.flush();  
14.        } catch (UnknownHostException e) {  
15.        } catch (IOException e) {  
16.        }    
17.    }  
18.  }  
</pre></div><p><span style="color:#333333">如果是采用</span><span style="color:#333333">Socket</span><span style="color:#333333">发送文件,则可以通过</span><span style="color:#333333">OutputStream</span><span style="color:#333333">将</span><span style="color:#333333">ByteArrayInputStream</span><span style="color:#333333">数据流读入,而文件数据流则转换为</span><span style="color:#333333">ByteArrayOutputStream</span><span style="color:#333333">。如果需要在前面添加文字,同样也需要转换为</span><span style="color:#333333">byte</span><span style="color:#333333">,然后写入</span><span style="color:#333333">OutputStream</span><span style="color:#333333">。同样也可以通过定义一个线程类发送文件,如下:</span></p><div><p><span style="color:#5C5C5C"></span></p><pre name="code" class="java">1. /**发送文件线程*/  
2.    class MySendFileThread extends Thread{     
3.     private String username;  
4.     private String ipname;  
5.     private int port;  
6.     private byte byteBuffer[] = new byte[1024];  
7.     private OutputStream outsocket;   
8.     private ByteArrayOutputStream myoutputstream;  
9.       
10.    public MySendFileThread(ByteArrayOutputStream myoutputstream,String username,String ipname,int port){  
11.        this.myoutputstream = myoutputstream;  
12.        this.username=username;  
13.        this.ipname = ipname;  
14.        this.port=port;  
15.           try {  
16.            myoutputstream.close();  
17.        } catch (IOException e) {  
18.            e.printStackTrace();  
19.        }  
20.    }  
21.      
22.       public void run() {  
23.           try{  
24.            //将图像数据通过Socket发送出去  
25.               Socket tempSocket = new Socket(ipname, port);  
26.               outsocket = tempSocket.getOutputStream();  
27.               //写入头部数据信息  
28.            String msg=java.net.URLEncoder.encode("PHONEVIDEO|"+username+"|","utf-8");  
29.               byte[] buffer= msg.getBytes();  
30.               outsocket.write(buffer);  
31.                 
32.               ByteArrayInputStream inputstream = new ByteArrayInputStream(myoutputstream.toByteArray());  
33.               int amount;  
34.               while ((amount = inputstream.read(byteBuffer)) != -1) {  
35.                   outsocket.write(byteBuffer, 0, amount);  
36.               }  
37.               myoutputstream.flush();  
38.               myoutputstream.close();  
39.               tempSocket.close();                     
40.           } catch (IOException e) {  
41.               e.printStackTrace();  
42.           }  
43.       }  
44.   }  


而获取摄像头当前图像的关键在于onPreviewFrame()重载函数里面,该函数里面有两个参数,第一个参数为byte[],为摄像头当前图像数据,通过YuvImage可以将该数据转换为图片文件,同时还可用对该图片进行压缩和裁剪,将图片进行压缩转换后转换为    ByteArrayOutputStream数据,即前面发送文件线程类中所需的文件数据,然后采用线程发送文件,如下代码:

1. @Override  
2. public void onPreviewFrame(byte[] data, Camera camera) {  
3.     // TODO Auto-generated method stub  
4.     //如果没有指令传输视频,就先不传  
5.     if(!startSendVideo)  
6.         return;  
7.     if(tempPreRate<VideoPreRate){  
8.         tempPreRate++;  
9.         return;  
10.    }  
11.    tempPreRate=0;        
12.    try {  
13.          if(data!=null)  
14.          {  
15.            YuvImage image = new YuvImage(data,VideoFormatIndex, VideoWidth, VideoHeight,null);  
16.            if(image!=null)  
17.            {  
18.                ByteArrayOutputStream outstream = new ByteArrayOutputStream();  
19.                //在此设置图片的尺寸和质量   
20.                image.compressToJpeg(new Rect(0, 0, (int)(VideoWidthRatio*VideoWidth),   
21.                    (int)(VideoHeightRatio*VideoHeight)), VideoQuality, outstream);    
22.                outstream.flush();  
23.                //启用线程将图像数据发送出去  
24.                Thread th = new MySendFileThread(outstream,pUsername,serverUrl,serverPort);  
25.                th.start();    
26.            }  
27.          }  
28.      } catch (IOException e) {  
29.          e.printStackTrace();  
30.      }  
31.}  


值得注意的是,在调试中YuvImage可能找不到,在模拟机上无法执行该过程,但是编译后在真机中可以通过。此外,以上传输文字字符都是采用UTF编码,在服务器端接收时进行解析时需要采用对应的编码进行解析,否则可能会出现错误解析。

Android客户端中关键的部分主要就这些,新建一个Android项目(项目名称为SocketCamera),在main布局中添加一个SurfaceView和两个按钮,如下图所示:

然后在SocketCameraActivity.java中添加代码,具体如下:

1. package com.xzy;  
2.   
3. import java.io.ByteArrayInputStream;  
4. import java.io.ByteArrayOutputStream;  
5. import java.io.IOException;  
6. import java.io.OutputStream;  
7. import java.io.PrintWriter;  
8. import java.net.Socket;  
9. import java.net.UnknownHostException;  
10.import android.app.Activity;  
11.import android.app.AlertDialog;  
12.import android.content.DialogInterface;  
13.import android.content.Intent;  
14.import android.content.SharedPreferences;  
15.import android.graphics.Rect;  
16.import android.graphics.YuvImage;  
17.import android.hardware.Camera;  
18.import android.hardware.Camera.Size;  
19.import android.os.Bundle;  
20.import android.preference.PreferenceManager;  
21.import android.view.Menu;  
22.import android.view.MenuItem;  
23.import android.view.SurfaceHolder;  
24.import android.view.SurfaceView;  
25.import android.view.View;  
26.import android.view.WindowManager;  
27.import android.view.View.OnClickListener;  
28.import android.widget.Button;  
29.  
30.public class SocketCameraActivity extends Activity implements SurfaceHolder.Callback,  
31.Camera.PreviewCallback{       
32.    private SurfaceView mSurfaceview = null; // SurfaceView对象:(视图组件)视频显示  
33.    private SurfaceHolder mSurfaceHolder = null; // SurfaceHolder对象:(抽象接口)SurfaceView支持类  
34.    private Camera mCamera = null; // Camera对象,相机预览   
35.      
36.    /**服务器地址*/  
37.    private String pUsername="XZY";  
38.    /**服务器地址*/  
39.    private String serverUrl="192.168.1.100";  
40.    /**服务器端口*/  
41.    private int serverPort=8888;  
42.    /**视频刷新间隔*/  
43.    private int VideoPreRate=1;  
44.    /**当前视频序号*/  
45.    private int tempPreRate=0;  
46.    /**视频质量*/  
47.    private int VideoQuality=85;  
48.      
49.    /**发送视频宽度比例*/  
50.    private float VideoWidthRatio=1;  
51.    /**发送视频高度比例*/  
52.    private float VideoHeightRatio=1;  
53.      
54.    /**发送视频宽度*/  
55.    private int VideoWidth=320;  
56.    /**发送视频高度*/  
57.    private int VideoHeight=240;  
58.    /**视频格式索引*/  
59.    private int VideoFormatIndex=0;  
60.    /**是否发送视频*/  
61.    private boolean startSendVideo=false;  
62.    /**是否连接主机*/  
63.    private boolean connectedServer=false;  
64.      
65.    private Button myBtn01, myBtn02;  
66.    /** Called when the activity is first created. */  
67.    @Override  
68.    public void onCreate(Bundle savedInstanceState) {  
69.        super.onCreate(savedInstanceState);  
70.        setContentView(R.layout.main);  
71.          
72.        //禁止屏幕休眠        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,   
73.                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  
74.                 
75.        mSurfaceview = (SurfaceView) findViewById(R.id.camera_preview);  
76.        myBtn01=(Button)findViewById(R.id.button1);  
77.        myBtn02=(Button)findViewById(R.id.button2);  
78.                  
79.        //开始连接主机按钮  
80.        myBtn01.setOnClickListener(new OnClickListener(){  
81.            public void onClick(View v) {  
82.                //Common.SetGPSConnected(LoginActivity.this, false);  
83.                if(connectedServer){//停止连接主机,同时断开传输  
84.                    startSendVideo=false;  
85.                    connectedServer=false;                    
86.                    myBtn02.setEnabled(false);  
87.                    myBtn01.setText("开始连接");  
88.                    myBtn02.setText("开始传输");  
89.                    //断开连接  
90.                    Thread th = new MySendCommondThread("PHONEDISCONNECT|"+pUsername+"|");  
91.                    th.start();   
92.                }  
93.                else//连接主机  
94.                {  
95.                    //启用线程发送命令PHONECONNECT  
96.                    Thread th = new MySendCommondThread("PHONECONNECT|"+pUsername+"|");  
97.                    th.start();   
98.                    connectedServer=true;  
99.                    myBtn02.setEnabled(true);  
100.                    myBtn01.setText("停止连接");  
101.                }  
102.            }});  
103.          
104.        myBtn02.setEnabled(false);  
105.        myBtn02.setOnClickListener(new OnClickListener(){  
106.            public void onClick(View v) {  
107.                if(startSendVideo)//停止传输视频  
108.                {  
109.                    startSendVideo=false;  
110.                    myBtn02.setText("开始传输");  
111.                }  
112.                else{ // 开始传输视频  
113.                    startSendVideo=true;  
114.                    myBtn02.setText("停止传输");  
115.                }  
116.            }});  
117.    }  
118.      
119.    @Override  
120.    public void onStart()//重新启动的时候  
121.    {     
122.        mSurfaceHolder = mSurfaceview.getHolder(); // 绑定SurfaceView,取得SurfaceHolder对象  
123.        mSurfaceHolder.addCallback(this); // SurfaceHolder加入回调接口         
124.        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// 设置显示器类型,setType必须设置  
125.        //读取配置文件  
126.        SharedPreferences preParas = PreferenceManager.getDefaultSharedPreferences(SocketCameraActivity.this);  
127.        pUsername=preParas.getString("Username", "XZY");  
128.        serverUrl=preParas.getString("ServerUrl", "192.168.0.100");  
129.        String tempStr=preParas.getString("ServerPort", "8888");  
130.        serverPort=Integer.parseInt(tempStr);  
131.        tempStr=preParas.getString("VideoPreRate", "1");  
132.        VideoPreRate=Integer.parseInt(tempStr);               
133.        tempStr=preParas.getString("VideoQuality", "85");  
134.        VideoQuality=Integer.parseInt(tempStr);  
135.        tempStr=preParas.getString("VideoWidthRatio", "100");  
136.        VideoWidthRatio=Integer.parseInt(tempStr);  
137.        tempStr=preParas.getString("VideoHeightRatio", "100");  
138.        VideoHeightRatio=Integer.parseInt(tempStr);  
139.        VideoWidthRatio=VideoWidthRatio/100f;  
140.        VideoHeightRatio=VideoHeightRatio/100f;  
141.          
142.        super.onStart();  
143.    }  
144.      
145.    @Override  
146.    protected void onResume() {  
147.        super.onResume();          
148.        InitCamera();  
149.    }  
150.      
151.    /**初始化摄像头*/  
152.    private void InitCamera(){  
153.        try{  
154.            mCamera = Camera.open();  
155.        } catch (Exception e) {  
156.            e.printStackTrace();  
157.        }  
158.    }  
159.   
160.    @Override  
161.    protected void onPause() {  
162.        super.onPause();  
163.        try{  
164.            if (mCamera != null) {  
165.                mCamera.setPreviewCallback(null); // !!这个必须在前,不然退出出错  
166.                mCamera.stopPreview();  
167.                mCamera.release();  
168.                mCamera = null;  
169.            }   
170.        } catch (Exception e) {  
171.            e.printStackTrace();  
172.        }   
173.    }  
174.    @Override  
175.    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {  
176.        // TODO Auto-generated method stub  
177.        if (mCamera == null) {  
178.            return;  
179.        }  
180.        mCamera.stopPreview();  
181.        mCamera.setPreviewCallback(this);  
182.        mCamera.setDisplayOrientation(90); //设置横行录制  
183.        //获取摄像头参数  
184.        Camera.Parameters parameters = mCamera.getParameters();  
185.        Size size = parameters.getPreviewSize();  
186.        VideoWidth=size.width;  
187.        VideoHeight=size.height;  
188.        VideoFormatIndex=parameters.getPreviewFormat();  
189.          
190.        mCamera.startPreview();  
191.    }  
192.  
193.    @Override  
194.    public void surfaceCreated(SurfaceHolder holder) {  
195.        // TODO Auto-generated method stub  
196.        try {  
197.            if (mCamera != null) {  
198.                mCamera.setPreviewDisplay(mSurfaceHolder);  
199.                mCamera.startPreview();  
200.            }  
201.        } catch (IOException e) {  
202.            e.printStackTrace();  
203.        }   
204.    }  
205.  
206.    @Override  
207.    public void surfaceDestroyed(SurfaceHolder holder) {  
208.        // TODO Auto-generated method stub  
209.        if (null != mCamera) {  
210.            mCamera.setPreviewCallback(null); // !!这个必须在前,不然退出出错  
211.            mCamera.stopPreview();  
212.            mCamera.release();  
213.            mCamera = null;  
214.        }  
215.    }  
216.  
217.    @Override  
218.    public void onPreviewFrame(byte[] data, Camera camera) {  
219.        // TODO Auto-generated method stub  
220.        //如果没有指令传输视频,就先不传  
221.        if(!startSendVideo)  
222.            return;  
223.        if(tempPreRate<VideoPreRate){  
224.            tempPreRate++;  
225.            return;  
226.        }  
227.        tempPreRate=0;        
228.        try {  
229.              if(data!=null)  
230.              {  
231.                YuvImage image = new YuvImage(data,VideoFormatIndex, VideoWidth, VideoHeight,null);  
232.                if(image!=null)  
233.                {  
234.                    ByteArrayOutputStream outstream = new ByteArrayOutputStream();  
235.                    //在此设置图片的尺寸和质量   
236.                    image.compressToJpeg(new Rect(0, 0, (int)(VideoWidthRatio*VideoWidth),   
237.                        (int)(VideoHeightRatio*VideoHeight)), VideoQuality, outstream);    
238.                    outstream.flush();  
239.                    //启用线程将图像数据发送出去  
240.                    Thread th = new MySendFileThread(outstream,pUsername,serverUrl,serverPort);  
241.                    th.start();    
242.                }  
243.              }  
244.          } catch (IOException e) {  
245.              e.printStackTrace();  
246.          }  
247.    }  
248.          
249.    /**创建菜单*/      
250.    public boolean onCreateOptionsMenu(Menu menu)  
251.    {  
252.        menu.add(0,0,0,"系统设置");  
253.        menu.add(0,1,1,"关于程序");   
254.        menu.add(0,2,2,"退出程序");   
255.        return super.onCreateOptionsMenu(menu);  
256.    }  
257.    /**菜单选中时发生的相应事件*/    
258.    public boolean onOptionsItemSelected(MenuItem item)  
259.    {  
260.        super.onOptionsItemSelected(item);//获取菜单  
261.        switch(item.getItemId())//菜单序号  
262.        {  
263.            case 0:  
264.                //系统设置  
265.                {  
266.                    Intent intent=new Intent(this,SettingActivity.class);  
267.                    startActivity(intent);    
268.                }  
269.                break;    
270.            case 1://关于程序  
271.            {  
272.                new AlertDialog.Builder(this)  
273.                .setTitle("关于本程序")  
274.                .setMessage("本程序由武汉大学水利水电学院肖泽云设计、编写。\nEmail:xwebsite@163.com")  
275.                .setPositiveButton  
276.                (  
277.                    "我知道了",  
278.                    new DialogInterface.OnClickListener()  
279.                    {                         
280.                        @Override  
281.                        public void onClick(DialogInterface dialog, int which)   
282.                        {  
283.                        }  
284.                    }  
285.                )  
286.                .show();  
287.            }  
288.            break;  
289.            case 2://退出程序  
290.                {  
291.                    //杀掉线程强制退出  
292.                    android.os.Process.killProcess(android.os.Process.myPid());  
293.                }  
294.                break;  
295.        }         
296.        return true;  
297.    }  
298.      
299.    /**发送命令线程*/  
300.    class MySendCommondThread extends Thread{  
301.        private String commond;  
302.        public MySendCommondThread(String commond){  
303.            this.commond=commond;  
304.        }  
305.        public void run(){  
306.            //实例化Socket    
307.            try {  
308.                Socket socket=new Socket(serverUrl,serverPort);  
309.                PrintWriter out = new PrintWriter(socket.getOutputStream());  
310.                out.println(commond);  
311.                out.flush();  
312.            } catch (UnknownHostException e) {  
313.            } catch (IOException e) {  
314.            }    
315.        }  
316.    }  
317.      
318.    /**发送文件线程*/  
319.    class MySendFileThread extends Thread{    
320.        private String username;  
321.        private String ipname;  
322.        private int port;  
323.        private byte byteBuffer[] = new byte[1024];  
324.        private OutputStream outsocket;   
325.        private ByteArrayOutputStream myoutputstream;  
326.          
327.        public MySendFileThread(ByteArrayOutputStream myoutputstream,String username,String ipname,int port){  
328.            this.myoutputstream = myoutputstream;  
329.            this.username=username;  
330.            this.ipname = ipname;  
331.            this.port=port;  
332.            try {  
333.                myoutputstream.close();  
334.            } catch (IOException e) {  
335.                e.printStackTrace();  
336.            }  
337.        }  
338.          
339.        public void run() {  
340.            try{  
341.                //将图像数据通过Socket发送出去  
342.                Socket tempSocket = new Socket(ipname, port);  
343.                outsocket = tempSocket.getOutputStream();  
344.                //写入头部数据信息  
345.                String msg=java.net.URLEncoder.encode("PHONEVIDEO|"+username+"|","utf-8");  
346.                byte[] buffer= msg.getBytes();  
347.                outsocket.write(buffer);  
348.                  
349.                ByteArrayInputStream inputstream = new ByteArrayInputStream(myoutputstream.toByteArray());  
350.                int amount;  
351.                while ((amount = inputstream.read(byteBuffer)) != -1) {  
352.                    outsocket.write(byteBuffer, 0, amount);  
353.                }  
354.                myoutputstream.flush();  
355.                myoutputstream.close();  
356.                tempSocket.close();      
357.            } catch (IOException e) {  
358.                e.printStackTrace();  
359.            }  
360.        }  
361.    }  
362.}  


此外还有一些参数,在res/xml新建一个setting.xml文件,添加服务器地址、端口、用户名等参数设置,如下:

1. <?xml version="1.0" encoding="utf-8"?>  
2. <PreferenceScreen  
3.   xmlns:android="http://schemas.android.com/apk/res/android">  
4.  <PreferenceCategory android:title="服务器设置">  
5.     <EditTextPreference  
6.    android:key="Username"  
7.    android:title="用户名"      
8.    android:summary="用于连接服务器的用户名"  
9.    android:defaultValue="XZY"/>  
10.   <EditTextPreference  
11.   android:key="ServerUrl"  
12.   android:title="视频服务器地址"      
13.   android:summary="保存服务器地址"  
14.   android:defaultValue="192.168.1.100"/>  
15.<EditTextPreference  
16.   android:key="ServerPort"  
17.   android:title="服务器端口"      
18.   android:summary="连接服务器的端口地址"  
19.   android:defaultValue="8888"/>  
20.   </PreferenceCategory>  
21.<PreferenceCategory android:title="视频设置">  
22.    <EditTextPreference  
23.   android:key="VideoPreRate"  
24.   android:title="视频刷新间隔"      
25.   android:summary="设置视频刷新的间隔值,应大于等于0,值越大视频传输间隔越长"  
26.   android:defaultValue="1"/>  
27.<EditTextPreference  
28.   android:key="VideoQuality"  
29.   android:title="图像质量"      
30.   android:summary="设置图像压缩的质量,值为0~100,值越高越清晰,但同时数据也更大"  
31.   android:defaultValue="85"/>  
32.<EditTextPreference  
33.   android:key="VideoWidthRatio"  
34.   android:title="图像宽度缩放比例"      
35.   android:summary="设置图像的宽度缩放比例,值为0~100,值越高图像分辨率越高"  
36.   android:defaultValue="100"/>  
37.   <EditTextPreference  
38.   android:key="VideoHeightRatio"  
39.   android:title="图像高度缩放比例"      
40.   android:summary="设置图像的高度缩放比例,值为0~100,值越高图像分辨率越高"  
41.   android:defaultValue="100"/>  
42.    </PreferenceCategory>  
43.</PreferenceScreen>  


编译程序,在模拟机上效果如下:

接下来就是服务器端接收手机传输的视频数据,这与一般CS架构中服务器程序类似,主要是监听端口,然后解析数据。现新建一个C#应用程序项目(项目名称为手机摄像头),首先定义一些全局变量,主要包括服务器地址、端口以及相关监听对象等,如下:

1. /// <summary>  
2.         /// 服务器状态,如果为false表示服务器暂停,true表示服务器开启  
3.         /// </summary>  
4.         public bool ServerStatus = false;  
5.         /// <summary>  
6.         /// 服务器地址  
7.         /// </summary>  
8.         public string ServerAddress;  
9.         /// <summary>  
10.        /// 服务器端口  
11.        /// </summary>  
12.        public int ServerPort;  
13.        /// <summary>  
14.        /// 开启服务的线程  
15.        /// </summary>  
16.        private Thread processor;  
17.        /// <summary>  
18.        /// 用于TCP监听  
19.        /// </summary>  
20.        private TcpListener tcpListener;  
21.        /// <summary>  
22.        /// 与客户端连接的套接字接口  
23.        /// </summary>  
24.        private Socket clientSocket;  
25.        /// <summary>  
26.        /// 用于处理客户事件的线程  
27.        /// </summary>  
28.        private Thread clientThread;  
29.        /// <summary>  
30.        /// 手机客户端所有客户端的套接字接口  
31.        /// </summary>  
32.        private Hashtable PhoneClientSockets = new Hashtable();  
33.        /// <summary>  
34.        /// 手机用户类数组  
35.        /// </summary>  
36.        public ArrayList PhoneUsersArray = new ArrayList();  
37.        /// <summary>  
38.        /// 手机用户名数组  
39.        /// </summary>  
40.        public ArrayList PhoneUserNamesArray = new ArrayList();  
41.        /// <summary>  
42.        /// 图像数据流  
43.        /// </summary>  
44.        private ArrayList StreamArray;  


然后定义处理客户端传递数据的函数ProcessClient(),主要对接收数据进行命令解析。如果是手机连接的命令("PHONECONNECT"),就在记录该套接字对象,同时在列表中添加该对象;如果是断开连接的命令("PHONEDISCONNECT",就移除该对象;如果是手机视频命令("PHONEVIDEO"),就分解其包含的图像数据,如果存在该用户对应的视频窗口,就传递该图像数据到这个视频窗口中。具体代码如下:

1. #region 处理客户端传递数据及处理事情  
2.       /// <summary>  
3.       /// 处理客户端传递数据及处理事情  
4.       /// </summary>  
5.       private void ProcessClient()  
6.       {  
7.           Socket client = clientSocket;  
8.           bool keepalive = true;  
9.           while (keepalive)  
10.          {  
11.              Thread.Sleep(50);  
12.              Byte[] buffer = null;  
13.              bool tag = false;  
14.              try  
15.              {  
16.                  buffer = new Byte[1024];//client.Available  
17.                  int count = client.Receive(buffer, SocketFlags.None);//接收客户端套接字数据  
18.                  if (count > 0)//接收到数据  
19.                      tag = true;  
20.              }  
21.              catch (Exception e)  
22.              {  
23.                  keepalive = false;  
24.                  if (client.Connected)  
25.                      client.Disconnect(true);  
26.                  client.Close();  
27.              }  
28.              if (!tag)  
29.              {  
30.                  if (client.Connected)  
31.                      client.Disconnect(true);  
32.                  client.Close();  
33.                  keepalive = false;  
34.              }  
35.  
36.              string clientCommand = "";  
37.              try  
38.              {  
39.                  clientCommand = System.Text.Encoding.UTF8.GetString(buffer);//转换接收的数据,数据来源于客户端发送的消息  
40.                  if (clientCommand.Contains("%7C"))//从Android客户端传递部分数据  
41.                      clientCommand = clientCommand.Replace("%7C", "|");//替换UTF中字符%7C为|  
42.              }  
43.              catch  
44.              {  
45.              }  
46.              //分析客户端传递的命令来判断各种操作  
47.              string[] messages = clientCommand.Split('|');  
48.              if (messages != null && messages.Length > 0)  
49.              {  
50.                  string tempStr = messages[0];//第一个字符串为命令  
51.                  if (tempStr == "PHONECONNECT")//手机连接服务器  
52.                  {  
53.                      try  
54.                      {  
55.                          string tempClientName = messages[1].Trim();  
56.                          PhoneClientSockets.Remove(messages[1]);//删除之前与该用户的连接  
57.                          PhoneClientSockets.Add(messages[1], client);//建立与该客户端的Socket连接                          
58.  
59.                          UserClass tempUser = new UserClass();  
60.                          tempUser.UserName = tempClientName;  
61.                          tempUser.LoginTime = DateTime.Now;  
62.                          Socket tempSocket = (Socket)PhoneClientSockets[tempClientName];  
63.                          tempUser.IPAddress = tempSocket.RemoteEndPoint.ToString();  
64.  
65.                          int tempIndex = PhoneUserNamesArray.IndexOf(tempClientName);  
66.                          if (tempIndex >= 0)  
67.                          {  
68.                              PhoneUserNamesArray[tempIndex] = tempClientName;  
69.                              PhoneUsersArray[tempIndex] = tempUser;  
70.                              MemoryStream stream2 = (MemoryStream)StreamArray[tempIndex];  
71.                              if (stream2 != null)  
72.                              {  
73.                                  stream2.Close();  
74.                                  stream2.Dispose();  
75.                              }  
76.                          }  
77.                          else//新增加  
78.                          {  
79.                              PhoneUserNamesArray.Add(tempClientName);  
80.                              PhoneUsersArray.Add(tempUser);  
81.                              StreamArray.Add(null);  
82.                          }  
83.                          RefreshPhoneUsers();  
84.                      }  
85.                      catch (Exception except)  
86.                      {  
87.                      }  
88.                  }  
89.                  else if (tempStr == "PHONEDISCONNECT")//某个客户端退出了  
90.                  {  
91.                      try  
92.                      {  
93.                          string tempClientName = messages[1];  
94.                          RemovePhoneUser(tempClientName);  
95.  
96.                          int tempPhoneIndex = PhoneUserNamesArray.IndexOf(tempClientName);  
97.                          if (tempPhoneIndex >= 0)  
98.                          {  
99.                              PhoneUserNamesArray.RemoveAt(tempPhoneIndex);  
100.                              MemoryStream memStream = (MemoryStream)StreamArray[tempPhoneIndex];  
101.                              if (memStream != null)  
102.                              {  
103.                                  memStream.Close();  
104.                                  memStream.Dispose();  
105.                              }  
106.                              StreamArray.RemoveAt(tempPhoneIndex);  
107.                          }  
108.                          Socket tempSocket = (Socket)PhoneClientSockets[tempClientName];//第1个为客户端的ID,找到该套接字  
109.                          if (tempSocket != null)  
110.                          {  
111.                              tempSocket.Close();  
112.                              PhoneClientSockets.Remove(tempClientName);  
113.                          }  
114.                          keepalive = false;  
115.                      }  
116.                      catch (Exception except)  
117.                      {  
118.                      }  
119.                      RefreshPhoneUsers();  
120.                  }  
121.                  else if (tempStr == "PHONEVIDEO")//接收手机数据流  
122.                  {  
123.                      try  
124.                      {  
125.                          string tempClientName = messages[1];  
126.                          string tempForeStr = messages[0] + "%7C" + messages[1] + "%7C";  
127.                          int startCount = System.Text.Encoding.UTF8.GetByteCount(tempForeStr);  
128.                          try  
129.                          {  
130.                              MemoryStream stream = new MemoryStream();  
131.                              if (stream.CanWrite)  
132.                              {  
133.                                  stream.Write(buffer, startCount, buffer.Length - startCount);  
134.                                  int len = -1;  
135.                                  while ((len = client.Receive(buffer)) > 0)  
136.                                  {  
137.                                      stream.Write(buffer, 0, len);  
138.                                  }  
139.                              }  
140.                              stream.Flush();  
141.  
142.                              int tempPhoneIndex = PhoneUserNamesArray.IndexOf(tempClientName);  
143.                              if (tempPhoneIndex >= 0)  
144.                              {  
145.                                  MemoryStream stream2 = (MemoryStream)StreamArray[tempPhoneIndex];  
146.                                  if (stream2 != null)  
147.                                  {  
148.                                      stream2.Close();  
149.                                      stream2.Dispose();  
150.                                  }  
151.                                  StreamArray[tempPhoneIndex] = stream;  
152.  
153.                                  PhoneVideoForm form = GetPhoneVideoForm(tempClientName);  
154.                                  if (form != null)  
155.                                      form.DataStream = stream;  
156.                              }  
157.                          }  
158.                          catch  
159.                          {  
160.                          }  
161.                      }  
162.                      catch (Exception except)  
163.                      {  
164.                      }  
165.                  }  
166.              }  
167.              else//客户端发送的命令或字符串为空,结束连接  
168.              {  
169.                  try  
170.                  {  
171.                      client.Close();  
172.                      keepalive = false;  
173.                  }  
174.                  catch  
175.                  {  
176.                      keepalive = false;  
177.                  }  
178.              }  
179.          }  
180.      }  
181.      #endregion  


关于开启服务监听、刷新用户列表、获取手机视频窗体、删除用户、寻找用户序号等代码在此就不详细介绍,具体参见源代码。

基于SocketAndroid手机视频实时传输手机客户端下载地址:

http://download.csdn.net/detail/xwebsite/4973592

基于SocketAndroid手机视频实时传输服务器端下载地址:

http://download.csdn.net/detail/xwebsite/4973601

基于SocketAndroid手机视频实时传输所有源程序下载地址:

http://download.csdn.net/detail/xwebsite/4973613

 

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值