要买新手机了旧手机怎么办?我们可以废物利用下,把旧的手机变成一个远程监控摄像头。这里使用Java创建手机camera客户端和远程服务上的监控界面。
实现方法考虑几点:
-
创建一个Android自定义的摄像头应用
-
把preview的数据发送到服务端
-
preview的NV21数据解码
-
把图像画出来
Android摄像头,Socket链接,服务端图像显示
创建preview回调函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
Camera.PreviewCallback mPreviewCallback =
new
PreviewCallback() {
@Override
public
void
onPreviewFrame(
byte
[] data, Camera camera) {
// TODO Auto-generated method stub
synchronized
(mQueue) {
if
(mQueue.size() == MAX_BUFFER) {
mQueue.poll();
}
mQueue.add(data);
}
}
};
|
注册回调函数:
1
2
3
4
5
6
7
8
|
try
{
mCamera.setPreviewCallback(mPreviewCallback);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch
(Exception e){
Log.d(TAG,
"Error starting camera preview: "
+ e.getMessage());
}
|
停止释放摄像头:
1
2
3
4
5
6
7
|
public
void
onPause() {
if
(mCamera !=
null
) {
mCamera.setPreviewCallback(
null
);
mCamera.stopPreview();
}
resetBuff();
}
|
使用AlertDialog来设置服务器IP地址:
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
|
private
void
setting() {
LayoutInflater factory = LayoutInflater.from(
this
);
final
View textEntryView = factory.inflate(R.layout.server_setting,
null
);
AlertDialog dialog =
new
AlertDialog.Builder(IPCamera.
this
)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setTitle(R.string.setting_title)
.setView(textEntryView)
.setPositiveButton(R.string.ok,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
whichButton) {
EditText ipEdit = (EditText)textEntryView.findViewById(R.id.ip_edit);
EditText portEdit = (EditText)textEntryView.findViewById(R.id.port_edit);
mIP = ipEdit.getText().toString();
mPort = Integer.parseInt(portEdit.getText().toString());
Toast.makeText(IPCamera.
this
,
"New address: "
+ mIP +
":"
+ mPort, Toast.LENGTH_LONG).show();
}
})
.setNegativeButton(R.string.cancel,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
whichButton) {
/* User clicked cancel so do some stuff */
}
})
.create();
dialog.show();
}
|
启动线程来创建socket链接,发送JSON数据和每一帧的图像数据:
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
|
mSocket =
new
Socket();
mSocket.connect(
new
InetSocketAddress(mIP, mPort),
10000
);
BufferedOutputStream outputStream =
new
BufferedOutputStream(mSocket.getOutputStream());
BufferedInputStream inputStream =
new
BufferedInputStream(mSocket.getInputStream());
JsonObject jsonObj =
new
JsonObject();
jsonObj.addProperty(
"type"
,
"data"
);
jsonObj.addProperty(
"length"
, mCameraPreview.getPreviewLength());
jsonObj.addProperty(
"width"
, mCameraPreview.getPreviewWidth());
jsonObj.addProperty(
"height"
, mCameraPreview.getPreviewHeight());
byte
[] buff =
new
byte
[
256
];
int
len =
0
;
String msg =
null
;
outputStream.write(jsonObj.toString().getBytes());
outputStream.flush();
while
((len = inputStream.read(buff)) != -
1
) {
msg =
new
String(buff,
0
, len);
// JSON analysis
JsonParser parser =
new
JsonParser();
boolean
isJSON =
true
;
JsonElement element =
null
;
try
{
element = parser.parse(msg);
}
catch
(JsonParseException e) {
Log.e(TAG,
"exception: "
+ e);
isJSON =
false
;
}
if
(isJSON && element !=
null
) {
JsonObject obj = element.getAsJsonObject();
element = obj.get(
"state"
);
if
(element !=
null
&& element.getAsString().equals(
"ok"
)) {
// send data
while
(
true
) {
outputStream.write(mCameraPreview.getImageBuffer());
outputStream.flush();
if
(Thread.currentThread().isInterrupted())
break
;
}
break
;
}
}
else
{
break
;
}
}
outputStream.close();
inputStream.close();
|
服务端接收数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
int
fillBuffer(
byte
[] data,
int
off,
int
len, LinkedList<
byte
[]> YUVQueue) {
mTotalLength += len;
mByteArrayOutputStream.write(data, off, len);
if
(mTotalLength == mFrameLength) {
synchronized
(YUVQueue) {
YUVQueue.add(mByteArrayOutputStream.toByteArray());
mByteArrayOutputStream.reset();
}
mTotalLength =
0
;
System.out.println(
"received file"
);
}
return
0
;
}
|
NV21解码:
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
|
public
static
int
[] convertYUVtoRGB(
byte
[] yuv,
int
width,
int
height)
throws
NullPointerException, IllegalArgumentException {
int
[] out =
new
int
[width * height];
int
sz = width * height;
int
i, j;
int
Y, Cr =
0
, Cb =
0
;
for
(j =
0
; j < height; j++) {
int
pixPtr = j * width;
final
int
jDiv2 = j >>
1
;
for
(i =
0
; i < width; i++) {
Y = yuv[pixPtr];
if
(Y <
0
)
Y +=
255
;
if
((i &
0x1
) !=
1
) {
final
int
cOff = sz + jDiv2 * width + (i >>
1
) *
2
;
Cb = yuv[cOff];
if
(Cb <
0
)
Cb +=
127
;
else
Cb -=
128
;
Cr = yuv[cOff +
1
];
if
(Cr <
0
)
Cr +=
127
;
else
Cr -=
128
;
}
int
R = Y + Cr + (Cr >>
2
) + (Cr >>
3
) + (Cr >>
5
);
if
(R <
0
)
R =
0
;
else
if
(R >
255
)
R =
255
;
int
G = Y - (Cb >>
2
) + (Cb >>
4
) + (Cb >>
5
) - (Cr >>
1
)
+ (Cr >>
3
) + (Cr >>
4
) + (Cr >>
5
);
if
(G <
0
)
G =
0
;
else
if
(G >
255
)
G =
255
;
int
B = Y + Cb + (Cb >>
1
) + (Cb >>
2
) + (Cb >>
6
);
if
(B <
0
)
B =
0
;
else
if
(B >
255
)
B =
255
;
out[pixPtr++] =
0xff000000
+ (B <<
16
) + (G <<
8
) + R;
}
}
return
out;
}
|
使用Swing绘制BufferedImage:
1
2
3
4
|
BufferedImage bufferedImage =
null
;
int
[] rgbArray = Utils.convertYUVtoRGB(data, mWidth, mHeight);
bufferedImage =
new
BufferedImage(mWidth, mHeight, BufferedImage.TYPE_USHORT_565_RGB);
bufferedImage.setRGB(
0
,
0
, mWidth, mHeight, rgbArray,
0
, mWidth);
|
1
2
3
4
5
6
7
8
9
10
11
12
|
synchronized
(mQueue) {
if
(mQueue.size() >
0
) {
mLastFrame = mQueue.poll();
}
}
if
(mLastFrame !=
null
) {
g.drawImage(mLastFrame,
0
,
0
,
null
);
}
else
if
(mImage !=
null
) {
g.drawImage(mImage,
0
,
0
,
null
);
}
}
|