1
1
import os
2
+ from threading import ThreadError
3
+
2
4
import cv2
3
5
6
+ from util import threaded
7
+
4
8
5
9
class RtspClient :
10
+ """
11
+ Inspiration from:
12
+ - https://benhowell.github.io/guide/2015/03/09/opencv-and-web-cam-streaming
13
+ - https://stackoverflow.com/questions/19846332/python-threading-inside-a-class
14
+ - https://stackoverflow.com/questions/55828451/video-streaming-from-ip-camera-in-python-using-opencv-cv2-videocapture
15
+ """
6
16
7
- def __init__ (self , ip , username , password , port = 554 , profile = "main" , use_udp = True , ** kwargs ):
17
+ def __init__ (self , ip , username , password , port = 554 , profile = "main" , use_udp = True , callback = None , ** kwargs ):
8
18
"""
19
+ RTSP client is used to retrieve frames from the camera in a stream
9
20
10
21
:param ip: Camera IP
11
22
:param username: Camera Username
@@ -15,33 +26,86 @@ def __init__(self, ip, username, password, port=554, profile="main", use_udp=Tru
15
26
:param use_upd: True to use UDP, False to use TCP
16
27
:param proxies: {"host": "localhost", "port": 8000}
17
28
"""
29
+ self .capture = None
30
+ self .thread_cancelled = False
31
+ self .callback = callback
32
+
18
33
capture_options = 'rtsp_transport;'
19
34
self .ip = ip
20
35
self .username = username
21
36
self .password = password
22
37
self .port = port
23
38
self .proxy = kwargs .get ("proxies" )
24
39
self .url = "rtsp://" + self .username + ":" + self .password + "@" + \
25
- self .ip + ":" + str (self .port ) + "//h264Preview_01_" + profile
40
+ self .ip + ":" + str (self .port ) + "//h264Preview_01_" + profile
26
41
if use_udp :
27
42
capture_options = capture_options + 'udp'
28
43
else :
29
44
capture_options = capture_options + 'tcp'
30
45
31
46
os .environ ["OPENCV_FFMPEG_CAPTURE_OPTIONS" ] = capture_options
32
47
33
- def preview (self ):
34
- """ Blocking function. Opens OpenCV window to display stream. """
35
- win_name = self .ip
36
- cap = cv2 .VideoCapture (self .url , cv2 .CAP_FFMPEG )
37
- ret , frame = cap .read ()
48
+ # opens the stream capture, but does not retrieve any frames yet.
49
+ self ._open_video_capture ()
50
+
51
+ def _open_video_capture (self ):
52
+ # To CAP_FFMPEG or not To ?
53
+ self .capture = cv2 .VideoCapture (self .url , cv2 .CAP_FFMPEG )
54
+
55
+ def _stream_blocking (self ):
56
+ while True :
57
+ try :
58
+ if self .capture .isOpened ():
59
+ ret , frame = self .capture .read ()
60
+ if ret :
61
+ yield frame
62
+ else :
63
+ print ("stream closed" )
64
+ self .capture .release ()
65
+ return
66
+ except Exception as e :
67
+ print (e )
68
+ self .capture .release ()
69
+ return
70
+
71
+ @threaded
72
+ def _stream_non_blocking (self ):
73
+ while not self .thread_cancelled :
74
+ try :
75
+ if self .capture .isOpened ():
76
+ ret , frame = self .capture .read ()
77
+ if ret :
78
+ self .callback (frame )
79
+ else :
80
+ print ("stream is closed" )
81
+ self .stop_stream ()
82
+ except ThreadError as e :
83
+ print (e )
84
+ self .stop_stream ()
38
85
39
- while ret :
40
- cv2 .imshow (win_name , frame )
86
+ def stop_stream (self ):
87
+ self .capture .release ()
88
+ self .thread_cancelled = True
41
89
42
- ret , frame = cap .read ()
43
- if (cv2 .waitKey (1 ) & 0xFF == ord ('q' )):
44
- break
90
+ def open_stream (self ):
91
+ """
92
+ Opens OpenCV Video stream and returns the result according to the OpenCV documentation
93
+ https://docs.opencv.org/3.4/d8/dfe/classcv_1_1VideoCapture.html#a473055e77dd7faa4d26d686226b292c1
94
+
95
+ :param callback: The function to callback the cv::mat frame to if required to be non-blocking. If this is left
96
+ as None, then the function returns a generator which is blocking.
97
+ """
45
98
46
- cap .release ()
47
- cv2 .destroyAllWindows ()
99
+ # Reset the capture object
100
+ if self .capture is None or not self .capture .isOpened ():
101
+ self ._open_video_capture ()
102
+
103
+ print ("opening stream" )
104
+
105
+ if self .callback is None :
106
+ return self ._stream_blocking ()
107
+ else :
108
+ # reset the thread status if the object was not re-created
109
+ if not self .thread_cancelled :
110
+ self .thread_cancelled = False
111
+ return self ._stream_non_blocking ()
0 commit comments