This might fix the stream errors in issue #25 Also refactored the code a bit since we do not want to display a window from within the API (offload to the person implementing it).
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
import os
|
|
from threading import ThreadError
|
|
|
|
import cv2
|
|
|
|
from util import threaded
|
|
|
|
|
|
class RtspClient:
|
|
"""
|
|
Inspiration from:
|
|
- https://benhowell.github.io/guide/2015/03/09/opencv-and-web-cam-streaming
|
|
- https://stackoverflow.com/questions/19846332/python-threading-inside-a-class
|
|
- https://stackoverflow.com/questions/55828451/video-streaming-from-ip-camera-in-python-using-opencv-cv2-videocapture
|
|
"""
|
|
|
|
def __init__(self, ip, username, password, port=554, profile="main", use_udp=True, callback=None, **kwargs):
|
|
"""
|
|
RTSP client is used to retrieve frames from the camera in a stream
|
|
|
|
:param ip: Camera IP
|
|
:param username: Camera Username
|
|
:param password: Camera User Password
|
|
:param port: RTSP port
|
|
:param profile: "main" or "sub"
|
|
:param use_upd: True to use UDP, False to use TCP
|
|
:param proxies: {"host": "localhost", "port": 8000}
|
|
"""
|
|
self.capture = None
|
|
self.thread_cancelled = False
|
|
self.callback = callback
|
|
|
|
capture_options = 'rtsp_transport;'
|
|
self.ip = ip
|
|
self.username = username
|
|
self.password = password
|
|
self.port = port
|
|
self.proxy = kwargs.get("proxies")
|
|
self.url = "rtsp://" + self.username + ":" + self.password + "@" + \
|
|
self.ip + ":" + str(self.port) + "//h264Preview_01_" + profile
|
|
if use_udp:
|
|
capture_options = capture_options + 'udp'
|
|
else:
|
|
capture_options = capture_options + 'tcp'
|
|
|
|
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = capture_options
|
|
|
|
# opens the stream capture, but does not retrieve any frames yet.
|
|
self._open_video_capture()
|
|
|
|
def _open_video_capture(self):
|
|
# To CAP_FFMPEG or not To ?
|
|
self.capture = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG)
|
|
|
|
def _stream_blocking(self):
|
|
while True:
|
|
try:
|
|
if self.capture.isOpened():
|
|
ret, frame = self.capture.read()
|
|
if ret:
|
|
yield frame
|
|
else:
|
|
print("stream closed")
|
|
self.capture.release()
|
|
return
|
|
except Exception as e:
|
|
print(e)
|
|
self.capture.release()
|
|
return
|
|
|
|
@threaded
|
|
def _stream_non_blocking(self):
|
|
while not self.thread_cancelled:
|
|
try:
|
|
if self.capture.isOpened():
|
|
ret, frame = self.capture.read()
|
|
if ret:
|
|
self.callback(frame)
|
|
else:
|
|
print("stream is closed")
|
|
self.stop_stream()
|
|
except ThreadError as e:
|
|
print(e)
|
|
self.stop_stream()
|
|
|
|
def stop_stream(self):
|
|
self.capture.release()
|
|
self.thread_cancelled = True
|
|
|
|
def open_stream(self):
|
|
"""
|
|
Opens OpenCV Video stream and returns the result according to the OpenCV documentation
|
|
https://docs.opencv.org/3.4/d8/dfe/classcv_1_1VideoCapture.html#a473055e77dd7faa4d26d686226b292c1
|
|
|
|
:param callback: The function to callback the cv::mat frame to if required to be non-blocking. If this is left
|
|
as None, then the function returns a generator which is blocking.
|
|
"""
|
|
|
|
# Reset the capture object
|
|
if self.capture is None or not self.capture.isOpened():
|
|
self._open_video_capture()
|
|
|
|
print("opening stream")
|
|
|
|
if self.callback is None:
|
|
return self._stream_blocking()
|
|
else:
|
|
# reset the thread status if the object was not re-created
|
|
if not self.thread_cancelled:
|
|
self.thread_cancelled = False
|
|
return self._stream_non_blocking()
|