基本过程是android作为socket客户端将采集到的每一帧图像数据发送出去,PC作为服务器接收并显示每一帧图像实现远程监控。图片如下(后来PC端加了个拍照功能)。。。
PS。刚学android和java不久很多东西还不懂,高手若是知道哪些地方可以继续优化的话还请多多指点下啊)
代码如下:
一、android手机客户端
(1)AndroidManifest.xml文件。添加camera和socket权限,并设置了程序开始执行的activity
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="org.wanghai.CameraTest"
4. android:versionCode="1"
5. android:versionName="1.0" >
6.
7. <uses-sdk android:minSdkVersion="15" />
8.
9. <!-- 授予程序使用摄像头的权限 -->
10. <uses-permission android:name="android.permission.CAMERA" />
11. <uses-feature android:name="android.hardware.camera" />
12. <uses-feature android:name="android.hardware.camera.autofocus" />
13. <uses-permission android:name="android.permission.INTERNET"/>
14. <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
15. <uses-permission android:name="android.permission.RESTART_PACKAGES"/>
16.
17. <application
18. android:icon="@drawable/ic_launcher"
19. android:label="@string/app_name" >
20.
21. <activity
22. android:name=".GetIP"
23. android:screenOrientation="landscape"
24. android:label="@string/app_name" >
25. <intent-filter>
26. <action android:name="android.intent.action.MAIN" />
27. <category android:name="android.intent.category.LAUNCHER" />
28. </intent-filter>
29. </activity>
30. <activity
31. android:name=".CameraTest"
32. android:screenOrientation="landscape"
33. android:label="@string/app_name" >
34.
35. </activity>
36.
37. </application>
38.
39. </manifest>
复制代码
(2)main.xml 设置surfaceview用于摄像头采集图像的预览
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:layout_width="fill_parent"
4. android:layout_height="fill_parent"
5. android:orientation="vertical" >
6.
7. <SurfaceView
8. android:id="@+id/sView"
9. android:layout_width="fill_parent"
10. android:layout_height="fill_parent"
11. android:scaleType="fitCenter"/>
12.
13. </LinearLayout>
复制代码
(3)login.xml 登录界面,用于输入服务器IP
1. <?xml version="1.0" encoding="utf-8"?>
2. <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:id="@+id/loginForm"
4. android:orientation="vertical"
5. android:layout_width="fill_parent"
6. android:layout_height="fill_parent"
7. >
8.
9. <TableRow>
10. <TextView
11. android:layout_width="fill_parent"
12. android:layout_height="wrap_content"
13. android:text="IP:"
14. android:textSize="10pt"
15. />
16. <!-- 输入用户名的文本框 -->
17. <EditText
18. android:id="@+id/ipedittext"
19. android:layout_width="fill_parent"
20. android:layout_height="wrap_content"
21. android:digits="0123456789."
22. android:hint="请填写服务器IP"
23. android:selectAllOnFocus="true"
24. />
25. </TableRow>
26.
27. </TableLayout>
复制代码
(4)GetIP.java 获得服务器IP后,通过Intent启动CameraTest的activity,ip信息通过Bundle传递
1. public class GetIP extends Activity {
2. String ipname = null;
3. @Override
4. public void onCreate(Bundle savedInstanceState) {
5. super.onCreate(savedInstanceState);
6. // 设置全屏
7. requestWindowFeature(Window.FEATURE_NO_TITLE);
8. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
9. setContentView(R.layout.main);
10.
11. final Builder builder = new AlertDialog.Builder(this); //定义一个AlertDialog.Builder对象
12. builder.setTitle("登录服务器对话框"); // 设置对话框的标题
13.
14. //装载/res/layout/login.xml界面布局
15. TableLayout loginForm = (TableLayout)getLayoutInflater().inflate( R.layout.login, null);
16. final EditText iptext = (EditText)loginForm.findViewById(R.id.ipedittext);
17. builder.setView(loginForm); // 设置对话框显示的View对象
18. // 为对话框设置一个“登录”按钮
19. builder.setPositiveButton("登录"
20. // 为按钮设置监听器
21. , new OnClickListener() {
22. @Override
23. public void onClick(DialogInterface dialog, int which) {
24. //此处可执行登录处理
25. ipname = iptext.getText().toString().trim();
26. Bundle data = new Bundle();
27. data.putString("ipname",ipname);
28. Intent intent = new Intent(GetIP.this,CameraTest.class);
29. intent.putExtras(data);
30. startActivity(intent);
31. }
32. });
33. // 为对话框设置一个“取消”按钮
34. builder.setNegativeButton("取消"
35. , new OnClickListener()
36. {
37. @Override
38. public void onClick(DialogInterface dialog, int which)
39. {
40. //取消登录,不做任何事情。
41. System.exit(1);
42. }
43. });
44. //创建、并显示对话框
45. builder.create().show();
46. }
47. }
复制代码
(5)CameraTest.java 程序主体。设置PreviewCallback后,每当一帧图像数据采集完成后将调用PreviewCallback的onPreviewFrame函数。在这里我们将YUV格式数据转为jpg,再启用线程将数据通过socket发送出去去
1. public class CameraTest extends Activity {
2. SurfaceView sView;
3. SurfaceHolder surfaceHolder;
4. int screenWidth, screenHeight;
5. Camera camera; // 定义系统所用的照相机
6. boolean isPreview = false; //是否在浏览中
7. private String ipname;
8.
9. @SuppressWarnings("deprecation")
10. @Override
11. public void onCreate(Bundle savedInstanceState) {
12. super.onCreate(savedInstanceState);
13. // 设置全屏
14. requestWindowFeature(Window.FEATURE_NO_TITLE);
15. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
16. setContentView(R.layout.main);
17.
18. // 获取IP地址
19. Intent intent = getIntent();
20. Bundle data = intent.getExtras();
21. ipname = data.getString("ipname");
22.
23. screenWidth = 640;
24. screenHeight = 480;
25. sView = (SurfaceView) findViewById(R.id.sView); // 获取界面中SurfaceView组件
26. surfaceHolder = sView.getHolder(); // 获得SurfaceView的SurfaceHolder
27.
28. // 为surfaceHolder添加一个回调监听器
29. surfaceHolder.addCallback(new Callback() {
30. @Override
31. public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
32. }
33. @Override
34. public void surfaceCreated(SurfaceHolder holder) {
35. initCamera(); // 打开摄像头
36. }
37. @Override
38. public void surfaceDestroyed(SurfaceHolder holder) {
39. // 如果camera不为null ,释放摄像头
40. if (camera != null) {
41. if (isPreview)
42. camera.stopPreview();
43. camera.release();
44. camera = null;
45. }
46. System.exit(0);
47. }
48. });
49. // 设置该SurfaceView自己不维护缓冲
50. surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
51.
52. }
53.
54. private void initCamera() {
55. if (!isPreview) {
56. camera = Camera.open();
57. }
58. if (camera != null && !isPreview) {
59. try{
60. Camera.Parameters parameters = camera.getParameters();
61. parameters.setPreviewSize(screenWidth, screenHeight); // 设置预览照片的大小
62. parameters.setPreviewFpsRange(20,30); // 每秒显示20~30帧
63. parameters.setPictureFormat(ImageFormat.NV21); // 设置图片格式
64. parameters.setPictureSize(screenWidth, screenHeight); // 设置照片的大小
65. //camera.setParameters(parameters); // android2.3.3以后不需要此行代码
66. camera.setPreviewDisplay(surfaceHolder); // 通过SurfaceView显示取景画面
67. camera.setPreviewCallback(new StreamIt(ipname)); // 设置回调的类
68. camera.startPreview(); // 开始预览
69. camera.autoFocus(null); // 自动对焦
70. } catch (Exception e) {
71. e.printStackTrace();
72. }
73. isPreview = true;
74. }
75. }
76.
77. }
78.
79. class StreamIt implements Camera.PreviewCallback {
80. private String ipname;
81. public StreamIt(String ipname){
82. this.ipname = ipname;
83. }
84.
85. @Override
86. public void onPreviewFrame(byte[] data, Camera camera) {
87. Size size = camera.getParameters().getPreviewSize();
88. try{
89. //调用image.compressToJpeg()将YUV格式图像数据data转为jpg格式
90. YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
91. if(image!=null){
92. ByteArrayOutputStream outstream = new ByteArrayOutputStream();
93. image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, outstream);
94. outstream.flush();
95. //启用线程将图像数据发送出去
96. Thread th = new MyThread(outstream,ipname);
97. th.start();
98. }
99. }catch(Exception ex){
100. Log.e("Sys","Error:"+ex.getMessage());
101. }
102. }
103. }
104.
105. class MyThread extends Thread{
106. private byte byteBuffer[] = new byte[1024];
107. private OutputStream outsocket;
108. private ByteArrayOutputStream myoutputstream;
109. private String ipname;
110.
111. public MyThread(ByteArrayOutputStream myoutputstream,String ipname){
112. this.myoutputstream = myoutputstream;
113. this.ipname = ipname;
114. try {
115. myoutputstream.close();
116. } catch (IOException e) {
117. e.printStackTrace();
118. }
119. }
120.
121. public void run() {
122. try{
123. //将图像数据通过Socket发送出去
124. Socket tempSocket = new Socket(ipname, 6000);
125. outsocket = tempSocket.getOutputStream();
126. ByteArrayInputStream inputstream = new ByteArrayInputStream(myoutputstream.toByteArray());
127. int amount;
128. while ((amount = inputstream.read(byteBuffer)) != -1) {
129. outsocket.write(byteBuffer, 0, amount);
130. }
131. myoutputstream.flush();
132. myoutputstream.close();
133. tempSocket.close();
134. } catch (IOException e) {
135. e.printStackTrace();
136. }
137. }
138.
139. }
复制代码
二、PC服务器端
ImageServer.java 用于显示图像,并且可以拍照
1. public class ImageServer {
2. public static ServerSocket ss = null;
3.
4. public static void main(String args[]) throws IOException{
5. ss = new ServerSocket(6000);
6.
7. final ImageFrame frame = new ImageFrame(ss);
8. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
9. frame.setVisible(true);
10.
11. while(true){
12. frame.panel.getimage();
13. frame.repaint();
14. }
15. }
16.
17. }
18.
19. /**
20. A frame with an image panel
21. */
22. @SuppressWarnings("serial")
23. class ImageFrame extends JFrame{
24. public ImagePanel panel;
25. public JButton jb;
26.
27. public ImageFrame(ServerSocket ss){
28. // get screen dimensions
29. Toolkit kit = Toolkit.getDefaultToolkit();
30. Dimension screenSize = kit.getScreenSize();
31. int screenHeight = screenSize.height;
32. int screenWidth = screenSize.width;
33.
34. // center frame in screen
35. setTitle("ImageTest");
36. setLocation((screenWidth - DEFAULT_WIDTH) / 2, (screenHeight - DEFAULT_HEIGHT) / 2);
37. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
38.
39. // add panel to frame
40. this.getContentPane().setLayout(null);
41. panel = new ImagePanel(ss);
42. panel.setSize(640,480);
43. panel.setLocation(0, 0);
44. add(panel);
45. jb = new JButton("拍照");
46. jb.setBounds(0,480,640,50);
47. add(jb);
48. saveimage saveaction = new saveimage(ss);
49. jb.addActionListener(saveaction);
50. }
51.
52. public static final int DEFAULT_WIDTH = 640;
53. public static final int DEFAULT_HEIGHT = 560;
54. }
55.
56. /**
57. A panel that displays a tiled image
58. */
59. @SuppressWarnings("serial")
60. class ImagePanel extends JPanel {
61. private ServerSocket ss;
62. private Image image;
63. private InputStream ins;
64.
65. public ImagePanel(ServerSocket ss) {
66. this.ss = ss;
67. }
68.
69. public void getimage() throws IOException{
70. Socket s = this.ss.accept();
71. System.out.println("连接成功!");
72. this.ins = s.getInputStream();
73. this.image = ImageIO.read(ins);
74. this.ins.close();
75. }
76.
77. public void paintComponent(Graphics g){
78. super.paintComponent(g);
79. if (image == null) return;
80. g.drawImage(image, 0, 0, null);
81. }
82.
83. }
84.
85. class saveimage implements ActionListener {
86. RandomAccessFile inFile = null;
87. byte byteBuffer[] = new byte[1024];
88. InputStream ins;
89. private ServerSocket ss;
90.
91. public saveimage(ServerSocket ss){
92. this.ss = ss;
93. }
94.
95. public void actionPerformed(ActionEvent event){
96. try {
97. Socket s = ss.accept();
98. ins = s.getInputStream();
99.
100. // 文件选择器以当前的目录打开
101. JFileChooser jfc = new JFileChooser(".");
102. jfc.showSaveDialog(new javax.swing.JFrame());
103. // 获取当前的选择文件引用
104. File savedFile = jfc.getSelectedFile();
105.
106. // 已经选择了文件
107. if (savedFile != null) {
108. // 读取文件的数据,可以每次以快的方式读取数据
109. try {
110. inFile = new RandomAccessFile(savedFile, "rw");
111. } catch (FileNotFoundException e) {
112. e.printStackTrace();
113. }
114. }
115.
116. int amount;
117. while ((amount = ins.read(byteBuffer)) != -1) {
118. inFile.write(byteBuffer, 0, amount);
119. }
120. inFile.close();
121. ins.close();
122. s.close();
123. javax.swing.JOptionPane.showMessageDialog(new javax.swing.JFrame(),
124. "已接保存成功", "提示!", javax.swing.JOptionPane.PLAIN_MESSAGE);
125. } catch (IOException e) {
126.
127. e.printStackTrace();
128. }
129. }
130. }
复制代码
开放源码如下(android我使用的是4.03的SDK,其它版本请自行更改。2.3.3版本以下的请注意initCamera()里被注释掉的哪一行)