Last active
May 15, 2020 05:04
-
-
Save arvind-iyer/90cd941d0885f422bdf90905d86f9e04 to your computer and use it in GitHub Desktop.
Async video stream relay with opencv + websockets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Websocket video stream client example</title> | |
</head> | |
<body> | |
<canvas id="canvas" width="640" height="480"></canvas> | |
<script> | |
var websocketAddress = "ws://localhost:8765/media/zurich"; | |
var c = document.getElementById("canvas"); | |
var ctx = c.getContext("2d"); | |
var websocket = new WebSocket(websocketAddress); | |
websocket.onopen = function () { | |
console.log("websocket connected"); | |
}; | |
websocket.onclose = function () { | |
console.log("websocket disconnected"); | |
}; | |
websocket.onmessage = function (evt) { | |
var image = new Image(); | |
console.log("recd image"); | |
image.onload = function () { | |
ctx.drawImage(image, 0, 0); | |
}; | |
evt.data.text().then((data) => { | |
image.src = "data:image/jpeg;base64," + data; | |
}) | |
}; | |
websocket.onerror = function (evt) { | |
console.log('error: ' + evt.data); | |
websocket.close(); | |
}; | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Async video streaming server that fetches videos using OpenCV and | |
serves them over Websockets(default port 8765) | |
Connect at http://<SERVER_URL>:8765/media/<camera_name> to receive a | |
video stream as mjpeg format | |
""" | |
import websockets | |
import cv2 | |
import threading | |
import time | |
import asyncio | |
import base64 | |
# prefilled for demo purposes, load these from db for actual uses | |
STREAMS = { | |
"corner": "http://145.232.236.102/axis-cgi/mjpg/video.cgi", | |
"zurich": "http://176.127.42.138/axis-cgi/mjpg/video.cgi" | |
} | |
cameras = {camera: Camera(url) for camera, url in STREAMS.items()} | |
class Camera(threading.Thread): | |
def __init__(self, url): | |
threading.Thread.__init__(self) | |
self.url = url | |
self.cap = cv2.VideoCapture(url) | |
ret, self.frame = self.cap.read() | |
self.last_frame_time = time.time() | |
print(ret) | |
def run(self): | |
print("Started streaming from " + self.url) | |
self.frame_n = 0 | |
while True: | |
self.frame_n += 1 | |
ret, frame = self.cap.read() | |
self.last_frame_time = time.time() | |
if not ret: | |
print("error fetching stream") | |
time.sleep(1) | |
continue | |
frame = cv2.resize(frame, (640, 480)) | |
retval, buffer_img = cv2.imencode('.jpg', frame) | |
self.frame = base64.b64encode(buffer_img) | |
async def media_server(websocket, path): | |
# accepted path: /media/<camera> | |
if not path.startswith("/media/"): | |
await websocket.send("invalid path") | |
return | |
camera = path.replace("/media/", "") | |
print(f"streaming {camera} to {websocket}") | |
last_frame_time = 0 | |
if camera in cameras: | |
stream = cameras[camera] | |
while True: | |
# check for new frame | |
if stream.last_frame_time > last_frame_time: | |
last_frame_time = stream.last_frame_time | |
# await websocket.send(f"recd frame {stream.frame_n}") | |
await websocket.send(stream.frame) | |
await asyncio.sleep(0.04) | |
await websocket.send(f"bye bye {camera}") | |
if __name__ == "__main__": | |
for cam in cameras.values(): | |
cam.start() | |
start_server = websockets.serve(media_server, "0.0.0.0", 8765) | |
loop = asyncio.get_event_loop() | |
loop.run_until_complete(start_server) | |
loop.run_forever() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment