基本过程是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()里被注释掉的哪一行)