From c69b7397171e49221a42c0c49154d29722d38af0 Mon Sep 17 00:00:00 2001 From: MaZderMind Date: Tue, 16 Jun 2015 16:59:04 +0200 Subject: Implement Stream-Blanker --- voctocore/README.md | 14 ++-- voctocore/default-config.ini | 4 + voctocore/lib/audiomix.py | 5 ++ voctocore/lib/commands.py | 20 +++++ voctocore/lib/pipeline.py | 31 +++++++- voctocore/lib/streamblanker.py | 100 +++++++++++++++++++++++++ voctocore/lib/videomix.py | 5 ++ voctocore/scripts/set-stream-blank-nostream.sh | 2 + voctocore/scripts/set-stream-blank-pause.sh | 2 + voctocore/scripts/set-stream-live.sh | 2 + 10 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 voctocore/lib/streamblanker.py create mode 100755 voctocore/scripts/set-stream-blank-nostream.sh create mode 100755 voctocore/scripts/set-stream-blank-pause.sh create mode 100755 voctocore/scripts/set-stream-live.sh (limited to 'voctocore') diff --git a/voctocore/README.md b/voctocore/README.md index 2520846..e47ec51 100644 --- a/voctocore/README.md +++ b/voctocore/README.md @@ -1,10 +1,14 @@ # Server-Pipeline Structure ```` -16000 BackgroundSource - \ - --> VideoMix - / \ - / \ /-> StreamBlanker -> StreamOutputPort 15000 + +17000… VSource (Stream-Blanker) -----\ +18000 ASource (Stream-Blanker) ------\ + \ +16000 VSource (Background) \ + \ \ + --> VideoMix \ + / \ -> StreamBlanker -> StreamOutputPort 15000 + / \ / / ------> OutputPort 11000 / / \-> Encoder* -> PreviewPort* 12000 / / diff --git a/voctocore/default-config.ini b/voctocore/default-config.ini index b9c10a2..84b7511 100644 --- a/voctocore/default-config.ini +++ b/voctocore/default-config.ini @@ -26,3 +26,7 @@ enabled=true ; default to mix-videocaps ;videocaps=video/x-raw,width=720,height=576,framerate=25/1 + +[stream-blanker] +enabled=true +sources=pause,nostream diff --git a/voctocore/lib/audiomix.py b/voctocore/lib/audiomix.py index c37d215..294155b 100644 --- a/voctocore/lib/audiomix.py +++ b/voctocore/lib/audiomix.py @@ -31,6 +31,11 @@ class AudioMix(object): tee. ! queue ! interaudiosink channel=audio_mix_preview """ + if Config.getboolean('stream-blanker', 'enabled'): + pipeline += """ + tee. ! queue ! interaudiosink channel=audio_mix_streamblanker + """ + for idx, name in enumerate(self.names): pipeline += """ interaudiosrc channel=audio_{name}_mixer ! diff --git a/voctocore/lib/commands.py b/voctocore/lib/commands.py index c16358b..f0b8011 100644 --- a/voctocore/lib/commands.py +++ b/voctocore/lib/commands.py @@ -10,6 +10,7 @@ class ControlServerCommands(): self.pipeline = pipeline self.sources = Config.getlist('mix', 'sources') + self.blankersources = Config.getlist('stream-blanker', 'sources') def decodeSourceName(self, src_name_or_id): if isinstance(src_name_or_id, str): @@ -21,6 +22,16 @@ class ControlServerCommands(): if src_name_or_id < 0 or src_name_or_id >= len(self.sources): raise IndexError("source %s unknown" % src_name_or_id) + def decodeBlankerSourceName(self, src_name_or_id): + if isinstance(src_name_or_id, str): + try: + return self.blankersources.index(src_name_or_id) + except Exception as e: + raise IndexError("source %s unknown" % src_name_or_id) + + if src_name_or_id < 0 or src_name_or_id >= len(self.blankersources): + raise IndexError("source %s unknown" % src_name_or_id) + def message(self, *args): return True @@ -48,3 +59,12 @@ class ControlServerCommands(): self.pipeline.vmix.setCompositeMode(mode) return True + + def set_stream_blank(self, src_name_or_id): + src_id = self.decodeBlankerSourceName(src_name_or_id) + self.pipeline.streamblanker.setBlankSource(src_id) + return True + + def set_stream_live(self): + self.pipeline.streamblanker.setBlankSource(None) + return True diff --git a/voctocore/lib/pipeline.py b/voctocore/lib/pipeline.py index 1004b0e..f341491 100644 --- a/voctocore/lib/pipeline.py +++ b/voctocore/lib/pipeline.py @@ -11,6 +11,7 @@ from lib.avrawoutput import AVRawOutput from lib.avpreviewoutput import AVPreviewOutput from lib.videomix import VideoMix from lib.audiomix import AudioMix +from lib.streamblanker import StreamBlanker class Pipeline(object): """mixing, streaming and encoding pipeline constuction and control""" @@ -27,6 +28,7 @@ class Pipeline(object): self.sources = [] self.mirrors = [] self.previews = [] + self.sbsources = [] self.log.info('Creating %u Creating AVSources: %s', len(names), names) for idx, name in enumerate(names): @@ -59,16 +61,41 @@ class Pipeline(object): self.amix = AudioMix() port = 16000 - self.log.info('Videomixer Background-Source-Port at tcp-port %u', port) + self.log.info('Creating Mixer-Background VSource at tcp-port %u', port) self.bgsrc = VSource('background', port) - port = 11000 self.log.info('Creating Mixer-Output at tcp-port %u', port) self.mixout = AVRawOutput('mix_out', port) + if Config.getboolean('previews', 'enabled'): port = 12000 self.log.info('Creating Preview-Output for AVSource %s at tcp-port %u', name, port) self.mixpreview = AVPreviewOutput('mix_preview', port) + + if Config.getboolean('stream-blanker', 'enabled'): + names = Config.getlist('stream-blanker', 'sources') + if len(names) < 1: + raise RuntimeException("At least one StreamBlanker-Source must be configured or the StreamBlanker disabled!") + for idx, name in enumerate(names): + port = 17000 + idx + self.log.info('Creating StreamBlanker VSource %s at tcp-port %u', name, port) + + source = VSource('%s_streamblanker' % name, port) + self.sbsources.append(source) + + port = 18000 + self.log.info('Creating StreamBlanker ASource at tcp-port %u', port) + + source = ASource('streamblanker', port) + self.sbsources.append(source) + + + self.log.info('Creating StreamBlanker') + self.streamblanker = StreamBlanker() + + port = 15000 + self.log.info('Creating StreamBlanker-Output at tcp-port %u', port) + self.streamout = AVRawOutput('streamblanker_out', port) diff --git a/voctocore/lib/streamblanker.py b/voctocore/lib/streamblanker.py new file mode 100644 index 0000000..2f4cc58 --- /dev/null +++ b/voctocore/lib/streamblanker.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +import logging +from gi.repository import Gst +from enum import Enum + +from lib.config import Config + +class StreamBlanker(object): + log = logging.getLogger('StreamBlanker') + + def __init__(self): + self.acaps = Config.get('mix', 'audiocaps') + self.vcaps = Config.get('mix', 'videocaps') + + self.names = Config.getlist('stream-blanker', 'sources') + self.log.info('Configuring StreamBlanker video %u Sources', len(self.names)) + + pipeline = """ + compositor name=vmix ! + {vcaps} ! + intervideosink channel=video_streamblanker_out + + audiomixer name=amix ! + {acaps} ! + interaudiosink channel=audio_streamblanker_out + + + intervideosrc channel=video_mix_streamblanker ! + {vcaps} ! + vmix. + + interaudiosrc channel=audio_mix_streamblanker ! + {acaps} ! + amix. + + + interaudiosrc channel=audio_streamblanker ! + {acaps} ! + amix. + """.format( + acaps=self.acaps, + vcaps=self.vcaps + ) + + for name in self.names: + pipeline += """ + intervideosrc channel=video_{name}_streamblanker ! + {vcaps} ! + vmix. + """.format( + name=name, + vcaps=self.vcaps + ) + + self.log.debug('Creating Mixing-Pipeline:\n%s', pipeline) + self.mixingPipeline = Gst.parse_launch(pipeline) + + self.log.debug('Binding Error & End-of-Stream-Signal on Mixing-Pipeline') + self.mixingPipeline.bus.add_signal_watch() + self.mixingPipeline.bus.connect("message::eos", self.on_eos) + self.mixingPipeline.bus.connect("message::error", self.on_error) + + self.log.debug('Initializing Mixer-State') + self.blankSource = None + self.applyMixerState() + + self.log.debug('Launching Mixing-Pipeline') + self.mixingPipeline.set_state(Gst.State.PLAYING) + + def on_eos(self, bus, message): + self.log.debug('Received End-of-Stream-Signal on Mixing-Pipeline') + + def on_error(self, bus, message): + self.log.debug('Received Error-Signal on Mixing-Pipeline') + (error, debug) = message.parse_error() + self.log.debug('Error-Details: #%u: %s', error.code, debug) + + + def applyMixerState(self): + self.applyMixerStateAudio() + self.applyMixerStateVideo() + + def applyMixerStateAudio(self): + mixpad = self.mixingPipeline.get_by_name('amix').get_static_pad('sink_0') + blankpad = self.mixingPipeline.get_by_name('amix').get_static_pad('sink_1') + + mixpad.set_property('volume', int(self.blankSource is None)) + blankpad.set_property('volume', int(self.blankSource is not None)) + + def applyMixerStateVideo(self): + mixpad = self.mixingPipeline.get_by_name('vmix').get_static_pad('sink_0') + mixpad.set_property('alpha', int(self.blankSource is None)) + + for idx, name in enumerate(self.names): + blankpad = self.mixingPipeline.get_by_name('vmix').get_static_pad('sink_%u' % (idx+1)) + blankpad.set_property('alpha', int(self.blankSource == idx)) + + def setBlankSource(self, source): + self.blankSource = source + self.applyMixerState() diff --git a/voctocore/lib/videomix.py b/voctocore/lib/videomix.py index 6784956..3964783 100644 --- a/voctocore/lib/videomix.py +++ b/voctocore/lib/videomix.py @@ -54,6 +54,11 @@ class VideoMix(object): tee. ! queue ! intervideosink channel=video_mix_preview """ + if Config.getboolean('stream-blanker', 'enabled'): + pipeline += """ + tee. ! queue ! intervideosink channel=video_mix_streamblanker + """ + for idx, name in enumerate(self.names): pipeline += """ intervideosrc channel=video_{name}_mixer ! diff --git a/voctocore/scripts/set-stream-blank-nostream.sh b/voctocore/scripts/set-stream-blank-nostream.sh new file mode 100755 index 0000000..abdab4b --- /dev/null +++ b/voctocore/scripts/set-stream-blank-nostream.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo set_stream_blank nostream | nc -q0 localhost 9999 diff --git a/voctocore/scripts/set-stream-blank-pause.sh b/voctocore/scripts/set-stream-blank-pause.sh new file mode 100755 index 0000000..abf3580 --- /dev/null +++ b/voctocore/scripts/set-stream-blank-pause.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo set_stream_blank pause | nc -q0 localhost 9999 diff --git a/voctocore/scripts/set-stream-live.sh b/voctocore/scripts/set-stream-live.sh new file mode 100755 index 0000000..e2584c8 --- /dev/null +++ b/voctocore/scripts/set-stream-live.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo set_stream_live | nc -q0 localhost 9999 -- cgit v1.2.3