Modification of the pipeline to have a better visualisation
[libre-streamer.git] / stream_2016 / libre-streamer.py
CommitLineData
6fdd41d9
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
20# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21# TODO list:
22# ----------
af8c02a4
DT
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)
6fdd41d9 26# - Create a module for the pipeline construction section to clarify the code
af8c02a4
DT
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)
6fdd41d9
DT
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
6fdd41d9 33# - Add the FSF logo (need to do some pixel art) as an application icon
af8c02a4 34# - Add the FSF logo inside the streamer use the 'textoverlay' method in ElementFactory.make()
6fdd41d9
DT
35# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36
af8c02a4
DT
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
6fdd41d9
DT
50import sys
51
52import gi
53gi.require_version('Gtk', '3.0')
54from gi.repository import Gtk
55from gi.repository import Gst
56from gi.repository import GdkX11
57from gi.repository import GstVideo
58
6fdd41d9
DT
59
60class 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
af8c02a4
DT
90 self.xid = self.videowidget.get_property('window').get_xid()
91 self.connectsignals()
92
93 def connectsignals(self):
94 """Connects signals with the methods"""
6fdd41d9
DT
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)
af8c02a4
DT
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')
6fdd41d9 117
af8c02a4
DT
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')
6fdd41d9 122
af8c02a4
DT
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):
6fdd41d9 129 """Add and link elements in a GStreamer pipeline"""
6fdd41d9
DT
130 # Create the pipelines instance.
131 self.streampipe = Gst.Pipeline()
6fdd41d9
DT
132
133 # Define pipeline elements.
af8c02a4
DT
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
6fdd41d9 201 # Add the elements to the pipeline.
af8c02a4 202 # Test of the first two lines of quidam's pipeline:
6fdd41d9 203 self.streampipe.add(self.videosrc)
af8c02a4
DT
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)
6fdd41d9 214 self.streampipe.add(self.screensink)
6fdd41d9 215
af8c02a4
DT
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
6fdd41d9
DT
237 return self.streampipe
238
239 def on_message(self, bus, message):
240
241 t = message.type
242 if t == Gst.MessageType.EOS:
af8c02a4
DT
243 self.pipel.set_state(Gst.State.NULL)
244 self.stream_button.set_label('Stream')
6fdd41d9 245 elif t == Gst.MessageType.ERROR:
6fdd41d9
DT
246 err, debug = message.parse_error()
247 print ("Error: %s" % err, debug)
af8c02a4
DT
248 self.pipel.set_state(Gst.State.NULL)
249 self.stream_button.set_label('Stream')
6fdd41d9
DT
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
6fdd41d9
DT
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
275if __name__ == "__main__":
276 Gst.init()
277 Streamgui()
278 Gtk.main()