Creation of the raw video pipeline [still prototype]
authorDavid Testé <soonum@gnu.org>
Wed, 24 Feb 2016 15:41:44 +0000 (16:41 +0100)
committerDavid Testé <soonum@gnu.org>
Wed, 24 Feb 2016 15:41:44 +0000 (16:41 +0100)
stream_2016/libre-streamer.py

index 1ac1bc17196fbaee14918e3d36885c4f08ad0699..86dadc22abb380bc99236c851ec7e704c30a0115 100755 (executable)
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # TODO list:
 # ----------
+#    - Add a form to fill before start streaming (conf title, name, etc...)
+#    - Add a checkbox to enable/disable options (storing/streaming - storing only - stream only - etc...)
+#    - Add a function to get the ip address of the camera automatically (see github.com/paulmilliken)
 #    - Create a module for the pipeline construction section to clarify the code
-#    - Create a module for the network configuration (ifconfig, stream server,etc)
+#    - Implement 2016 edition pipeline, see file 'gstream_pipeline_by_quidam'
+#    - Create a module for the network configuration (fan/cpu, ifconfig, stream server,etc)
 #    - Generate a log file during runtime. (e.g. this will let you know if the network configuration
 #      and the pipeline construction went well (or not))
 #    - Add an input source choice for the user (camera on IP or webcam)
 #    - Add a VU-meter to check if audio feed is emitting signal
-#    - Investigate this error (at stream launching during prototyping phase):
-#        (libre-streamer.py:7856): Gdk-ERROR **: The program 'libre-streamer.py' received an X Window System error.
-#        This probably reflects a bug in the program.
-#        The error was 'BadIDChoice (invalid resource ID chosen for this connection)'.
-#        (Details: serial 592 error_code 14 request_code 1 (core protocol) minor_code 0)
 #    - Add the FSF logo (need to do some pixel art) as an application icon
+#    - Add the FSF logo inside the streamer use the 'textoverlay' method in ElementFactory.make()
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# INFO: run the following command in a terminal before launching libre-streamer to get a error log.
+# GST_DEBUG=3,python:5,gnl*:5 ./libre-streamer.py | tee -a log 2>&1
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+__author__ = 'David Testé'
+__licence__ = 'GPLv3'
+__version__ = 0.1
+__maintainer__ = 'David Testé'
+__email__ = 'soonum@gnu.org'
+__status__ = 'Prototype'
+
+
 import sys
 
 import gi
@@ -43,9 +56,6 @@ from gi.repository import Gst
 from gi.repository import GdkX11
 from gi.repository import GstVideo
 
-##import networkinit
-##import gstpipeline
-
 
 class Streamgui(object):
 
@@ -77,6 +87,11 @@ class Streamgui(object):
         self.win.set_position(Gtk.WindowPosition.CENTER)
         self.win.show_all()
 
+        self.xid = self.videowidget.get_property('window').get_xid()
+        self.connectsignals()
+
+    def connectsignals(self):
+        """Connects signals with the methods"""
         bus = self.pipel.get_bus()
         bus.add_signal_watch()
         bus.enable_sync_message_emission()
@@ -84,50 +99,154 @@ class Streamgui(object):
         bus.connect("message", self.on_message)
         # Used for connecting video to your application.
         bus.connect("sync-message::element", self.on_sync_message)
+        # Connect the rtpdepay signal
+        self.videosrc.connect("pad-added", self.on_pad_added_to_rtspsrc)
+        if self.decodebin:
+            self.decodebin.connect("pad-added", self.on_pad_added_to_decodebin)
+##        elif self.jpegdec:
+##            self.jpegdec.connect("pad-added", self.on_pad_added_to_jpegdec)
+            
+    def on_pad_added_to_rtspsrc(self, rtspsrc, pad):
+##        if self.decodebin:
+##            sinkpad = self.decodebin.get_static_pad('sink')
+##        elif self.rtpjpegdepay:
+##            sinkpad = self.rtpjpegdepay.get_static_pad('sink')
+        sinkpad = self.queuev_1.get_static_pad('sink')
+        pad.link(sinkpad)
+        print('[DEBUG] rtspsrc LINKED')
 
-    def constructpipeline (self):
+    def on_pad_added_to_decodebin(self, decodebin, pad):
+        screen_sinkpad = self.screensink.get_static_pad('sink')
+        pad.link(screen_sinkpad)
+        print('[DEBUG] decodebin LINKED')
 
+    def on_pad_added_to_jpegdec(self, jpegdec, pad):
+        screen_sinkpad = self.screensink.get_static_pad('sink')
+        pad.link(screen_sinkpad)
+        print('[DEBUG] decodebin LINKED')
+        
+    def constructpipeline (self):
         """Add and link elements in a GStreamer pipeline"""
-
         # Create the pipelines instance.
         self.streampipe = Gst.Pipeline()
-##        self.storepipe_hi = Gst.Pipeline()
-##        self.storepipe_lo = Gst.Pipeline()
 
         # Define pipeline elements.
-        ## The next line WILL NOT display the camera's video feed
-        ##    self.videosrc = Gst.ElementFactory.make('location', 'rtsp://192.168.48.2:554')
-        self.videosrc = Gst.ElementFactory.make('videotestsrc', 'source')
-        self.queue = Gst.ElementFactory.make('queue')
-        ##    self.oggstreamsink = Gst.ElementFactory.make()
-        ##    self.oggdisksink = Gst.ElementFactory.make()
-
-        ##    self.jpegdisksink = Gst.ElementFactory.make()
-
-        self.screensink = Gst.ElementFactory.make('xvimagesink', 'rawfeed')
-    
-        ##    self.webmdisksink = Gst.ElementFactory.make()
-        ##    self.webmstreamsink = Gst.ElementFactory.make()
-    
+        self.videosrc = Gst.ElementFactory.make('rtspsrc', 'videosrc')
+        self.videosrc.set_property('location', 'rtsp://192.168.48.2:554')
+        self.videosrc.set_property('latency', 100)
+
+        self.decodebin = Gst.ElementFactory.make('decodebin', 'decodebin')
+        
+## Video source for testing purpose:        
+##        self.videosrc = Gst.ElementFactory.make('videotestsrc', 'videosrc')
+        self.rtpjpegdepay = Gst.ElementFactory.make('rtpjpegdepay', 'rtpjpegdepay')
+        self.jpegdec = Gst.ElementFactory.make('jpegdec', 'jpegdec')
+        self.jpegdec.set_property('max-errors', -1)
+        self.mkvmux = Gst.ElementFactory.make('matroskamux', 'mkvmux')
+        self.tee_rawvideo = Gst.ElementFactory.make('tee', 'tee_rawvideo')
+        self.queuev_1 = Gst.ElementFactory.make('queue', 'queuev_1')
+        self.queuev_2 = Gst.ElementFactory.make('queue', 'queuev_2')
+        self.queuev_3 = Gst.ElementFactory.make('queue', 'queuev_3')
+        self.queuev_4 = Gst.ElementFactory.make('queue', 'queuev_4')
+
+        self.disksink_rawvideo = Gst.ElementFactory.make('filesink')
+#[TO DO]: File location has to be defined 
+        self.disksink_rawvideo.set_property('location', 'popo_rawvideo')
+        self.screensink = Gst.ElementFactory.make('xvimagesink', 'screensink')
+        
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# IMPORTANT:
+# for 'webmmux' element streamable=True MUST be set!
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+        # Elements to test:
+        #---------------------------------------------------------------------------
+        self.audiosrc = Gst.ElementFactory.make('pulsesrc', 'audiosrc')
+        self.vorbisenc = Gst.ElementFactory.make('vorbisenc', 'vorbisenc')
+##        scaling_caps = Gst.Caps('video/x-raw, width=640, height=360')
+        self.scaling = Gst.ElementFactory.make('videoscale', 'scaling')
+        self.vp8enc = Gst.ElementFactory.make('vp8enc', 'vp8enc')
+        self.vp8enc.set_property('min_quantizer', 1)
+        self.vp8enc.set_property('max_quantizer', 13)
+        self.vp8enc.set_property('cpu-used', 5)
+        self.vp8enc.set_property('deadline', 42000)
+        self.vp8enc.set_property('threads', 2)
+        self.vp8enc.set_property('sharpness', 7)
+        self.webmmux = Gst.ElementFactory.make('webmmux', 'webmmux')
+        self.webmmux.set_property('streamable', True)
+        
+        self.tee_streamvideo = Gst.ElementFactory.make('tee', 'tee_streamvideo')
+        self.tee_streamaudio = Gst.ElementFactory.make('tee', 'tee_streamaudio')
+        self.queuea_1 = Gst.ElementFactory.make('queue', 'queuea_1')
+        self.queuea_2 = Gst.ElementFactory.make('queue', 'queuea_2')
+        
+        self.disksink_audio = Gst.ElementFactory.make('filesink')
+        self.disksink_audio.set_property('location', 'popo_audio')
+        self.disksink_stream = Gst.ElementFactory.make('filesink')
+        self.disksink_stream.set_property('location', 'popo_stream')
+
+        self.icecastsink_audio = Gst.ElementFactory.make('shout2send', 'icecastsink_audio')
+        # Configuration should be written on a file locally to keep safe private addresses
+        self.icecastsink_audio.set_property('ip', 'live2.fsf.org')
+        self.icecastsink_audio.set_property('port', 80)
+        self.icecastsink_audio.set_property('mount', 'testaudio.ogv')
+        self.icecastsink_audio.set_property('password', '')
+        self.icecastsink_stream = Gst.ElementFactory.make('shout2send', 'icecastsink_stream')
+        self.icecastsink_stream.set_property('ip', 'live2.fsf.org')
+        self.icecastsink_stream.set_property('port', 80)
+        self.icecastsink_stream.set_property('mount', 'teststream.ogv')
+        self.icecastsink_stream.set_property('password', '')
+        #---------------------------------------------------------------------------
+        
         # Add the elements to the pipeline.
+        # Test of the first two lines of quidam's pipeline:
         self.streampipe.add(self.videosrc)
-        self.streampipe.add(self.queue)
+##        self.streampipe.add(self.decodebin)
+        self.streampipe.add(self.queuev_1)
+        self.streampipe.add(self.rtpjpegdepay)
+        self.streampipe.add(self.queuev_2)
+        self.streampipe.add(self.jpegdec)
+        self.streampipe.add(self.tee_rawvideo)
+        self.streampipe.add(self.queuev_3)
+        self.streampipe.add(self.mkvmux)
+        self.streampipe.add(self.queuev_4)
+        self.streampipe.add(self.disksink_rawvideo)
         self.streampipe.add(self.screensink)
-        # Link the elements in the pipeline.
-        self.videosrc.link(self.queue)
-        self.queue.link(self.screensink)
 
+        
+        # Link the elements in the pipeline.
+##        self.videosrc.link(self.decodebin)
+        self.queuev_1.link(self.rtpjpegdepay)
+##        self.rtpjpegdepay.link(self.queuev_2)
+##        self.rtpjpegdepay.link(self.jpegdec)
+        self.rtpjpegdepay.link(self.tee_rawvideo)
+##        self.queuev_2.link(self.jpegdec)
+##        self.jpegdec.link(self.tee_rawvideo)
+##        self.jpegdec.link(self.queuev_3)
+        self.tee_rawvideo.link(self.queuev_2)
+        self.tee_rawvideo.link(self.jpegdec)
+##        self.tee_rawvideo.link(self.queuev_3)
+        self.queuev_2.link(self.mkvmux)
+        self.mkvmux.link(self.queuev_4)
+        self.queuev_4.link(self.disksink_rawvideo)
+##        self.decodebin.link(self.screensink)
+##        self.queuev_3.link(self.disksink_rawvideo)
+        self.jpegdec.link(self.queuev_3)
+        self.queuev_3.link(self.screensink)
+        
         return self.streampipe
 
     def on_message(self, bus, message):
 
         t = message.type
         if t == Gst.MessageType.EOS:
-            self.player.set_state(Gst.State.NULL)
+            self.pipel.set_state(Gst.State.NULL)
+            self.stream_button.set_label('Stream')
         elif t == Gst.MessageType.ERROR:
-            self.player.set_state(Gst.State.NULL)
             err, debug = message.parse_error()
             print ("Error: %s" % err, debug)
+            self.pipel.set_state(Gst.State.NULL)
+            self.stream_button.set_label('Stream')
 
     def on_sync_message(self, bus, message):
 
@@ -144,8 +263,6 @@ class Streamgui(object):
 
     def on_stream_clicked(self, widget):
 
-        # Put here the script to launch streaming and start recording
-##        self.streampipe.uri_construct('rtsp', '://192.168.48.2:554')
         labelname = self.stream_button.get_label()
         if labelname == 'Stream':
             self.pipel.set_state(Gst.State.PLAYING)