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

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

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

[java]  view plain copy
  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.   }  

如果是采用Socket发送文件,则可以通过OutputStream将ByteArrayInputStream数据流读入,而文件数据流则转换为ByteArrayOutputStream。如果需要在前面添加文字,同样也需要转换为byte,然后写入OutputStream。同样也可以通过定义一个线程类发送文件,如下:

[java]  view plain copy
  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数据,即前面发送文件线程类中所需的文件数据,然后采用线程发送文件,如下代码:

[java]  view plain copy
  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
  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(00, (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中添加代码,具体如下:

[java]  view plain copy
  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
  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(00, (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:[email protected]")  
  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文件,添加服务器地址、端口、用户名等参数设置,如下:

[html]  view plain copy
  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#应用程序项目(项目名称为“手机摄像头”),首先定义一些全局变量,主要包括服务器地址、端口以及相关监听对象等,如下:

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

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

[csharp]  view plain copy
  1. #region 处理客户端传递数据及处理事情  
  2.       ///   
  3.       /// 处理客户端传递数据及处理事情  
  4.       ///   
  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  

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

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

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

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

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

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

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

你可能感兴趣的:(基于Socket的Android手机视频实时传输)