summaryrefslogtreecommitdiff
path: root/voctocore
diff options
context:
space:
mode:
Diffstat (limited to 'voctocore')
-rw-r--r--voctocore/README.md14
-rw-r--r--voctocore/default-config.ini4
-rw-r--r--voctocore/lib/audiomix.py5
-rw-r--r--voctocore/lib/commands.py20
-rw-r--r--voctocore/lib/pipeline.py31
-rw-r--r--voctocore/lib/streamblanker.py100
-rw-r--r--voctocore/lib/videomix.py5
-rwxr-xr-xvoctocore/scripts/set-stream-blank-nostream.sh2
-rwxr-xr-xvoctocore/scripts/set-stream-blank-pause.sh2
-rwxr-xr-xvoctocore/scripts/set-stream-live.sh2
10 files changed, 178 insertions, 7 deletions
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