转载请注明出处:https://blog.csdn.net/mymottoissh/article/details/83590248
上一篇对开发需要的环境进行了准备。现在开始准备代码。前面我们说过,会在树莓派一端建立服务器,而Android会作为客户端进行连接。本篇针对的即是服务端的代码。具体来讲,服务端需要实现两个服务器,分别作为视频文件服务器和实时视频推送服务器。本篇实现的功能包括定时转存视频文件以及本地HTTP服务器的准备。
依照之前的设计思路,该服务器实现的功能是每隔3min,将采集到的视频流存储为文件。同时,该路径下启动一HTTP服务器供Android访问。那么可以这样设计,树莓派上电时,开启后台service。这个service有两个作用,一是需要实现定时转存文件功能。二是提供HTTP本地服务。考虑到后续需要实现的实时视频流的推送功能,HTTP服务至少需要提供一个GET处理方式。来确定客户端请求的是历史视频文件还是实时视频流,又或者是直接访问的文件。如果客户端请求的是历史视频文件,那么需要将所有视频文件的文件名返回给客户端供访问。如果请求的是实时视频流,则需要开启流推送。如果是直接访问的视频文件,还需要将文件传送给客户端。
为了实现上述功能,我们的main函数加载时,需要干两件事:
if __name__ == '__main__':
#开启文件转存线程
startRecording()
#开启本地HTTP服务器
server = MyVideoHttpServer.MyVideoHttpServer()
server.setPort("127.0.0.1", 8080)
server.setHandler(MyVideoHttpServer.MyVideoHttpHandler)
server.start()
其实有了上一节对picamera的介绍,视频转存部分的代码,直接在例子的基础是改吧改吧就可以用了。
def getFileName():
ts = time.localtime(time.time())
return 'video_' + str(ts.tm_year) + '_' + str(ts.tm_mon) + '_' + str(ts.tm_mday) + '_' + str(ts.tm_hour) + '_' + str(ts.tm_min) + '_' + str(ts.tm_sec) + ".h264"
def recordFile():
camera = picamera.PiCamera()
camera.resolution = (640, 480)
while True:
camera.start_recording(getFileName())
camera.wait_recording(180)
camera.stop_recording()
def startRecording():
trd = threading.Thread(target=recordFile, args=())
trd.start()
对于HTTP的部分,直接上代码
import SocketServer
import BaseHTTPServer
import urlparse
import os
import socket
import shutil
class MyVideoHttpServer:
handler = None
ip = "127.0.0.1"
port = 8080
def setHandler(self, handler):
self.handler = handler
def setPort(self, addr="127.0.0.1", port=8080):
self.ip = addr
self.port = port
def start(self):
httpd = SocketServer.TCPServer(("", self.port), self.handler)
httpd.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
httpd.serve_forever()
class MyVideoHttpHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
print "get req received"
print self.path
path = urlparse.urlparse(self.path).path
query = urlparse.urlparse(self.path).query
print query
if len(query) == 0:
if len(path.split(".")) > 1 and (path.split(".")[1] == "h264" or path.split(".")[1] == "mp4"):
try:
file = open("." + path, 'rb')
self.respFileHeaders(os.path.getsize("." + path))
shutil.copyfileobj(file, self.wfile)
except:
print "file process error"
finally:
file.close()
else:
self.respErr()
else:
params = dict()
for p in query.split("&"):
if p.find("=") > 0:
params[p.split("=")[0]] = p.split("=")[1]
showMode = params.get("showMode")
print showMode
#1 hist
if showMode == "1":
print "history file mode"
self.sendFileList()
#2 air
elif showMode == "0":
print "air mode"
self.startRtmpServer()
else:
self.respErr()
def respTextHeaders(self, contentLen):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", contentLen)
self.send_header("Transfer-Encoding", "utf-8")
self.end_headers()
def respFileHeaders(self, contentLen):
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", contentLen)
self.end_headers()
def respErr(self):
self.send_response(404)
self.end_headers()
def sendFileList(self):
fileList = ""
for root, dirs, files in os.walk(os.curdir):
if len(files) > 0:
for f in files:
if len(f.split(".")) > 0 and f.split(".")[1] == "h264":
fileList += f
fileList += " "
self.respTextHeaders(len(fileList))
self.wfile.write(fileList)
def startRtmpServer(self):
self.respTextHeaders(2)
self.wfile.write("OK")
os.system("gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-raw, width=640, height=480, framerate=30/1' ! queue ! videoconvert ! omxh264enc ! h264parse ! flvmux ! rtmpsink location='rtmp://192.168.0.102/live live=1' &")
重点说一下handler的实现。提供了doGet方法来处理get请求。
如果url中含有showMode的查询字段,那么如果showMode等于1表示历史文件列表请求,那么会给客户端返回当前目录下所有以.mp4和.h264结尾的文件名供前端访问。
如果showMode等于0,表示请求实时数据,此时会开启GStreamer进行视频采集并发送给rtmp服务器。关于rtmp的部署会在下一节记录。
如果请求路径中含有文件名,服务端会尝试打开该文件并返回给客户端。
如果以上情况均不是,则直接返回404。
其实关于上述实现,还有一个重要的缺陷。就是经过测试picamera和GStreamer同时访问一个摄像头资源会造成错误。这个问题需要在结合具体需求来进行权衡。本次实现只针对功能,所以暂且不去考虑这个问题。默认同一时间内仅有一个功能可以使用。