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