描述:linux作为服务器,将摄像头的视频数据实时传输到安卓客户端。
主要思路:linux端通过opencv,连续获取摄像头图片,然后将图片压缩,通过socket传输给 安卓客户端,安卓客户端接收数据后将图片用控件(ImgView)显示出来。连续的的图片 组成了视频。
1,服务端
#!/usr/bin/env python
# -*- coding=utf-8 -*-
import socket
import numpy as np
import urllib
import cv2 as cv
import threading
import time
import sys
print('this is Server')
cap = cv.VideoCapture(0)
cap.set(cv.CAP_PROP_FRAME_WIDTH, 200)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, 200)
def socket_service():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止socket server重启后端口被占用(socket.error: [Errno 98] Address already in use)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#s.bind(('127.0.0.1', 6666))
s.bind(('192.168.16.107', 6666))
s.listen(10)
except socket.error as msg:
print (msg)
sys.exit(1)
print ('Waiting connection...')
while True:
conn, addr = s.accept()
t = threading.Thread(target=deal_data, args=(conn, addr))
t.start()
def deal_data(conn, addr):
print ('Accept new connection from {0}'.format(addr))
while True:
# get a frame
ret, frame = cap.read()
# 重整图片像素大小
frame = cv.resize(frame, (180,180))# 200,200 延迟约4秒
# '.jpg'表示把当前图片img按照jpg格式编码
img_encode = cv.imencode('.jpg', frame)[1]
data_encode = np.array(img_encode)
str_encode = data_encode.tostring()
encode_len = len(str_encode)
print('img size : %d'%encode_len)
#开始先发送8个1
conn.send("11111111")
time.sleep(0.05)
#分段发送图片数据, 发送到android, 低于1408才稳定
for i in range(encode_len/1380+1):
temp = str_encode[1380*i : 1380*(i+1)]
print("temp len : %d"%len(temp))
conn.send(temp)
time.sleep(0.05)# 0.05最小
print("send a frame ok")
#发送8个2 表示结束
conn.send("22222222")
time.sleep(0.05)
conn.close()
socket_service()
注:测试的时候发现安卓‘一下’只能最大接收1408个字节数据(linux与linxu的传输不会出现这种问题,不知道为什么),而我要传输的图片远远大于这一数值,所以采用分段传输的方法。安卓端分段接收。其中用‘11111111’和‘22222222’分别代表一张图片传输的开始和结束。其中的延时是我自己测试的情况,不同设备可能效果不一样。
2,客户端
//Socket连接的子线程,联网后不断接收数据
public class thread_ConnectAndReceive extends Thread{
public void run(){
String serverIP =ET_ip.getText().toString();
int serverPort = Integer.parseInt(ET_port.getText().toString());
try{
socket = new Socket();
socket.connect(new InetSocketAddress(serverIP,serverPort),500);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
socket.setReceiveBufferSize(1024*32);
sendMsgToHandler('S');//连接成功
//接收数据并处理
while(true){
byte[] buffer = new byte[1380];//稳定接收最大值1408
try {
inputStringLen = inputStream.read(buffer);//接收数据,返回值为数据长度
//收到头 "11111111"
if (headFlag == 0 && buffer[0] == 49 && buffer[1] == 49 && buffer[2] == 49 && buffer[3] == 49 &&
buffer[4] == 49 && buffer[5] == 49 && buffer[6] == 49 && buffer[7] == 49) {
headFlag = 1;
//收到尾 "22222222"
}else if(buffer[0] == 50 && buffer[1] == 50 && buffer[2] == 50 && buffer[3] == 50 &&
buffer[4] == 50 && buffer[5] == 50 && buffer[6] == 50 && buffer[7] == 50){
sendMsgToHandler('B');//更新画面
headFlag = 0;
//收图片数据
}else if(headFlag == 1) {
//分段收图片数据
tmp = byteMerger(tmp, buffer);//这个tmp是关键,后面用到
}
}catch (IOException e) {
//receiveMes = "receive err";
//sendMsgToHandler('M');
}
}
}catch (IOException e){
//在命令号打印异常信息在程序中出错的位置和原因
//e.printStackTrace();
}
}
}
注:安卓主要是 接收数据并处理(while循环)。接收到8个1,准备开始接收图片,接收到8个2,则
认为图片数据传输完毕。然后发消息给子线程,在子线程里更新控件(ImgView)的显示。由于完整代码太不简洁,展示一下部分代码
//安卓内接收消息并处理的子线程
@SuppressLint("handlerLeak")
private Handler messageHandler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case 'S':
Tips("连接中");
BT_connect.setText("连接中,点击断开");
break;
case 'D':
Tips("未连接");
BT_connect.setText("连接服务器");
break;
case 'I':
Tips("无IP");
break;
case 'N':
Tips("无内容");
break;
case 'B'://更新画面 用到前面接收的数据的tmp
bitmap = BitmapFactory.decodeByteArray(tmp, 0,tmp.length);
imgView.setImageBitmap(bitmap);//这句就能显示图片(bitmap数据没问题的情况下)
tmp = new byte[0];
break;
}
}
};
效果图:
安卓界面是之前的,还没改,主要是测试用。。。
注:在(180,180)的像素下,视频的延迟大概是3秒,这个好像和控件的大小也有关系。前面测试的时候,更小的像素和更小的控件,延迟大约2秒。
吐槽:这里花了最多时间的是,bitmap,和接收数据。开始bitmap不知道怎么用,用了又不知道对不对。后来传输了一张20*20的图片数据,才在显示出来。再后来,调试了很久才发现‘1408’这个奇怪的数,然后又用分段传输的思路,再后来加入头和尾的判断。。。。
感谢前面查到的资料的分享者,例如byte[]合并、bitmap显示. . . . . .
如果有更高效的视频传输方法,感谢留言指教。。