首先,简单介绍一下原理。主要是在手机客户端(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:[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文件,添加服务器地址、端口、用户名等参数设置,如下:
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
关于开启服务监听、刷新用户列表、获取手机视频窗体、删除用户、寻找用户序号等代码在此就不详细介绍,具体参见源代码。
基于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