#!/usr/bin/env python3.4 # This file is part of Libre-Streamer. # # Libre-Streamer is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Libre-Streamer is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Libre-Streamer. If not, see . # # Copyright (c) 2016 David Testé # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TODO list: # ---------- # - Add a form to fill before start streaming (conf title, name, etc...) # - Add a checkbox to enable/disable options (storing/streaming - storing only - stream only - etc...) # - Add a function to get the ip address of the camera automatically (see github.com/paulmilliken) # - Create a module for the pipeline construction section to clarify the code # - Implement 2016 edition pipeline, see file 'gstream_pipeline_by_quidam' # - Create a module for the network configuration (fan/cpu, ifconfig, stream server,etc) # - Generate a log file during runtime. (e.g. this will let you know if the network configuration # and the pipeline construction went well (or not)) # - Add an input source choice for the user (camera on IP or webcam) # - Add a VU-meter to check if audio feed is emitting signal # - Add the FSF logo (need to do some pixel art) as an application icon # - Add the FSF logo inside the streamer use the 'textoverlay' method in ElementFactory.make() # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # INFO: run the following command in a terminal before launching libre-streamer to get a error log. # GST_DEBUG=3,python:5,gnl*:5 ./libre-streamer.py | tee -a log 2>&1 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! __author__ = 'David Testé' __licence__ = 'GPLv3' __version__ = 0.1 __maintainer__ = 'David Testé' __email__ = 'soonum@gnu.org' __status__ = 'Prototype' import sys import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gst from gi.repository import GdkX11 from gi.repository import GstVideo class Streamgui(object): def __init__(self): self.multimedia_file="" # Create the global pipeline (might wanna use a general bin instead) self.pipel = self.constructpipeline() # Create the GUI self.win = Gtk.Window() self.win.set_title("Libre-Streamer") self.win.connect("delete_event", lambda w,e: Gtk.main_quit()) vbox = Gtk.VBox(False, 0) hbox = Gtk.HBox(False, 0) self.load_file = Gtk.FileChooserButton("Choose Audio File") self.stream_button = Gtk.Button("Stream") self.videowidget = Gtk.DrawingArea() self.videowidget.set_size_request(600, 400) self.load_file.connect("selection-changed", self.on_file_selected) self.stream_button.connect("clicked", self.on_stream_clicked) hbox.pack_start(self.stream_button, False, True, 0) vbox.pack_start(self.load_file, False, True, 0) vbox.pack_start(self.videowidget, True, True, 0) vbox.pack_start(hbox, False, True, 0) self.win.add(vbox) self.win.set_position(Gtk.WindowPosition.CENTER) self.win.show_all() self.xid = self.videowidget.get_property('window').get_xid() self.connectsignals() def connectsignals(self): """Connects signals with the methods""" bus = self.pipel.get_bus() bus.add_signal_watch() bus.enable_sync_message_emission() # Used to get messages that GStreamer emits. bus.connect("message", self.on_message) # Used for connecting video to your application. bus.connect("sync-message::element", self.on_sync_message) # Connect the rtpdepay signal self.videosrc.connect("pad-added", self.on_pad_added_to_rtspsrc) if self.decodebin: self.decodebin.connect("pad-added", self.on_pad_added_to_decodebin) ## elif self.jpegdec: ## self.jpegdec.connect("pad-added", self.on_pad_added_to_jpegdec) def on_pad_added_to_rtspsrc(self, rtspsrc, pad): ## if self.decodebin: ## sinkpad = self.decodebin.get_static_pad('sink') ## elif self.rtpjpegdepay: ## sinkpad = self.rtpjpegdepay.get_static_pad('sink') sinkpad = self.queuev_1.get_static_pad('sink') pad.link(sinkpad) print('[DEBUG] rtspsrc LINKED') def on_pad_added_to_decodebin(self, decodebin, pad): screen_sinkpad = self.screensink.get_static_pad('sink') pad.link(screen_sinkpad) print('[DEBUG] decodebin LINKED') def on_pad_added_to_jpegdec(self, jpegdec, pad): screen_sinkpad = self.screensink.get_static_pad('sink') pad.link(screen_sinkpad) print('[DEBUG] decodebin LINKED') def constructpipeline (self): """Add and link elements in a GStreamer pipeline""" # Create the pipelines instance. self.streampipe = Gst.Pipeline() # Define pipeline elements. self.videosrc = Gst.ElementFactory.make('rtspsrc', 'videosrc') self.videosrc.set_property('location', 'rtsp://192.168.48.2:554') self.videosrc.set_property('latency', 100) self.decodebin = Gst.ElementFactory.make('decodebin', 'decodebin') ## Video source for testing purpose: ## self.videosrc = Gst.ElementFactory.make('videotestsrc', 'videosrc') self.rtpjpegdepay = Gst.ElementFactory.make('rtpjpegdepay', 'rtpjpegdepay') self.jpegdec = Gst.ElementFactory.make('jpegdec', 'jpegdec') self.jpegdec.set_property('max-errors', -1) self.mkvmux = Gst.ElementFactory.make('matroskamux', 'mkvmux') self.tee_rawvideo = Gst.ElementFactory.make('tee', 'tee_rawvideo') self.queuev_1 = Gst.ElementFactory.make('queue', 'queuev_1') self.queuev_2 = Gst.ElementFactory.make('queue', 'queuev_2') self.queuev_3 = Gst.ElementFactory.make('queue', 'queuev_3') self.queuev_4 = Gst.ElementFactory.make('queue', 'queuev_4') self.disksink_rawvideo = Gst.ElementFactory.make('filesink') #[TO DO]: File location has to be defined self.disksink_rawvideo.set_property('location', 'popo_rawvideo') self.screensink = Gst.ElementFactory.make('xvimagesink', 'screensink') # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # IMPORTANT: # for 'webmmux' element streamable=True MUST be set! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Elements to test: #--------------------------------------------------------------------------- self.audiosrc = Gst.ElementFactory.make('pulsesrc', 'audiosrc') self.vorbisenc = Gst.ElementFactory.make('vorbisenc', 'vorbisenc') ## scaling_caps = Gst.Caps('video/x-raw, width=640, height=360') self.scaling = Gst.ElementFactory.make('videoscale', 'scaling') self.vp8enc = Gst.ElementFactory.make('vp8enc', 'vp8enc') self.vp8enc.set_property('min_quantizer', 1) self.vp8enc.set_property('max_quantizer', 13) self.vp8enc.set_property('cpu-used', 5) self.vp8enc.set_property('deadline', 42000) self.vp8enc.set_property('threads', 2) self.vp8enc.set_property('sharpness', 7) self.webmmux = Gst.ElementFactory.make('webmmux', 'webmmux') self.webmmux.set_property('streamable', True) self.tee_streamvideo = Gst.ElementFactory.make('tee', 'tee_streamvideo') self.tee_streamaudio = Gst.ElementFactory.make('tee', 'tee_streamaudio') self.queuea_1 = Gst.ElementFactory.make('queue', 'queuea_1') self.queuea_2 = Gst.ElementFactory.make('queue', 'queuea_2') self.disksink_audio = Gst.ElementFactory.make('filesink') self.disksink_audio.set_property('location', 'popo_audio') self.disksink_stream = Gst.ElementFactory.make('filesink') self.disksink_stream.set_property('location', 'popo_stream') self.icecastsink_audio = Gst.ElementFactory.make('shout2send', 'icecastsink_audio') # Configuration should be written on a file locally to keep safe private addresses self.icecastsink_audio.set_property('ip', 'live2.fsf.org') self.icecastsink_audio.set_property('port', 80) self.icecastsink_audio.set_property('mount', 'testaudio.ogv') self.icecastsink_audio.set_property('password', '') self.icecastsink_stream = Gst.ElementFactory.make('shout2send', 'icecastsink_stream') self.icecastsink_stream.set_property('ip', 'live2.fsf.org') self.icecastsink_stream.set_property('port', 80) self.icecastsink_stream.set_property('mount', 'teststream.ogv') self.icecastsink_stream.set_property('password', '') #--------------------------------------------------------------------------- # Add the elements to the pipeline. # Test of the first two lines of quidam's pipeline: self.streampipe.add(self.videosrc) ## self.streampipe.add(self.decodebin) self.streampipe.add(self.queuev_1) self.streampipe.add(self.rtpjpegdepay) self.streampipe.add(self.queuev_2) self.streampipe.add(self.jpegdec) self.streampipe.add(self.tee_rawvideo) self.streampipe.add(self.queuev_3) self.streampipe.add(self.mkvmux) self.streampipe.add(self.queuev_4) self.streampipe.add(self.disksink_rawvideo) self.streampipe.add(self.screensink) # Link the elements in the pipeline. ## self.videosrc.link(self.decodebin) self.queuev_1.link(self.rtpjpegdepay) ## self.rtpjpegdepay.link(self.queuev_2) ## self.rtpjpegdepay.link(self.jpegdec) self.rtpjpegdepay.link(self.tee_rawvideo) ## self.queuev_2.link(self.jpegdec) ## self.jpegdec.link(self.tee_rawvideo) ## self.jpegdec.link(self.queuev_3) self.tee_rawvideo.link(self.queuev_2) self.tee_rawvideo.link(self.jpegdec) ## self.tee_rawvideo.link(self.queuev_3) self.queuev_2.link(self.mkvmux) self.mkvmux.link(self.queuev_4) self.queuev_4.link(self.disksink_rawvideo) ## self.decodebin.link(self.screensink) ## self.queuev_3.link(self.disksink_rawvideo) self.jpegdec.link(self.queuev_3) self.queuev_3.link(self.screensink) return self.streampipe def on_message(self, bus, message): t = message.type if t == Gst.MessageType.EOS: self.pipel.set_state(Gst.State.NULL) self.stream_button.set_label('Stream') elif t == Gst.MessageType.ERROR: err, debug = message.parse_error() print ("Error: %s" % err, debug) self.pipel.set_state(Gst.State.NULL) self.stream_button.set_label('Stream') def on_sync_message(self, bus, message): if message.get_structure().get_name() == 'prepare-window-handle': imagesink = message.src imagesink.set_property('force-aspect-ratio', True) imagesink.set_window_handle(self.videowidget.get_property('window').get_xid()) # USE THAT FUNCTION TO GET THE SOURCE CHOICE (ELPHEL OR WEBCAM) def on_file_selected(self, widget): self.multimedia_file = self.load_file.get_filename() def on_stream_clicked(self, widget): labelname = self.stream_button.get_label() if labelname == 'Stream': self.pipel.set_state(Gst.State.PLAYING) self.stream_button.set_label('ON AIR') elif labelname == 'ON AIR': self.pipel.set_state(Gst.State.NULL) self.stream_button.set_label('Stream') if __name__ == "__main__": Gst.init() Streamgui() Gtk.main()