手机端操作平板跟或者是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补助,在启动步骤等】 例图: