1 #!/usr/bin/env python3.4
3 # This file is part of Libre-Streamer.
5 # Libre-Streamer is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # Libre-Streamer is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Libre-Streamer. If not, see <http://www.gnu.org/licenses/>.
18 # Copyright (c) 2016 David Testé
20 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 # - Add a form to fill before start streaming (conf title, name, etc...)
24 # - Add a checkbox to enable/disable options (storing/streaming - storing only - stream only - etc...)
25 # - Add a function to get the ip address of the camera automatically (see github.com/paulmilliken)
26 # - Create a module for the pipeline construction section to clarify the code
27 # - Implement 2016 edition pipeline, see file 'gstream_pipeline_by_quidam'
28 # - Create a module for the network configuration (fan/cpu, ifconfig, stream server,etc)
29 # - Generate a log file during runtime. (e.g. this will let you know if the network configuration
30 # and the pipeline construction went well (or not))
31 # - Add an input source choice for the user (camera on IP or webcam)
32 # - Add a VU-meter to check if audio feed is emitting signal
33 # - Add the FSF logo (need to do some pixel art) as an application icon
34 # - Add the FSF logo inside the streamer use the 'textoverlay' method in ElementFactory.make()
35 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
38 # INFO: run the following command in a terminal before launching libre-streamer to get a error log.
39 # GST_DEBUG=3,python:5,gnl*:5 ./libre-streamer.py | tee -a log 2>&1
40 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
42 __author__
= 'David Testé'
45 __maintainer__
= 'David Testé'
46 __email__
= 'soonum@gnu.org'
47 __status__
= 'Prototype'
53 gi
.require_version('Gtk', '3.0')
54 from gi
.repository
import Gtk
55 from gi
.repository
import Gst
56 from gi
.repository
import GdkX11
57 from gi
.repository
import GstVideo
60 class Streamgui(object):
65 self
.multimedia_file
=""
66 # Create the global pipeline (might wanna use a general bin instead)
67 self
.pipel
= self
.constructpipeline()
69 self
.win
= Gtk
.Window()
70 self
.win
.set_title("Libre-Streamer")
71 self
.win
.connect("delete_event",
72 lambda w
,e
: Gtk
.main_quit())
73 vbox
= Gtk
.VBox(False, 0)
74 hbox
= Gtk
.HBox(False, 0)
75 self
.load_file
= Gtk
.FileChooserButton("Choose Audio File")
76 self
.stream_button
= Gtk
.Button("Stream")
77 self
.videowidget
= Gtk
.DrawingArea()
78 self
.videowidget
.set_size_request(600, 400)
79 self
.load_file
.connect("selection-changed", self
.on_file_selected
)
80 self
.stream_button
.connect("clicked", self
.on_stream_clicked
)
82 hbox
.pack_start(self
.stream_button
, False, True, 0)
83 vbox
.pack_start(self
.load_file
, False, True, 0)
84 vbox
.pack_start(self
.videowidget
, True, True, 0)
85 vbox
.pack_start(hbox
, False, True, 0)
87 self
.win
.set_position(Gtk
.WindowPosition
.CENTER
)
90 self
.xid
= self
.videowidget
.get_property('window').get_xid()
93 def connectsignals(self
):
94 """Connects signals with the methods"""
95 bus
= self
.pipel
.get_bus()
96 bus
.add_signal_watch()
97 bus
.enable_sync_message_emission()
98 # Used to get messages that GStreamer emits.
99 bus
.connect("message", self
.on_message
)
100 # Used for connecting video to your application.
101 bus
.connect("sync-message::element", self
.on_sync_message
)
102 # Connect the rtpdepay signal
103 self
.videosrc
.connect("pad-added", self
.on_pad_added_to_rtspsrc
)
105 self
.decodebin
.connect("pad-added", self
.on_pad_added_to_decodebin
)
106 ## elif self.jpegdec:
107 ## self.jpegdec.connect("pad-added", self.on_pad_added_to_jpegdec)
109 def on_pad_added_to_rtspsrc(self
, rtspsrc
, pad
):
110 ## if self.decodebin:
111 ## sinkpad = self.decodebin.get_static_pad('sink')
112 ## elif self.rtpjpegdepay:
113 ## sinkpad = self.rtpjpegdepay.get_static_pad('sink')
114 sinkpad
= self
.queuev_1
.get_static_pad('sink')
116 print('[DEBUG] rtspsrc LINKED')
118 def on_pad_added_to_decodebin(self
, decodebin
, pad
):
119 screen_sinkpad
= self
.screensink
.get_static_pad('sink')
120 pad
.link(screen_sinkpad
)
121 print('[DEBUG] decodebin LINKED')
123 def on_pad_added_to_jpegdec(self
, jpegdec
, pad
):
124 screen_sinkpad
= self
.screensink
.get_static_pad('sink')
125 pad
.link(screen_sinkpad
)
126 print('[DEBUG] decodebin LINKED')
128 def constructpipeline (self
):
129 """Add and link elements in a GStreamer pipeline"""
130 # Create the pipelines instance.
131 self
.streampipe
= Gst
.Pipeline()
133 # Define pipeline elements.
134 self
.videosrc
= Gst
.ElementFactory
.make('rtspsrc', 'videosrc')
135 self
.videosrc
.set_property('location', 'rtsp://192.168.48.2:554')
136 self
.videosrc
.set_property('latency', 100)
138 self
.decodebin
= Gst
.ElementFactory
.make('decodebin', 'decodebin')
140 ## Video source for testing purpose:
141 ## self.videosrc = Gst.ElementFactory.make('videotestsrc', 'videosrc')
142 self
.rtpjpegdepay
= Gst
.ElementFactory
.make('rtpjpegdepay', 'rtpjpegdepay')
143 self
.jpegdec
= Gst
.ElementFactory
.make('jpegdec', 'jpegdec')
144 self
.jpegdec
.set_property('max-errors', -1)
145 self
.mkvmux
= Gst
.ElementFactory
.make('matroskamux', 'mkvmux')
146 self
.tee_rawvideo
= Gst
.ElementFactory
.make('tee', 'tee_rawvideo')
147 self
.queuev_1
= Gst
.ElementFactory
.make('queue', 'queuev_1')
148 self
.queuev_2
= Gst
.ElementFactory
.make('queue', 'queuev_2')
149 self
.queuev_3
= Gst
.ElementFactory
.make('queue', 'queuev_3')
150 self
.queuev_4
= Gst
.ElementFactory
.make('queue', 'queuev_4')
152 self
.disksink_rawvideo
= Gst
.ElementFactory
.make('filesink')
153 #[TO DO]: File location has to be defined
154 self
.disksink_rawvideo
.set_property('location', 'popo_rawvideo')
155 self
.screensink
= Gst
.ElementFactory
.make('xvimagesink', 'screensink')
157 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
159 # for 'webmmux' element streamable=True MUST be set!
160 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
163 #---------------------------------------------------------------------------
164 self
.audiosrc
= Gst
.ElementFactory
.make('pulsesrc', 'audiosrc')
165 self
.vorbisenc
= Gst
.ElementFactory
.make('vorbisenc', 'vorbisenc')
166 ## scaling_caps = Gst.Caps('video/x-raw, width=640, height=360')
167 self
.scaling
= Gst
.ElementFactory
.make('videoscale', 'scaling')
168 self
.vp8enc
= Gst
.ElementFactory
.make('vp8enc', 'vp8enc')
169 self
.vp8enc
.set_property('min_quantizer', 1)
170 self
.vp8enc
.set_property('max_quantizer', 13)
171 self
.vp8enc
.set_property('cpu-used', 5)
172 self
.vp8enc
.set_property('deadline', 42000)
173 self
.vp8enc
.set_property('threads', 2)
174 self
.vp8enc
.set_property('sharpness', 7)
175 self
.webmmux
= Gst
.ElementFactory
.make('webmmux', 'webmmux')
176 self
.webmmux
.set_property('streamable', True)
178 self
.tee_streamvideo
= Gst
.ElementFactory
.make('tee', 'tee_streamvideo')
179 self
.tee_streamaudio
= Gst
.ElementFactory
.make('tee', 'tee_streamaudio')
180 self
.queuea_1
= Gst
.ElementFactory
.make('queue', 'queuea_1')
181 self
.queuea_2
= Gst
.ElementFactory
.make('queue', 'queuea_2')
183 self
.disksink_audio
= Gst
.ElementFactory
.make('filesink')
184 self
.disksink_audio
.set_property('location', 'popo_audio')
185 self
.disksink_stream
= Gst
.ElementFactory
.make('filesink')
186 self
.disksink_stream
.set_property('location', 'popo_stream')
188 self
.icecastsink_audio
= Gst
.ElementFactory
.make('shout2send', 'icecastsink_audio')
189 # Configuration should be written on a file locally to keep safe private addresses
190 self
.icecastsink_audio
.set_property('ip', 'live2.fsf.org')
191 self
.icecastsink_audio
.set_property('port', 80)
192 self
.icecastsink_audio
.set_property('mount', 'testaudio.ogv')
193 self
.icecastsink_audio
.set_property('password', '')
194 self
.icecastsink_stream
= Gst
.ElementFactory
.make('shout2send', 'icecastsink_stream')
195 self
.icecastsink_stream
.set_property('ip', 'live2.fsf.org')
196 self
.icecastsink_stream
.set_property('port', 80)
197 self
.icecastsink_stream
.set_property('mount', 'teststream.ogv')
198 self
.icecastsink_stream
.set_property('password', '')
199 #---------------------------------------------------------------------------
201 # Add the elements to the pipeline.
202 # Test of the first two lines of quidam's pipeline:
203 self
.streampipe
.add(self
.videosrc
)
204 ## self.streampipe.add(self.decodebin)
205 self
.streampipe
.add(self
.queuev_1
)
206 self
.streampipe
.add(self
.rtpjpegdepay
)
207 self
.streampipe
.add(self
.queuev_2
)
208 self
.streampipe
.add(self
.jpegdec
)
209 self
.streampipe
.add(self
.tee_rawvideo
)
210 self
.streampipe
.add(self
.queuev_3
)
211 self
.streampipe
.add(self
.mkvmux
)
212 self
.streampipe
.add(self
.queuev_4
)
213 self
.streampipe
.add(self
.disksink_rawvideo
)
214 self
.streampipe
.add(self
.screensink
)
217 # Link the elements in the pipeline.
218 ## self.videosrc.link(self.decodebin)
219 self
.queuev_1
.link(self
.rtpjpegdepay
)
220 ## self.rtpjpegdepay.link(self.queuev_2)
221 ## self.rtpjpegdepay.link(self.jpegdec)
222 self
.rtpjpegdepay
.link(self
.tee_rawvideo
)
223 ## self.queuev_2.link(self.jpegdec)
224 ## self.jpegdec.link(self.tee_rawvideo)
225 ## self.jpegdec.link(self.queuev_3)
226 self
.tee_rawvideo
.link(self
.queuev_2
)
227 self
.tee_rawvideo
.link(self
.jpegdec
)
228 ## self.tee_rawvideo.link(self.queuev_3)
229 self
.queuev_2
.link(self
.mkvmux
)
230 self
.mkvmux
.link(self
.queuev_4
)
231 self
.queuev_4
.link(self
.disksink_rawvideo
)
232 ## self.decodebin.link(self.screensink)
233 ## self.queuev_3.link(self.disksink_rawvideo)
234 self
.jpegdec
.link(self
.queuev_3
)
235 self
.queuev_3
.link(self
.screensink
)
237 return self
.streampipe
239 def on_message(self
, bus
, message
):
242 if t
== Gst
.MessageType
.EOS
:
243 self
.pipel
.set_state(Gst
.State
.NULL
)
244 self
.stream_button
.set_label('Stream')
245 elif t
== Gst
.MessageType
.ERROR
:
246 err
, debug
= message
.parse_error()
247 print ("Error: %s" % err
, debug
)
248 self
.pipel
.set_state(Gst
.State
.NULL
)
249 self
.stream_button
.set_label('Stream')
251 def on_sync_message(self
, bus
, message
):
253 if message
.get_structure().get_name() == 'prepare-window-handle':
254 imagesink
= message
.src
255 imagesink
.set_property('force-aspect-ratio', True)
256 imagesink
.set_window_handle(self
.videowidget
.get_property('window').get_xid())
259 # USE THAT FUNCTION TO GET THE SOURCE CHOICE (ELPHEL OR WEBCAM)
260 def on_file_selected(self
, widget
):
262 self
.multimedia_file
= self
.load_file
.get_filename()
264 def on_stream_clicked(self
, widget
):
266 labelname
= self
.stream_button
.get_label()
267 if labelname
== 'Stream':
268 self
.pipel
.set_state(Gst
.State
.PLAYING
)
269 self
.stream_button
.set_label('ON AIR')
270 elif labelname
== 'ON AIR':
271 self
.pipel
.set_state(Gst
.State
.NULL
)
272 self
.stream_button
.set_label('Stream')
275 if __name__
== "__main__":