diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..157fa6b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM python:3.8 + +RUN set -ex; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + gcc \ + gir1.2-gstreamer-1.0 \ + gir1.2-gst-plugins-base-1.0 \ + gir1.2-gtk-3.0 \ + gstreamer1.0-alsa \ + gstreamer1.0-doc \ + gstreamer1.0-gl \ + gstreamer1.0-gtk3 \ + gstreamer1.0-libav \ + gstreamer1.0-plugins-bad \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-ugly \ + gstreamer1.0-pulseaudio \ + gstreamer1.0-qt5 \ + gstreamer1.0-tools \ + gstreamer1.0-x \ + libcairo2-dev \ + libgirepository1.0-dev \ + libgstreamer1.0-0 \ + pkg-config \ + python-gst-1.0; \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt ./ + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ./main_prg.py diff --git a/README.md b/README.md index 80c396d..cdcfbbc 100644 --- a/README.md +++ b/README.md @@ -6,48 +6,10 @@ This project helps in fetching continous live RTSP stream using GStreamer, Pytho ## Getting Started -Just clone this Repo then in main_prg.py add your rtsp stream on below line: +Just clone this Repo and run: -```python -self.camlink = '' #Add your RTSP cam link -``` - -### Prerequisites - -1. Python 3 -2. GStreamer -3. OpenCV (if you want to run this example as is) -4. Numpy - -##### 1. Python 3 Installation -This you would already know - -##### 2. GStreamer Installation -You will need GStreamer. Installation instruction can be found on this link [GStreamer](https://gstreamer.freedesktop.org/download/) -Still for your quick reference will list installation instruction for Ubuntu: - -``` -apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio -``` - -##### 3. OpenCV Installation -There are various way to install OpenCV but example using (Conda, PIP or build from source). But for purpose of this project below is instruction using PIP - -``` -pip3 install opencv-contrib-python -``` - -##### 4. Numpy Installation -``` -pip3 install numpy -``` - -### Running the program - -Post cloning the Repo, go to repo dir (Also include cam link in main_prg.py as mentioned above). - -```python -python3 main_prg.py +``` sh +$ CAMERA_URL=... docker-compose up --build ``` ## License diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..66f5a24 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +version: "2.4" + +services: + rtsp-client: + image: rtsp-client + build: . + environment: + QT_X11_NO_MITSHM: 1 + DISPLAY: $DISPLAY + CAMERA_URL: $CAMERA_URL + devices: + - '/dev/dri' + - '/dev/snd' + - '/dev/video0' + volumes: + - '/tmp/.X11-unix:/tmp/.X11-unix' diff --git a/main_prg.py b/main_prg.py old mode 100644 new mode 100755 index 5a95e46..75c2a67 --- a/main_prg.py +++ b/main_prg.py @@ -1,46 +1,41 @@ - +#!/usr/bin/env python3 '''\ -This Simple program Demonstrates how to use G-Streamer and capture RTSP Frames in Opencv using Python -- Sahil Parekh +This Simple program Demonstrates how to use G-Streamer and capture RTSP Frames +in Opencv using Python + - Sahil Parekh ''' import multiprocessing as mp import time import vid_streamv3 as vs import cv2 -import sys -''' -Main class -''' -class mainStreamClass: - def __init__(self): - #Current Cam +class mainStream: + def __init__(self, camlink): + # Current Cam self.camProcess = None self.cam_queue = None self.stopbit = None - self.camlink = '' #Add your RTSP cam link + self.camlink = camlink self.framerate = 6 - - def startMain(self): - #set queue size + def start(self): + # set queue size self.cam_queue = mp.Queue(maxsize=100) - #get all cams + # get all cams time.sleep(3) self.stopbit = mp.Event() - self.camProcess = vs.StreamCapture(self.camlink, - self.stopbit, - self.cam_queue, - self.framerate) + self.camProcess = vs.StreamCapture( + self.camlink, + self.stopbit, + self.cam_queue, + self.framerate + ) self.camProcess.start() - # calculate FPS - lastFTime = time.time() - try: while True: @@ -53,10 +48,7 @@ def startMain(self): diffTime = time.time() - lastFTime` fps = 1 / diffTime # print(fps) - ''' - lastFTime = time.time() - # if cmd == vs.StreamCommands.RESOLUTION: # pass #print(val) @@ -68,15 +60,13 @@ def startMain(self): except KeyboardInterrupt: print('Caught Keyboard interrupt') - except: - e = sys.exc_info() + except Exception as e: print('Caught Main Exception') print(e) self.stopCamStream() cv2.destroyAllWindows() - def stopCamStream(self): print('in stopCamStream') @@ -85,7 +75,7 @@ def stopCamStream(self): while not self.cam_queue.empty(): try: _ = self.cam_queue.get() - except: + except Exception: break self.cam_queue.close() @@ -93,5 +83,6 @@ def stopCamStream(self): if __name__ == "__main__": - mc = mainStreamClass() - mc.startMain() \ No newline at end of file + import os + stream = mainStream(os.environ["CAMERA_URL"]) + stream.start() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d06e31b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +opencv-contrib-python +numpy +pycairo +PyGObject diff --git a/vid_streamv3.py b/vid_streamv3.py index 2fb1a93..4823fe4 100644 --- a/vid_streamv3.py +++ b/vid_streamv3.py @@ -1,4 +1,3 @@ -#cython: language_level=3, boundscheck=False import multiprocessing as mp from enum import Enum import numpy as np @@ -7,11 +6,12 @@ from gi.repository import Gst Gst.init(None) -'''Konwn issues +'''Known issues * if format changes at run time system hangs ''' + class StreamMode(Enum): INIT_STREAM = 1 SETUP_STREAM = 1 @@ -27,7 +27,6 @@ class StreamCommands(Enum): class StreamCapture(mp.Process): - def __init__(self, link, stop, outQueue, framerate): """ Initialize the stream capturing process @@ -54,8 +53,6 @@ def __init__(self, link, stop, outQueue, framerate): self.num_unexpected_tot = 40 self.unexpected_cnt = 0 - - def gst_to_opencv(self, sample): buf = sample.get_buffer() caps = sample.get_caps() @@ -66,11 +63,14 @@ def gst_to_opencv(self, sample): # print(caps.get_structure(0).get_value('width')) arr = np.ndarray( - (caps.get_structure(0).get_value('height'), - caps.get_structure(0).get_value('width'), - 3), + ( + caps.get_structure(0).get_value('height'), + caps.get_structure(0).get_value('width'), + 3 + ), buffer=buf.extract_dup(0, buf.get_size()), - dtype=np.uint8) + dtype=np.uint8 + ) return arr def new_buffer(self, sink, _): @@ -83,9 +83,13 @@ def new_buffer(self, sink, _): def run(self): # Create the empty pipeline self.pipeline = Gst.parse_launch( - 'rtspsrc name=m_rtspsrc ! rtph264depay name=m_rtph264depay ! avdec_h264 name=m_avdech264 ! videoconvert name=m_videoconvert ! videorate name=m_videorate ! appsink name=m_appsink') + 'rtspsrc name=m_rtspsrc ! ' + 'rtph264depay name=m_rtph264depay ! ' + 'avdec_h264 name=m_avdech264 ! ' + 'videoconvert name=m_videoconvert ! ' + 'videorate name=m_videorate ! appsink name=m_appsink' + ) - # source params self.source = self.pipeline.get_by_name('m_rtspsrc') self.source.set_property('latency', 0) self.source.set_property('location', self.streamLink) @@ -95,6 +99,8 @@ def run(self): self.source.set_property('tcp-timeout', 5000000) self.source.set_property('drop-on-latency', 'true') + print("stream_link - %s" % self.streamLink) + # decode params self.decode = self.pipeline.get_by_name('m_avdech264') self.decode.set_property('max-threads', 2) @@ -103,7 +109,7 @@ def run(self): # convert params self.convert = self.pipeline.get_by_name('m_videoconvert') - #framerate parameters + # framerate parameters self.framerate_ctr = self.pipeline.get_by_name('m_videorate') self.framerate_ctr.set_property('max-rate', self.framerate/1) self.framerate_ctr.set_property('drop-only', 'true') @@ -111,7 +117,8 @@ def run(self): # sink params self.sink = self.pipeline.get_by_name('m_appsink') - # Maximum number of nanoseconds that a buffer can be late before it is dropped (-1 unlimited) + # Maximum number of nanoseconds that a buffer can be late before + # it is dropped (-1 unlimited) # flags: readable, writable # Integer64. Range: -1 - 9223372036854775807 Default: -1 self.sink.set_property('max-lateness', 500000000) @@ -138,10 +145,17 @@ def run(self): # flags: readable, writable # Caps (NULL) caps = Gst.caps_from_string( - 'video/x-raw, format=(string){BGR, GRAY8}; video/x-bayer,format=(string){rggb,bggr,grbg,gbrg}') + 'video/x-raw, format=(string){BGR, GRAY8};' + 'video/x-bayer,format=(string){rggb,bggr,grbg,gbrg}' + ) self.sink.set_property('caps', caps) - if not self.source or not self.sink or not self.pipeline or not self.decode or not self.convert: + if not self.source \ + or not self.sink \ + or not self.pipeline \ + or not self.decode \ + or not self.convert: + print("Not all elements could be created.") self.stop.set() @@ -156,8 +170,9 @@ def run(self): # Wait until error or EOS bus = self.pipeline.get_bus() - while True: + print("Before cycle") + while True: if self.stop.is_set(): print('Stopping CAM Stream by main process') break @@ -165,21 +180,21 @@ def run(self): message = bus.timed_pop_filtered(10000, Gst.MessageType.ANY) # print "image_arr: ", image_arr if self.image_arr is not None and self.newImage is True: - if not self.outQueue.full(): - # print("\r adding to queue of size{}".format(self.outQueue.qsize()), end='\r') - self.outQueue.put((StreamCommands.FRAME, self.image_arr), block=False) + # print("\r adding to queue of size %s" % + # self.outQueue.qsize()), end='\r') + self.outQueue.put((StreamCommands.FRAME, self.image_arr), + block=False) self.image_arr = None self.unexpected_cnt = 0 - if message: if message.type == Gst.MessageType.ERROR: err, debug = message.parse_error() - print("Error received from element %s: %s" % ( - message.src.get_name(), err)) + print("Error received from element %s: %s" % + (message.src.get_name(), err)) print("Debugging information: %s" % debug) break elif message.type == Gst.MessageType.EOS: @@ -187,7 +202,8 @@ def run(self): break elif message.type == Gst.MessageType.STATE_CHANGED: if isinstance(message.src, Gst.Pipeline): - old_state, new_state, pending_state = message.parse_state_changed() + old_state, new_state, pending_state = \ + message.parse_state_changed() print("Pipeline state changed from %s to %s." % (old_state.value_nick, new_state.value_nick)) else: @@ -196,7 +212,6 @@ def run(self): if self.unexpected_cnt == self.num_unexpected_tot: break - print('terminating cam pipe') self.stop.set() - self.pipeline.set_state(Gst.State.NULL) \ No newline at end of file + self.pipeline.set_state(Gst.State.NULL)