Automatic call of backup pipeline when feed is lost.
[libre-streamer.git] / stream_2016 / gstconf.py
... / ...
CommitLineData
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
20from os import rename
21from os import listdir
22
23import gi
24from gi.repository import Gst
25from gi.repository import GstVideo
26
27# Pathname has to be defined
28PATHNAME = ''
29AUDIO_DEFAULT = PATHNAME + 'AUDIO_DEFAULT'
30RAWVIDEO_DEFAULT = PATHNAME + 'RAWVIDEO_DEFAULT'
31STREAM_DEFAULT = PATHNAME + 'STREAM_DEFAULT'
32
33
34class New_user_pipeline():
35
36
37 def __init__(self, feed='main'):
38 self.feed = feed
39 if self.feed == 'main':
40 self.user_pipeline = self.create_gstreamer_pipeline()
41 elif self.feed == 'backup':
42 self.user_pipeline = self.create_gstreamer_pipeline(feed='backup')
43
44 def create_video_sources(self):
45 """Create video inputs from various sources."""
46 self.videosrc = Gst.ElementFactory.make('rtspsrc', 'videosrc')
47 self.videosrc.set_property('location', 'rtsp://192.168.48.2:554')
48 self.videosrc.set_property('latency', 0)
49 if self.feed == 'backup':
50 self.videosrc_backup = Gst.ElementFactory.make('v4l2src',
51 'videosrc_backup')
52 device_location = self.find_webcam_device()
53 print ('[INFO] Webcam device location: ', device_location)
54 self.videosrc_backup.set_property('device', device_location)
55
56 def find_webcam_device(self):
57 """Look out for the USB webcam device."""
58 devices = [dev for dev in listdir('/dev/') if 'video' in dev]
59 for item in devices:
60 # In case of computer having a built-in webcam
61 if item != 'video0' and len(devices) > 1:
62 return '/dev/' + item
63 # Without built-in webcam
64 elif len(devices) == 1:
65 return '/dev/video0'
66 print('[ERROR] No webcam device found.')
67
68 def create_pipeline_callbacks(self):
69 """Callbacks to connect dynamically created pads."""
70 self.videosrc.connect('pad-added', self.on_pad_added_to_rtspsrc)
71
72 def on_pad_added_to_rtspsrc(self, rtspsrc, pad):
73 """Connect the dynamic 'src'pad of an RTSP source."""
74 sinkpad = self.queuev_1.get_static_pad('sink')
75 pad.link(sinkpad)
76
77 def create_audio_sources(self):
78 """Create audio inputs from various sources."""
79 self.audiosrc = Gst.ElementFactory.make('pulsesrc', 'audiosrc')
80## self.videosrc.set_property('latency', 0)
81
82 def create_audiolevel_plugin(self):
83 """Create audio level plugin to feed a vu-meter."""
84 self.audiolevel = Gst.ElementFactory.make('level', 'audiolevel')
85 self.audiolevel.set_property('interval', 200000000)
86
87 def create_filesink(self):
88 """Create storable output elements."""
89 self.disksink_rawvideo = Gst.ElementFactory.make('filesink')
90 #[TO DO]: File location has to be defined
91 self.disksink_rawvideo.set_property('location', RAWVIDEO_DEFAULT)
92 self.disksink_audio = Gst.ElementFactory.make('filesink')
93 self.disksink_audio.set_property('location', AUDIO_DEFAULT)
94 self.disksink_stream = Gst.ElementFactory.make('filesink')
95 self.disksink_stream.set_property('location', STREAM_DEFAULT)
96
97 def create_streamsink(self):
98 """Create streamable output elements."""
99 # To local screen:
100 self.screensink = Gst.ElementFactory.make('xvimagesink', 'screensink')
101 self.screensink.set_property('sync', False)
102 # To icecast server:
103 self.icecastsink_audio = Gst.ElementFactory.make('shout2send', 'icecastsink_audio')
104## Configuration should be written on a file locally to keep safe private addresses
105 self.icecastsink_audio.set_property('ip', 'live2.fsf.org')
106 self.icecastsink_audio.set_property('port', 80)
107 self.icecastsink_audio.set_property('mount', 'testaudio.ogv')
108 self.icecastsink_audio.set_property('password', 'thahw3Wiez')
109 self.icecastsink_stream = Gst.ElementFactory.make('shout2send', 'icecastsink_stream')
110 self.icecastsink_stream.set_property('ip', 'live2.fsf.org')
111 self.icecastsink_stream.set_property('port', 80)
112 self.icecastsink_stream.set_property('mount', 'teststream.ogv')
113 self.icecastsink_stream.set_property('password', 'thahw3Wiez')
114
115 def create_payloader_elements(self):
116 pass
117
118 def create_depayloader_elements(self):
119 self.rtpjpegdepay = Gst.ElementFactory.make('rtpjpegdepay', 'rtpjpegdepay')
120
121 def create_encoder_elements(self):
122 # Audio encoders:
123 self.vorbisenc = Gst.ElementFactory.make('vorbisenc', 'vorbisenc')
124 # Video encoders:
125 self.vp8enc = Gst.ElementFactory.make('vp8enc', 'vp8enc')
126 self.vp8enc.set_property('min_quantizer', 1)
127 self.vp8enc.set_property('max_quantizer', 13)
128 self.vp8enc.set_property('cpu-used', 5)
129 self.vp8enc.set_property('deadline', 42000)
130 self.vp8enc.set_property('threads', 2)
131 self.vp8enc.set_property('sharpness', 7)
132
133 def create_decoder_elements(self):
134 self.jpegdec = Gst.ElementFactory.make('jpegdec', 'jpegdec')
135 self.jpegdec.set_property('max-errors', -1)
136
137 def create_muxer_elements(self):
138 self.oggmux = Gst.ElementFactory.make('oggmux', 'oggmux')
139 self.mkvmux = Gst.ElementFactory.make('matroskamux', 'mkvmux')
140 self.webmmux = Gst.ElementFactory.make('webmmux', 'webmmux')
141 self.webmmux.set_property('streamable', True)
142
143 def create_demuxer_elements(self):
144 pass
145
146 def create_filtering_elements(self):
147 self.scaling = Gst.ElementFactory.make('videoscale', 'scaling')
148 caps = Gst.caps_from_string('video/x-raw, width=(int)640, height=(int)360')
149 self.capsfilter = Gst.ElementFactory.make('capsfilter', 'capsfilter')
150 self.capsfilter.set_property('caps', caps)
151
152 caps_backup = Gst.caps_from_string('video/x-raw, width=(int)640, height=(int)360')
153 self.capsfilter_backup = Gst.ElementFactory.make('capsfilter', 'capsfilter_backup')
154 self.capsfilter_backup.set_property('caps', caps_backup)
155
156 def create_tee_elements(self):
157 """Create tee elements to divide feeds."""
158 self.tee_rawvideo = Gst.ElementFactory.make('tee', 'tee_rawvideo')
159 self.tee_videodecoded = Gst.ElementFactory.make('tee', 'tee_videodecoded')
160 self.tee_streamfull = Gst.ElementFactory.make('tee', 'tee_streamfull')
161 self.tee_rawaudio = Gst.ElementFactory.make('tee', 'tee_rawaudio')
162 self.tee_streamaudio = Gst.ElementFactory.make('tee', 'tee_streamaudio')
163
164 def connect_tee(self,
165 tee_element,
166 input_element,
167 output_element_1,
168 output_element_2,):
169 """Links input and outputs of a given tee element."""
170 # Find a way to check if the element given are in the pipeline
171 # then pass the result to the 'if' statement.
172 ## argcheck = [True for arg in locals() if arg in 'the_list_of_elements_added']
173 ## print('[DEBUG] ArgList check: ', argcheck)
174 ## if False not in argcheck
175 if True:
176 input_element.link(tee_element)
177 tee_element.link(output_element_1)
178 tee_element.link(output_element_2)
179 else:
180 print('[ERROR] Couldn\'t link the tee. Element(s) probably not in the pipeline ')
181
182 def create_queues(self):
183 # For video feed:
184 self.queuev_1 = Gst.ElementFactory.make('queue', 'queuev_1')
185 self.queuev_2 = Gst.ElementFactory.make('queue', 'queuev_2')
186 self.queuev_3 = Gst.ElementFactory.make('queue', 'queuev_3')
187 self.queuev_4 = Gst.ElementFactory.make('queue', 'queuev_4')
188 self.queuev_5 = Gst.ElementFactory.make('queue', 'queuev_5')
189 self.queuev_6 = Gst.ElementFactory.make('queue', 'queuev_6')
190 # For audio feed:
191 self.queuea_1 = Gst.ElementFactory.make('queue', 'queuea_1')
192 self.queuea_2 = Gst.ElementFactory.make('queue', 'queuea_2')
193 self.queuea_3 = Gst.ElementFactory.make('queue', 'queuea_3')
194 self.queuea_4 = Gst.ElementFactory.make('queue', 'queuea_4')
195 self.queuea_5 = Gst.ElementFactory.make('queue', 'queuea_5')
196 # For audio+video muxer:
197 self.queuem_1 = Gst.ElementFactory.make('queue', 'queuem_1')
198 self.queuem_2 = Gst.ElementFactory.make('queue', 'queuem_2')
199
200 def create_pipeline_elements(self):
201 print('Pipeline creation state: creating elements... ', end='')
202 # Inputs elements:
203 self.create_video_sources()
204 self.create_audio_sources()
205 # Middle elements:
206 self.create_audiolevel_plugin()
207 self.create_payloader_elements()
208 self.create_depayloader_elements()
209 self.create_encoder_elements()
210 self.create_decoder_elements()
211 self.create_muxer_elements()
212 self.create_filtering_elements()
213 self.create_tee_elements()
214 self.create_queues()
215 # Output elements:
216 self.create_filesink()
217 self.create_streamsink()
218 print('created')
219
220
221 def add_elements_to_pipeline(self, feed='main'):
222 print('Pipeline creation state: adding elements... ', end='')
223
224 if feed == 'main':
225 # Add here the elments associated with the RTSP feed
226 # Inputs elements:
227 self.streampipe.add(self.videosrc)
228 self.streampipe.add(self.audiosrc)
229 # Middle elements:
230 self.streampipe.add(self.audiolevel)
231 self.streampipe.add(self.rtpjpegdepay)
232 self.streampipe.add(self.jpegdec)
233 self.streampipe.add(self.tee_rawvideo)
234 self.streampipe.add(self.mkvmux)
235 self.streampipe.add(self.vorbisenc)
236 self.streampipe.add(self.oggmux)
237 self.streampipe.add(self.scaling)
238 self.streampipe.add(self.capsfilter)
239 self.streampipe.add(self.vp8enc)
240 self.streampipe.add(self.webmmux)
241 self.streampipe.add(self.tee_rawaudio)
242 self.streampipe.add(self.tee_streamaudio)
243 self.streampipe.add(self.tee_videodecoded)
244 self.streampipe.add(self.tee_streamfull)
245 self.streampipe.add(self.queuev_1)
246 self.streampipe.add(self.queuev_2)
247 self.streampipe.add(self.queuev_3)
248 self.streampipe.add(self.queuev_4)
249 self.streampipe.add(self.queuev_5)
250 self.streampipe.add(self.queuev_6)
251 self.streampipe.add(self.queuea_1)
252 self.streampipe.add(self.queuea_2)
253 self.streampipe.add(self.queuea_3)
254 self.streampipe.add(self.queuea_4)
255 self.streampipe.add(self.queuea_5)
256 self.streampipe.add(self.queuem_1)
257 self.streampipe.add(self.queuem_2)
258 # Outputs elements:
259 self.streampipe.add(self.screensink)
260 self.streampipe.add(self.disksink_rawvideo)
261 self.streampipe.add(self.disksink_audio)
262 self.streampipe.add(self.disksink_stream)
263 self.streampipe.add(self.icecastsink_audio)
264 self.streampipe.add(self.icecastsink_stream)
265 elif feed == 'backup':
266 # Add here the elments associated with the WEBCAM feed
267 self.streampipe.add(self.videosrc_backup)
268 self.streampipe.add(self.capsfilter_backup)
269 self.streampipe.add(self.queuev_1)
270 self.streampipe.add(self.screensink)
271 print ('BACKUP OK...', end='')
272 print('added')
273
274 def link_pipeline_elements(self, feed='main'):
275 """Link all elements with static pads."""
276 print('Pipeline creation state: linking elements... ', end='')
277
278 if feed == 'main':
279 # linking here RTSP feed
280 # Video feed:
281 self.queuev_1.link(self.rtpjpegdepay)
282 self.connect_tee(self.tee_rawvideo,
283 self.rtpjpegdepay,
284 self.queuev_2,
285 self.jpegdec,)
286 self.queuev_2.link(self.mkvmux)
287 self.mkvmux.link(self.queuev_4)
288 self.queuev_4.link(self.disksink_rawvideo)
289 self.connect_tee(self.tee_videodecoded,
290 self.jpegdec,
291 self.queuev_3,
292 self.scaling,)
293 self.queuev_3.link(self.screensink)
294 # Audio feed:
295 self.audiosrc.link(self.audiolevel)
296 self.audiolevel.link(self.queuea_1)
297 self.queuea_1.link(self.vorbisenc)
298 self.connect_tee(self.tee_rawaudio,
299 self.vorbisenc,
300 self.queuea_2,
301 self.queuea_5,)
302 self.queuea_2.link(self.oggmux)
303 self.connect_tee(self.tee_streamaudio,
304 self.oggmux,
305 self.queuea_3,
306 self.queuea_4,)
307 self.queuea_3.link(self.disksink_audio)
308 self.queuea_4.link(self.icecastsink_audio)
309 self.queuea_5.link(self.webmmux)
310 # Stream (audio+video) feed:
311 self.scaling.link(self.capsfilter)
312 self.capsfilter.link(self.vp8enc)
313 self.vp8enc.link(self.queuev_6)
314 self.queuev_6.link(self.webmmux)
315 self.connect_tee(self.tee_streamfull,
316 self.webmmux,
317 self.queuem_1,
318 self.queuem_2,)
319 self.queuem_1.link(self.disksink_stream)
320 self.queuem_2.link(self.icecastsink_stream)
321 elif feed == 'backup':
322 # linking here backup feed (WEBCAM)
323 self.videosrc_backup.link(self.capsfilter_backup)
324 self.capsfilter_backup.link(self.queuev_1)
325 self.queuev_1.link(self.screensink)
326 print('BACKUP OK...', end='')
327 print('linked')
328
329 def create_gstreamer_pipeline(self, feed='main'):
330 # New empty pipeline:
331 self.streampipe = Gst.Pipeline()
332 self.create_pipeline_elements()
333 # Setting-up:
334 if feed == 'main':
335 self.add_elements_to_pipeline()
336 self.link_pipeline_elements()
337 self.create_pipeline_callbacks()
338 elif feed == 'backup':
339 self.add_elements_to_pipeline(feed='backup')
340 self.link_pipeline_elements(feed='backup')
341
342 global bus
343 bus = self.streampipe.get_bus()
344 bus.add_signal_watch()
345 bus.enable_sync_message_emission()
346 # Used to get messages that GStreamer emits.
347 bus.connect("message", self.on_message)
348
349 print('Pipeline creation state: successfully done.')
350 return self.streampipe
351
352 def create_gstreamer_backup_pipeline(self):
353 """Creates a backup pipeline based on a webcam feed."""
354 print("CREATE HERE A BACKUP PIPELINE")
355
356 def on_message(self, bus, message):
357 #
358## print("[MESSAGE]", message.get_structure().get_name()) # [DEBUG]
359 #
360 t = message.type
361 if t == Gst.MessageType.EOS:
362 self.streampipe.set_state(Gst.State.NULL)
363 elif t == Gst.MessageType.ERROR:
364 err, debug = message.parse_error()
365 print ("Error: %s" % err, debug)
366 self.streampipe.set_state(Gst.State.NULL)
367## self.create_gstreamer_backup_pipeline()
368## return 'ERROR'
369
370 def stream_play(self):
371 self.streampipe.set_state(Gst.State.PLAYING)
372 print('[INFO] PLAYING State resquested')
373
374 def stream_stop(self):
375 self.streampipe.set_state(Gst.State.NULL)
376
377 def get_stream_state(self):
378 print(self.streampipe.get_state(self))
379##[FIXME] return self.streampipe.get_state()
380
381 def set_filenames(self, string):
382 """Sets filename and location for each sink."""
383 filename = string
384 audio = PATHNAME + filename + '_AUDIO'
385 rawvideo = PATHNAME + filename + '_RAWVIDEO'
386 stream = PATHNAME + filename + '_STREAM'
387 rename(AUDIO_DEFAULT, audio)
388 rename(RAWVIDEO_DEFAULT, rawvideo)
389 rename(STREAM_DEFAULT, stream)
390
391def get_gstreamer_bus():
392 return bus
393