手机端操作平板跟或者是TV里的摄像头实现远程拍照。



有java中大家熟悉的网络数据传输通信,在android也同样实现网络通信,看下图最原始的server和client接口图:


 


图一


上图可以看出他们之间通信通过接口方式来达到目的,而且可以同时多个client与server连接建立;


手机操作远程的平板摄像头实现照相功能,实现方式各式各样,这里头从简单java中实现网络传输控制;


从上图可以得出:


首先服务器方要先启动,并根据请求提供相应服务:


1.       打开一通信通道并告知本地主机,它愿意在某一公认端口上,如某服务端口为8888,接收客户请求; 
2. 等待客户请求到达该端口; 
3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。 
4. 返回第二步,等待另一客户请求。 
5. 关闭服务器;


2.       Client端:


1.       打开一通信通道,并连接到服务器所在主机的特定端口; 
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求...... 
3. 请求结束后关闭通信通道并终止。


接下来就创建相应的类;


部分:


public class SocketClient {
    private Socket client = null;
    private PrintStream out = null;
    private BufferedReader inbuf = null;
    private InputStream ins;
    private Handler handler;
    private int status = NetWorkState.OFFLINE;
    private ReadThread readThread;
    public byte[] yuv420sp = null;
    public SocketClient(Handler handler) {
       yuv420sp = new byte[240000];
       this.handler = handler;
    }




先是要固定的几个属性,在构造一个 Handler 方法,获取 handler ,在监听器中调用 Handler 的 post 的方法;将要执行的线程对象添加到线程队列中,这时候会把该线程对象添加到 handler 的对象的线程队列中;


接下来是连接:


public boolean connectToServer(String address, int port) {
       try {
           client = new Socket(address, port);
           out = new PrintStream(client.getOutputStream());
           ins = client.getInputStream();
           inbuf = new BufferedReader(newInputStreamReader(ins));
           status =NetWorkState.CONNECTED;
           readThread = new ReadThread();
           readThread.start();
           return true;
       } catch (Exception e) {
           e.printStackTrace();
           return false;
       }
    }




try{}catch{} 捕获异常


第一步是先给程序绑定地址和端口;


第二步建立请求;


第三部用 BufferedReader 来读取;


下一步就线程启动;


打开连接一定少不了关闭;关闭是必不可少,让其关闭释放内存;


public void disConnectServer() throws Exception {
       if (status == NetWorkState.OFFLINE)
           return;
       try {
           status =NetWorkState.OFFLINE;
           client.close();
       } catch (Exceptione) {
           e.printStackTrace();
           throw e;
       }
    }





图2-1用两个并行的连接:一个是控制连接,一个是数据连接。控制连接用于在client和server之间发送控制信息,如用手机端打开远程摄像头按钮。数据连接用于传送文件。Server用于在888端口上监听控制连接,如果有需要拍照,就另外建立一个数据连接,通过它来传送文件。数据连接的建立有两种方


如图2-2所示,TCP服务器server在80端口上监听数据连接,client主动请求建立与该端口的连接。




图 2-2 client 在 8888 端口上监听数据连接


首先由 client 创建一个监听匿名端口的 ServerSocket ,再把这个 ServerSocket 监听的端口号(调用 ServerSocket 的 getLocalPort() 方法就能得到端口号)发送给 server ,然后由 server 主动请求建立与客户端的连接。


   

public int getStatus() {
       return this.status;
    }
public String getLocalHost() {
       try {
           for(Enumeration<NetworkInterface> en = NetworkInterface
                  .getNetworkInterfaces();en.hasMoreElements();) {
              NetworkInterface intf =en.nextElement();
              for(Enumeration<InetAddress> enumIpAddr = intf
                     .getInetAddresses();enumIpAddr.hasMoreElements();) {
                  InetAddress inetAddress =enumIpAddr.nextElement();
                  if(!inetAddress.isLoopbackAddress()) {
                     returninetAddress.getHostAddress().toString();
                  }
              }
           }
       } catch(SocketException ex) {
       }
       return null;
    }
public void excuteCommond(byte[] commond) {
       try {
           Message msg = handler.obtainMessage(Commond.PHOTODATA, commond);
           handler.sendMessage(msg);
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
接下来就是Handler提供的一种异步消息处理机制,当消息队列中发送消息后就立即返回;
public void sendMessage(String message) throws Exception {
       try {
           if (status == NetWorkState.CONNECTED && client.isConnected()) {
              out.println(message);
           } else {
              throw new Exception();
           }
       } catch (Exceptione) {
           throw e;
       }
    }
    public void sendMessage(int message) throws Exception {
       try {
           if (status == NetWorkState.CONNECTED && client.isConnected()) {
              out.println(message);
           } else {
              throw new Exception();
           }
       } catch (Exceptione) {
           throw e;
       }
    }
线程处理:
class ReadThread extends Thread {
       public void run() {
           while (status == NetWorkState.CONNECTED) {
              try {
                  if(client.isClosed())
                     throw new Exception();
                  ins.read(yuv420sp);
              //  excuteCommond(yuv420sp);
              //  sleep(100);
              } catch (Exceptione) {
                  handler.sendEmptyMessage(Commond.NETWORKERROR);
                  break;
              }
           }
           try {
              disConnectServer();
           } catch (Exceptione) {
              e.printStackTrace();
           }
       }
    }



线程处理过程中,使用 Handler 的 post 方法将 runable 对象放到 Handler 的队列中后,该 runable 的执行并未单独开启线程,而是仍然在当前的 activity 的线程中执行, Handler 只是调用了 runable 的 run 方法,上面方法 catch 里面一旦获得 handler .sendEmptyMessage 的就会抛出网络异常;


如果主机只有一个 IP 地址,那么默认情况下,服务器程序就与该 IP 地址绑定。


public static String getIp(String address) {
       try {
           Log.i("test", "address:  "+address);
           String ipstr = InetAddress.getByName(address).getHostAddress();
           Log.i("test", "ipstr:  "+ipstr);
           if (ipstr == null || ipstr.equals("220.250.64.24"))
              return null;
           else
              return ipstr;
       } catch (UnknownHostException e) {
           e.printStackTrace();
           return null;
       }
    }



下一步需要写一个 activity 类:


public class MySocketClientTestActivity extends Activity implements
       OnClickListener,Callback {
    private static final String DEFAULT = "视频设备未连接";
    private static final String CONNECTOK = "视频设备连接成功";
    private static final String CONNECTFAIL = "视频设备连接失败,请检查网络状况及服务器地址及端口输入是否正确!";
    private static final String SERVERCLOSED = "视频设备已断开连接";
    private SocketClient client;
    private Builder connectDialog;
    private SurfaceView photoShow;
    private SurfaceHolder mHolde;
    private Paint paint;
    private Button connectBtn;
    private Button disConnectBtn;
    private Button openCamera;
    private Button takePhoto;
    private TextView statusView;
    private int cameraStatus = CameraState.CAMERAOPENED;
    private String serverAddress;
    private int port;
    private String status;
    public byte[] yuv420sp = null;
    public byte[] rgbArray = null;
    public int[] imgarray = null;
这个类需要打开视频拍照,所以需要状态参数和SocketClient对象,还有服务器地址;当oncreate方法之后,需要连接到server上:
private void connectServer(CharSequence sAddress) {
       if (sAddress== null) {
           status = DEFAULT;
       } else {
           try {
              port =NetWorkState.ServerPort;
              serverAddress = SocketClient.getIp(sAddress.toString());
              if (client.connectToServer(this.serverAddress, this.port)) {
                  status = CONNECTOK + serverAddress + ":" + port;
                  this.drawThread.start();
              } else {
                  status = CONNECTFAIL;
              }
           } catch (Exceptione) {
              e.printStackTrace();
              status = CONNECTFAIL;
           }
       }
       updateLayout();  //这里同时调用打开相机的方法;
    }
有连接就有关闭,
private void disConnectServer() {
       try {
           client.disConnectServer();
       } catch (Exception e) {
       }
       status = DEFAULT;  //当状态为默认的情况下,再次调用打开相机的方法
       updateLayout();
    }
//打开相机方法:
private void updateLayout() {
       if (client != null && client.getStatus()==NetWorkState.CONNECTED) {
           this.connectBtn.setEnabled(false);
           this.disConnectBtn.setEnabled(true);
           this.openCamera.setEnabled(true);
           if(cameraStatus == CommonState.CameraState.CAMERAOPENED) {
              this.openCamera.setText("关闭相机");
              this.takePhoto.setEnabled(true);
           } else {
              this.openCamera.setText("打开相机");
              this.takePhoto.setEnabled(false);
           }
       } else {
           this.connectBtn.setEnabled(true);
           this.disConnectBtn.setEnabled(false);
           this.openCamera.setText("打开相机");
           this.openCamera.setEnabled(false);
           this.takePhoto.setEnabled(false);
       }
       statusView.setText(status);
    }
显示连接窗口: 连接到网络,和输入IP地址或者是域名
private void showConnectDialog() {
       this.connectDialog.setTitle(R.string.connect_dilog_title);
       this.connectDialog.setIcon(R.drawable.network);
       finalLinearLayout connectForm = (LinearLayout) getLayoutInflater()
              .inflate(R.layout.connect_dialog_layout, null);
       connectDialog.setView(connectForm);
       connectDialog.setPositiveButton(R.string.connect_btn_text,
              newDialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface arg0, int arg1) {
                     TextView address =(TextView) connectForm
                            .findViewById(R.id.server_address);
                     connectServer(address.getText());
                  }
              });
       connectDialog.setNegativeButton(R.string.cancle_btn_text,
              newDialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface arg0, int arg1) {
                  }
              });
       connectDialog.create().show();
    }
//消息处理:
获取消息之后,创建图片,在远程拍完之后的照片需要传入到本地手机上显示或者是保存在servlet里面:
final Handler handler = new Handler() {
       @Override
       public void handleMessage(Message msg) {
           if(msg.what == Commond.PHOTODATA){
              updateImage(msg);
           }
       }
    };
    public void updateImage(Message msg) {
       yuv420sp = (byte[]) msg.obj;
       try {
           ImageUtils.decodeYUV420SP(imgarray, yuv420sp, 400, 400);
           Bitmap temp = Bitmap.createBitmap(imgarray, 400, 400,Bitmap.Config.ARGB_8888);
           if (temp == null)
              throw new Exception("创建图片失败");
           drawPhoto(temp,imgarray);
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
   
    public void updateImage(byte[] yuvdata){
       try {
           ImageUtils.decodeYUV420SP(imgarray, yuvdata, 400, 400);
           Bitmap temp = Bitmap.createBitmap(imgarray, 400, 400,Bitmap.Config.ARGB_8888);
           if (temp == null)
              throw new Exception("创建图片失败");
           drawPhoto(temp,imgarray);
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
事件触发监听:
    @Override
    public void onClick(View v) {
       try {
           switch (v.getId()){
           case R.id.connect_server:
              showConnectDialog();
              break;
           case R.id.disconnect_server:
              disConnectServer();
              break;
           case R.id.open_camera:
              if (client != null)
                  client.sendMessage(Commond.OPENCAMERA);
              break;
           case R.id.take_photo:
              if (client != null)
                  client.sendMessage(Commond.CLOSECAMERA);
               break;
           }
       } catch (Exceptione) {
           e.printStackTrace();
       }
    }
需要一个ImageUtils类来转换图片的大小;(详见程序ImageUtils.java类);
Server部分:
建立SocketServer实现线程接口:
public void run() {  
       try {
           netState =CommondList.WAITING;
           handler.sendEmptyMessage(CommondList.WAITING);
           client = commondServer.accept();
           handler.sendEmptyMessage(CommondList.CONNECTED);
           out = new PrintStream(client.getOutputStream());
           buf = new BufferedReader(newInputStreamReader(
                  client.getInputStream()));
           netState = CommondList.CONNECTED;
           while (netState == CommondList.CONNECTED) {
              try {
                  netCommond = buf.read();
                  handler.sendEmptyMessage(netCommond);
              } catch (Exception e) {
                  netState =CommondList.OFFLINE;
                  e.printStackTrace();
              }
           }
           out.close();
           buf.close();
           client.close();
           commondServer.close();
       } catch(IOException e) {
           e.printStackTrace();
       }
    }
//启动监听,绑定端口。这里默认设置是8888
    public boolean startListener() {
       if (netState == CommondList.OFFLINE) {
           try {
              netState = CommondList.OFFLINE;
              commondServer = new ServerSocket(8888);
              new Thread(this).start();
              return true;
           } catch(IOException e) {
              netState =CommondList.OFFLINE;
              e.printStackTrace();
              return false;
           }
       }
       return true;
    }
//发送数据
    public void sendData(byte[] data){
       if(netState == CommondList.CONNECTED){
           try {
              out.write(data);
           } catch (IOException e) {
              e.printStackTrace();
           }
       }
    }
    public void sendData(String data){
       if(netState == CommondList.CONNECTED){
           out.println(data);
       }
    }
    public void sendData(int data){
       if(netState == CommondList.CONNECTED){
           out.println(data);
       }
    }
    //关闭服务
    public void close() {
       netState = CommondList.OFFLINE;
       if (commondServer != null)
           try {
              if(out!=null)
              out.close();
              if(buf!=null)
              buf.close();
              commondServer.close();
           } catch(IOException e) {
              e.printStackTrace();
           }
    }
//本地手机上的主机地址还有网络是否连接
    public String getLocalHost() {
      
       try {
           for(Enumeration<NetworkInterface> en = NetworkInterface
                  .getNetworkInterfaces();en.hasMoreElements();) {
              NetworkInterface intf =en.nextElement();
              for(Enumeration<InetAddress> enumIpAddr = intf
                     .getInetAddresses();enumIpAddr.hasMoreElements();) {
                  InetAddress inetAddress =enumIpAddr.nextElement();
                  if(!inetAddress.isLoopbackAddress()) {
                     Log.i("test", inetAddress.getHostAddress().toString());
                     returninetAddress.getHostAddress().toString();
                  }
              }
           }
       } catch(SocketException ex) {
           ex.printStackTrace();
       }
       return null;
    }
   
    public int getNetState(){
       return this.netState;
    }




创建自己的MySocketServerActivity类继承原有的activity;onCreate方法之后是指定用于显示拍照过程影像的容器,通常是 SurfaceHolder 对象。由于影像需要在 SurfaceView 对象中显示,因此可以使用 SurfaceView 类的 getHolder 方法获得 SurfaceHolder 对象。在拍照过程中涉及到一些状态的变化。这些状态包括开始拍照(对应 surfaceCreated 事件方法);拍照状态变化(例如图像格式或方向,对应 surfaceChanged事件方法);结 束拍照(对应 surfaceDestroyed事件方法)。这 3 个事件方法都是在 SurfaceHolder.Callback 接口中定义的,因此,需要使用 SurfaceHolder 接口的 addCallback 方法指定 SurfaceHolder.Callback 对象,以便捕捉这 3 个事件。

//图像格式或方向
@Override    
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {     
	if (mCamera == null)         
		return;     
	if (mIsPreviewing) {  
		mCamera.stopPreview(); 
		}    
	Camera.Parameters p = mCamera.getParameters(); 
	p.setPreviewFrameRate(20);     
	p.setPreviewSize(pWidth, pHeight);  
	p.setFlashMode(Camera.Parameters.FLASH_MODE_ON);  
	p.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);    
	CameraAudiaoPreviewStream audioStream = new CameraAudiaoPreviewStream();   
	mCamera.setPreviewCallback(audioStream);    
	mCamera.setParameters(p);   
	try {         
		mCamera.setPreviewDisplay(mSurfaceHolder);  
		} catch (Exceptione) {        
			e.printStackTrace();       
		}    
	mCamera.startPreview();    
	videoThread.start();     
	mIsPreviewing = true;  
	}
//状态变化:   
@Override    
public void surfaceCreated(SurfaceHolder arg0) {   
	try {         
		mCamera = Camera.open();      
		if (mCamera ==null)           
			throw new Exception();       
		} catch (Exception e) {      
			e.printStackTrace();      
			Toast.makeText(this, "无法连接相机",Toast.LENGTH_LONG).show();  
		}    
	}
//结束拍照(对应 surfaceDestroyed事件方法)   
@Override   
public void surfaceDestroyed(SurfaceHolder arg0) {   

	if (mCamera != null) {       
		mCamera.stopPreview();
		mIsPreviewing = false;   
		mCamera.release();      
		mCamera = null;     
	}  
}
//启动service服务
private void startService() { 
	localAddress = server.getLocalHost();     
	if (localAddress == null) {         
		localAddress = "请检查本机是否联网"; 
		statusStr = "建立网络视频服务失败";   
	} else {   
		if (server.startListener()) { 
			statusStr = "建立网络视频服务成功";   
			} else {        
				statusStr = "建立网络视频服务失败,可能8888端口已经被占用";  
			}   
	}       
	updateView();//调用数据更新       
	localhostName.setText(localAddress);   
}   
private void stopService() {   
	server.close();     
	statusStr = "网络视频已经停止";  
}   
private void updateView() {     
	switch (server.getNetState()) {  
	case CommondList.WAITING:     
	case CommondList.CONNECTED:    
		this.startService.setEnabled(false);       
		this.stopService.setEnabled(true);       
		break;    
	case CommondList.OFFLINE: 
		this.startService.setEnabled(true);
         this.stopService.setEnabled(false); 
	}  
}

//Handler获取消息对象   
final Handler handler = new Handler() {     
	@Override       
	public void handleMessage(Message msg) {       
		switch (msg.what) {         
		caseCommondList.WAITING:     
			clientName.setText("正在等待设备连接.......");   
		break;      
		caseCommondList.CONNECTED:    
			clientName.setText("已经连接");         
		break;         
		case 0x115:          
			Stringcommond = (String) msg.obj;    
			Toast.makeText(MySocketServerTestActivity.this, commond,                
					Toast.LENGTH_LONG).show();         
			}           
		super.handleMessage(msg);    
			} 
	};               
	class CameraAudiaoPreviewStream implements Camera.PreviewCallback {   
		@Override     
		public void onPreviewFrame(byte[] data,Camera camera) {      
			yuv420sp = data; 
			}   
		}
	//发送数据线程   
	class SendVideoDataThread extends Thread {  
		public void run() {          
			while (mIsPreviewing) {   
				if (server.getNetState() == CommondList.CONNECTED) {  
					server.sendData(yuv420sp);            
					}        
				}   
			}   
		}
}



以上是用socket server、client来处理远程拍照。其在拍照的过程中,我们还可以利用云端存储照片,在通过调用api返回到手机终端上显示。

俗话说:“无图无真相,有视频更好”根据现在的网络社会发达,人们一般喜欢看图片和视频,而不喜欢皱巴巴的文字。


看似简单的意愿,却让开发这不知道如何是好,因为图片和视频随着数量的增多,会导致占用大量的内存,这时候就可以想到把图片和视频存储在云端,来释放服务器资源。在利用API来调用返回到手机端。


【没完整,因为API也需要自己写,之后就可以用API控制云端,具体实现,我想应该需要,创建步骤,改权限步骤,绑定公网ip补助,在启动步骤等】 例图: