基本过程是android作为socket客户端将采集到的每一帧图像数据发送出去,PC作为服务器接收并显示每一帧图像实现远程监控。图片如下(后来PC端加了个拍照功能)。。。
(PS。刚学android和java不久很多东西还不懂,高手若是知道哪些地方可以继续优化的话还请多多指点下啊)
系统代码如下:
一、android手机客户端
(1)AndroidManifest.xml文件。添加camera和socket权限,并设置了程序开始执行的activity、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
manifest
xmlns:android
=
"http://schemas.android.com/apk/res/android"
package
=
"org.wanghai.CameraTest"
android:versionCode
=
"1"
android:versionName
=
"1.0"
>
<
uses-sdk
android:minSdkVersion
=
"15"
/>
<!-- 授予程序使用摄像头的权限 -->
<
uses-permission
android:name
=
"android.permission.CAMERA"
/>
<
uses-feature
android:name
=
"android.hardware.camera"
/>
<
uses-feature
android:name
=
"android.hardware.camera.autofocus"
/>
<
uses-permission
android:name
=
"android.permission.INTERNET"
/>
<
uses-permission
android:name
=
"android.permission.KILL_BACKGROUND_PROCESSES"
/>
<
uses-permission
android:name
=
"android.permission.RESTART_PACKAGES"
/>
<
application
android:icon
=
"@drawable/ic_launcher"
android:label
=
"@string/app_name"
>
<
activity
android:name
=
".GetIP"
android:screenOrientation
=
"landscape"
android:label
=
"@string/app_name"
>
<
intent-filter
>
<
action
android:name
=
"android.intent.action.MAIN"
/>
<
category
android:name
=
"android.intent.category.LAUNCHER"
/>
</
intent-filter
>
</
activity
>
<
activity
android:name
=
".CameraTest"
android:screenOrientation
=
"landscape"
android:label
=
"@string/app_name"
>
</
activity
>
</
application
>
</
manifest
>
(2)main.xml 设置surfaceview用于摄像头采集图像的预览
?
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
android:orientation
=
"vertical"
>
<
SurfaceView
android:id
=
"@+id/sView"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
android:scaleType
=
"fitCenter"
/>
</
LinearLayout
>
(3)login.xml 登录界面,用于输入服务器IP
?
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
TableLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:id
=
"@+id/loginForm"
android:orientation
=
"vertical"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
>
<
TableRow
>
<
TextView
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:text
=
"IP:"
android:textSize
=
"10pt"
/>
<!-- 输入用户名的文本框 -->
<
EditText
android:id
=
"@+id/ipedittext"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:digits
=
"0123456789."
android:hint
=
"请填写服务器IP"
android:selectAllOnFocus
=
"true"
/>
</
TableRow
>
</
TableLayout
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
(
4
)GetIP.java 获得服务器IP后,通过Intent启动CameraTest的activity,ip信息通过Bundle传递
?
public
class
GetIP
extends
Activity {
String ipname =
null
;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
// 设置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
final
Builder builder =
new
AlertDialog.Builder(
this
);
//定义一个AlertDialog.Builder对象
builder.setTitle(
"登录服务器对话框"
);
// 设置对话框的标题
//装载/res/layout/login.xml界面布局
TableLayout loginForm = (TableLayout)getLayoutInflater().inflate( R.layout.login,
null
);
final
EditText iptext = (EditText)loginForm.findViewById(R.id.ipedittext);
builder.setView(loginForm);
// 设置对话框显示的View对象
// 为对话框设置一个“登录”按钮
builder.setPositiveButton(
"登录"
// 为按钮设置监听器
,
new
OnClickListener() {
@Override
public
void
onClick(DialogInterface dialog,
int
which) {
//此处可执行登录处理
ipname = iptext.getText().toString().trim();
Bundle data =
new
Bundle();
data.putString(
"ipname"
,ipname);
Intent intent =
new
Intent(GetIP.
this
,CameraTest.
class
);
intent.putExtras(data);
startActivity(intent);
}
});
// 为对话框设置一个“取消”按钮
builder.setNegativeButton(
"取消"
,
new
OnClickListener()
{
@Override
public
void
onClick(DialogInterface dialog,
int
which)
{
//取消登录,不做任何事情。
System.exit(
1
);
}
});
//创建、并显示对话框
builder.create().show();
}
}
(
5
)CameraTest.java 程序主体。设置PreviewCallback后,每当一帧图像数据采集完成后将调用PreviewCallback的onPreviewFrame函数。在这里我们将YUV格式数据转为jpg,再启用线程将数据通过socket发送出去。
?
public
class
CameraTest
extends
Activity {
SurfaceView sView;
SurfaceHolder surfaceHolder;
int
screenWidth, screenHeight;
Camera camera;
// 定义系统所用的照相机
boolean
isPreview =
false
;
//是否在浏览中
private
String ipname;
@SuppressWarnings
(
"deprecation"
)
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
// 设置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
// 获取IP地址
Intent intent = getIntent();
Bundle data = intent.getExtras();
ipname = data.getString(
"ipname"
);
screenWidth =
640
;
screenHeight =
480
;
sView = (SurfaceView) findViewById(R.id.sView);
// 获取界面中SurfaceView组件
surfaceHolder = sView.getHolder();
// 获得SurfaceView的SurfaceHolder
// 为surfaceHolder添加一个回调监听器
surfaceHolder.addCallback(
new
Callback() {
@Override
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
}
@Override
public
void
surfaceCreated(SurfaceHolder holder) {
initCamera();
// 打开摄像头
}
@Override
public
void
surfaceDestroyed(SurfaceHolder holder) {
// 如果camera不为null ,释放摄像头
if
(camera !=
null
) {
if
(isPreview)
camera.stopPreview();
camera.release();
camera =
null
;
}
System.exit(
0
);
}
});
// 设置该SurfaceView自己不维护缓冲
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
private
void
initCamera() {
if
(!isPreview) {
camera = Camera.open();
}
if
(camera !=
null
&& !isPreview) {
try
{
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(screenWidth, screenHeight);
// 设置预览照片的大小
parameters.setPreviewFpsRange(
20
,
30
);
// 每秒显示20~30帧
parameters.setPictureFormat(ImageFormat.NV21);
// 设置图片格式
parameters.setPictureSize(screenWidth, screenHeight);
// 设置照片的大小
//camera.setParameters(parameters); // android2.3.3以后不需要此行代码
camera.setPreviewDisplay(surfaceHolder);
// 通过SurfaceView显示取景画面
camera.setPreviewCallback(
new
StreamIt(ipname));
// 设置回调的类
camera.startPreview();
// 开始预览
camera.autoFocus(
null
);
// 自动对焦
}
catch
(Exception e) {
e.printStackTrace();
}
isPreview =
true
;
}
}
}
class
StreamIt
implements
Camera.PreviewCallback {
private
String ipname;
public
StreamIt(String ipname){
this
.ipname = ipname;
}
@Override
public
void
onPreviewFrame(
byte
[] data, Camera camera) {
Size size = camera.getParameters().getPreviewSize();
try
{
//调用image.compressToJpeg()将YUV格式图像数据data转为jpg格式
YuvImage image =
new
YuvImage(data, ImageFormat.NV21, size.width, size.height,
null
);
if
(image!=
null
){
ByteArrayOutputStream outstream =
new
ByteArrayOutputStream();
image.compressToJpeg(
new
Rect(
0
,
0
, size.width, size.height),
80
, outstream);
outstream.flush();
//启用线程将图像数据发送出去
Thread th =
new
MyThread(outstream,ipname);
th.start();
}
}
catch
(Exception ex){
Log.e(
"Sys"
,
"Error:"
+ex.getMessage());
}
}
}
class
MyThread
extends
Thread{
private
byte
byteBuffer[] =
new
byte
[
1024
];
private
OutputStream outsocket;
private
ByteArrayOutputStream myoutputstream;
private
String ipname;
public
MyThread(ByteArrayOutputStream myoutputstream,String ipname){
this
.myoutputstream = myoutputstream;
this
.ipname = ipname;
try
{
myoutputstream.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
public
void
run() {
try
{
//将图像数据通过Socket发送出去
Socket tempSocket =
new
Socket(ipname,
6000
);
outsocket = tempSocket.getOutputStream();
ByteArrayInputStream inputstream =
new
ByteArrayInputStream(myoutputstream.toByteArray());
int
amount;
while
((amount = inputstream.read(byteBuffer)) != -
1
) {
outsocket.write(byteBuffer,
0
, amount);
}
myoutputstream.flush();
myoutputstream.close();
tempSocket.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
二、PC服务器端
ImageServer.java 用于显示图像,并且可以拍照
?
public
class
ImageServer {
public
static
ServerSocket ss =
null
;
public
static
void
main(String args[])
throws
IOException{
ss =
new
ServerSocket(
6000
);
final
ImageFrame frame =
new
ImageFrame(ss);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(
true
);
while
(
true
){
frame.panel.getimage();
frame.repaint();
}
}
}
/**
A frame with an image panel
*/
@SuppressWarnings
(
"serial"
)
class
ImageFrame
extends
JFrame{
public
ImagePanel panel;
public
JButton jb;
public
ImageFrame(ServerSocket ss){
// get screen dimensions
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int
screenHeight = screenSize.height;
int
screenWidth = screenSize.width;
// center frame in screen
setTitle(
"ImageTest"
);
setLocation((screenWidth - DEFAULT_WIDTH) /
2
, (screenHeight - DEFAULT_HEIGHT) /
2
);
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// add panel to frame
this
.getContentPane().setLayout(
null
);
panel =
new
ImagePanel(ss);
panel.setSize(
640
,
480
);
panel.setLocation(
0
,
0
);
add(panel);
jb =
new
JButton(
"拍照"
);
jb.setBounds(
0
,
480
,
640
,
50
);
add(jb);
saveimage saveaction =
new
saveimage(ss);
jb.addActionListener(saveaction);
}
public
static
final
int
DEFAULT_WIDTH =
640
;
public
static
final
int
DEFAULT_HEIGHT =
560
;
}
/**
A panel that displays a tiled image
*/
@SuppressWarnings
(
"serial"
)
class
ImagePanel
extends
JPanel {
private
ServerSocket ss;
private
Image image;
private
InputStream ins;
public
ImagePanel(ServerSocket ss) {
this
.ss = ss;
}
public
void
getimage()
throws
IOException{
Socket s =
this
.ss.accept();
System.out.println(
"连接成功!"
);
this
.ins = s.getInputStream();
this
.image = ImageIO.read(ins);
this
.ins.close();
}
public
void
paintComponent(Graphics g){
super
.paintComponent(g);
if
(image ==
null
)
return
;
g.drawImage(image,
0
,
0
,
null
);
}
}
class
saveimage
implements
ActionListener {
RandomAccessFile inFile =
null
;
byte
byteBuffer[] =
new
byte
[
1024
];
InputStream ins;
private
ServerSocket ss;
public
saveimage(ServerSocket ss){
this
.ss = ss;
}
public
void
actionPerformed(ActionEvent event){
try
{
Socket s = ss.accept();
ins = s.getInputStream();
// 文件选择器以当前的目录打开
JFileChooser jfc =
new
JFileChooser(
"."
);
jfc.showSaveDialog(
new
javax.swing.JFrame());
// 获取当前的选择文件引用
File savedFile = jfc.getSelectedFile();
// 已经选择了文件
if
(savedFile !=
null
) {
// 读取文件的数据,可以每次以快的方式读取数据
try
{
inFile =
new
RandomAccessFile(savedFile,
"rw"
);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
int
amount;
while
((amount = ins.read(byteBuffer)) != -
1
) {
inFile.write(byteBuffer,
0
, amount);
}
inFile.close();
ins.close();
s.close();
javax.swing.JOptionPane.showMessageDialog(
new
javax.swing.JFrame(),
"已接保存成功"
,
"提示!"
, javax.swing.JOptionPane.PLAIN_MESSAGE);
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
|
开放源码如下(android我使用的是4.03的SDK,其它版本请自行更改。2.3.3版本以下的请注意initCamera()里被注释掉的哪一行)
只能在android4.04系统的手机上运行成功哦。
下面是测试成功时的启动画面:
转自:http://www.cnblogs.com/feifei1010/archive/2012/08/31/2664939.html