import logging from gi.repository import Gst from lib.args import Args from lib.config import Config from lib.clock import Clock class VideoDisplay(object): """Displays a Voctomix-Video-Stream into a GtkWidget""" def __init__(self, drawing_area, port, width=None, height=None, play_audio=False, level_callback=None): self.log = logging.getLogger('VideoDisplay[%u]' % port) self.drawing_area = drawing_area self.level_callback = level_callback if Config.has_option('previews', 'videocaps'): previewcaps = Config.get('previews', 'videocaps') else: previewcaps = Config.get('mix', 'videocaps') use_previews = (Config.getboolean('previews', 'enabled') and Config.getboolean('previews', 'use')) # Preview-Ports are Raw-Ports + 1000 if use_previews: self.log.info('using encoded previews instead of raw-video') port += 1000 vdec = 'image/jpeg ! jpegdec' if Config.has_option('previews', 'vaapi'): try: decoder = Config.get('previews', 'vaapi') decoders = { 'h264': 'video/x-h264 ! avdec_h264', 'jpeg': 'image/jpeg ! jpegdec', 'mpeg2': 'video/mpeg,mpegversion=2 ! mpeg2dec' } vdec = decoders[decoder] except Exception as e: self.log.error(e) else: self.log.info('using raw-video instead of encoded-previews') # Setup Server-Connection, Demuxing and Decoding pipeline = """ tcpclientsrc host={host} port={port} blocksize=1048576 ! queue ! matroskademux name=demux """ if use_previews: pipeline += """ demux. ! {vdec} ! {previewcaps} ! queue ! """ else: pipeline += """ demux. ! {vcaps} ! queue ! """ # Video Display videosystem = Config.get('videodisplay', 'system') self.log.debug('Configuring for Video-System %s', videosystem) if videosystem == 'gl': pipeline += """ glupload ! glcolorconvert ! glimagesinkelement """ elif videosystem == 'xv': pipeline += """ xvimagesink """ elif videosystem == 'x': prescale_caps = 'video/x-raw' if width and height: prescale_caps += ',width=%u,height=%u' % (width, height) pipeline += """ videoconvert ! videoscale ! {prescale_caps} ! ximagesink """.format(prescale_caps=prescale_caps) else: raise Exception( 'Invalid Videodisplay-System configured: %s' % videosystem ) # If an Audio-Path is required, # add an Audio-Path through a level-Element if self.level_callback or play_audio: pipeline += """ demux. ! {acaps} ! queue ! level name=lvl interval=50000000 ! """ # If Playback is requested, push fo pulseaudio if play_audio: pipeline += """ pulsesink """ # Otherwise just trash the Audio else: pipeline += """ fakesink """ pipeline = pipeline.format( acaps=Config.get('mix', 'audiocaps'), vcaps=Config.get('mix', 'videocaps'), previewcaps=previewcaps, host=Args.host if Args.host else Config.get('server', 'host'), vdec=vdec, port=port, ) self.log.debug('Creating Display-Pipeline:\n%s', pipeline) self.pipeline = Gst.parse_launch(pipeline) self.pipeline.use_clock(Clock) self.drawing_area.realize() self.xid = self.drawing_area.get_property('window').get_xid() self.log.debug('Realized Drawing-Area with xid %u', self.xid) bus = self.pipeline.get_bus() bus.add_signal_watch() bus.enable_sync_message_emission() bus.connect('message::error', self.on_error) bus.connect("sync-message::element", self.on_syncmsg) if self.level_callback: bus.connect("message::element", self.on_level) self.log.debug('Launching Display-Pipeline') self.pipeline.set_state(Gst.State.PLAYING) def on_syncmsg(self, bus, msg): if msg.get_structure().get_name() == "prepare-window-handle": self.log.info('Setting imagesink window-handle to %s', self.xid) msg.src.set_window_handle(self.xid) def on_error(self, bus, message): self.log.debug('Received Error-Signal on Display-Pipeline') (error, debug) = message.parse_error() self.log.debug('Error-Details: #%u: %s', error.code, debug) def on_level(self, bus, msg): if msg.src.name != 'lvl': return if msg.type != Gst.MessageType.ELEMENT: return rms = msg.get_structure().get_value('rms') peak = msg.get_structure().get_value('peak') decay = msg.get_structure().get_value('decay') self.level_callback(rms, peak, decay)