Creation of the raw video pipeline [still prototype]
[libre-streamer.git] / stream_2016 / libre-streamer.py
1 #!/usr/bin/env python3.4
2
3 # This file is part of Libre-Streamer.
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 # Copyright (c) 2016 David Testé
19
20 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 # TODO list:
22 # ----------
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 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36
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 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
41
42 __author__ = 'David Testé'
43 __licence__ = 'GPLv3'
44 __version__ = 0.1
45 __maintainer__ = 'David Testé'
46 __email__ = 'soonum@gnu.org'
47 __status__ = 'Prototype'
48
49
50 import sys
51
52 import gi
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
58
59
60 class Streamgui(object):
61
62
63 def __init__(self):
64
65 self.multimedia_file=""
66 # Create the global pipeline (might wanna use a general bin instead)
67 self.pipel = self.constructpipeline()
68 # Create the GUI
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)
81
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)
86 self.win.add(vbox)
87 self.win.set_position(Gtk.WindowPosition.CENTER)
88 self.win.show_all()
89
90 self.xid = self.videowidget.get_property('window').get_xid()
91 self.connectsignals()
92
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)
104 if self.decodebin:
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)
108
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')
115 pad.link(sinkpad)
116 print('[DEBUG] rtspsrc LINKED')
117
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')
122
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')
127
128 def constructpipeline (self):
129 """Add and link elements in a GStreamer pipeline"""
130 # Create the pipelines instance.
131 self.streampipe = Gst.Pipeline()
132
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)
137
138 self.decodebin = Gst.ElementFactory.make('decodebin', 'decodebin')
139
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')
151
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')
156
157 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
158 # IMPORTANT:
159 # for 'webmmux' element streamable=True MUST be set!
160 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
161
162 # Elements to test:
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)
177
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')
182
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')
187
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 #---------------------------------------------------------------------------
200
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)
215
216
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)
236
237 return self.streampipe
238
239 def on_message(self, bus, message):
240
241 t = message.type
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')
250
251 def on_sync_message(self, bus, message):
252
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())
257
258
259 # USE THAT FUNCTION TO GET THE SOURCE CHOICE (ELPHEL OR WEBCAM)
260 def on_file_selected(self, widget):
261
262 self.multimedia_file = self.load_file.get_filename()
263
264 def on_stream_clicked(self, widget):
265
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')
273
274
275 if __name__ == "__main__":
276 Gst.init()
277 Streamgui()
278 Gtk.main()