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