aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--voctocore/README.md23
-rw-r--r--voctocore/lib/commands.py39
-rw-r--r--voctocore/lib/controlserver.py118
-rw-r--r--voctocore/lib/pipeline.py8
-rw-r--r--voctocore/lib/video/mix.py63
-rwxr-xr-xvoctocore/scripts/set-composite-none.sh2
-rwxr-xr-xvoctocore/scripts/set-composite-pip.sh2
-rwxr-xr-xvoctocore/scripts/set-composite-side-by-side-equal.sh2
-rwxr-xr-xvoctocore/scripts/set-video-cam1.sh2
-rwxr-xr-xvoctocore/scripts/set-video-cam2.sh2
10 files changed, 204 insertions, 57 deletions
diff --git a/voctocore/README.md b/voctocore/README.md
index 40bb793..7fa069a 100644
--- a/voctocore/README.md
+++ b/voctocore/README.md
@@ -1,3 +1,4 @@
+# Server-Pipeline Structure
````
/-> Encoder -> PreviewPort 12000
/-> VideoMix --> OutputPort 11000
@@ -11,3 +12,25 @@
20000… AudioSrc --> MirrorPort 23000…
\-> Encoder -> PreviewPort 24000…
````
+
+# Control Protocol
+TCP-Port 9999
+````
+< set_video_a cam1
+> ok
+
+< set_composite_mode side_by_side_equal
+> ok
+
+< get_video_output_port
+> ok 11000
+
+< get_video_a
+> ok 0 cam1
+
+< set_composite_mode
+> ok side_by_side_equal
+
+< set_video_a blafoo
+> error "blafoo" is no known src
+````
diff --git a/voctocore/lib/commands.py b/voctocore/lib/commands.py
new file mode 100644
index 0000000..f3243f3
--- /dev/null
+++ b/voctocore/lib/commands.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python3
+import logging
+
+from lib.config import Config
+
+class ControlServerCommands():
+ log = logging.getLogger('ControlServerCommands')
+
+ pipeline = None
+ vnames = []
+
+ def __init__(self, pipeline):
+ self.pipeline = pipeline
+ self.vnames = Config.getlist('sources', 'video')
+
+ def decodeVideoSrcName(self, src_name_or_id):
+ if isinstance(src_name_or_id, str):
+ try:
+ return self.vnames.index(src_name_or_id)
+ except Exception as e:
+ raise IndexError("video-source %s unknown" % src_name_or_id)
+
+ if src_name_or_id < 0 or src_name_or_id >= len(self.vnames):
+ raise IndexError("video-source %s unknown" % src_name_or_id)
+
+
+ def set_video_a(self, src_name_or_id):
+ src_id = self.decodeVideoSrcName(src_name_or_id)
+ self.pipeline.vmixer.setVideoA(src_id)
+ return True
+
+ def set_video_b(self, src_name_or_id):
+ src_id = self.decodeVideoSrcName(src_name_or_id)
+ self.pipeline.vmixer.setVideoB(src_id)
+ return True
+
+ def set_composite_mode(self, composite_mode):
+ self.pipeline.vmixer.set_composite_mode(src_name_or_id)
+ return True
diff --git a/voctocore/lib/controlserver.py b/voctocore/lib/controlserver.py
index fffeda8..e267ca7 100644
--- a/voctocore/lib/controlserver.py
+++ b/voctocore/lib/controlserver.py
@@ -1,65 +1,109 @@
-import socket, threading, queue, logging
+#!/usr/bin/python3
+import socket, logging
from gi.repository import GObject
-def controlServerEntrypoint(f):
- # mark the method as something that requires view's class
- f.is_control_server_entrypoint = True
- return f
+from lib.commands import ControlServerCommands
class ControlServer():
log = logging.getLogger('ControlServer')
- def __init__(self, videomix):
+
+ boundSocket = None
+
+ def __init__(self, pipeline):
'''Initialize server and start listening.'''
- self.videomix = videomix
+ self.commands = ControlServerCommands(pipeline)
- sock = socket.socket()
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind(('0.0.0.0', 23000))
- sock.listen(1)
+ port = 9999
+ self.log.debug('Binding to Command-Socket on [::]:%u', port)
+ self.boundSocket = socket.socket(socket.AF_INET6)
+ self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
+ self.boundSocket.bind(('::', port))
+ self.boundSocket.listen(1)
- # register socket for callback inside the GTK-Mainloop
- GObject.io_add_watch(sock, GObject.IO_IN, self.listener)
+ self.log.debug('Setting GObject io-watch on Socket')
+ GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)
- def listener(self, sock, *args):
+ def on_connect(self, sock, *args):
'''Asynchronous connection listener. Starts a handler for each connection.'''
conn, addr = sock.accept()
- self.log.info("Connection from %s", addr)
+ self.log.info("Incomming Connection from %s", addr)
- # register data-received handler inside the GTK-Mainloop
- GObject.io_add_watch(conn, GObject.IO_IN, self.handler)
+ self.log.debug('Setting GObject io-watch on Connection')
+ GObject.io_add_watch(conn, GObject.IO_IN, self.on_data)
return True
- def handler(self, conn, *args):
+ def on_data(self, conn, *args):
'''Asynchronous connection handler. Processes each line from the socket.'''
- line = conn.recv(4096)
- if not len(line):
- self.log.debug("Connection closed.")
+ # construct a file-like object fro mthe socket
+ # to be able to read linewise and in utf-8
+ filelike = conn.makefile('rw')
+
+ # read a line from the socket
+ line = filelike.readline().strip()
+
+ # no data = remote closed connection
+ if len(line) == 0:
+ self.log.info("Connection closed.")
return False
- r = self.processLine(line.decode('utf-8'))
- if isinstance(r, str):
- conn.send((r+'\n').encode('utf-8'))
+ # 'quit' = remote wants us to close the connection
+ if line == 'quit':
+ self.log.info("Client asked us to close the Connection")
return False
- conn.send('OK\n'.encode('utf-8'))
+ # process the received line
+ success, msg = self.processLine(line)
+
+ # success = False -> error
+ if success == False:
+ # on error-responses the message is mandatory
+ if msg is None:
+ msg = '<no message>'
+
+ # respond with 'error' and the message
+ filelike.write('error '+msg+'\n')
+ self.log.info("Function-Call returned an Error: %s", msg)
+
+ # keep on listening on that connection
+ return True
+
+ # success = True and not message
+ if msg is None:
+ # respond with a simple 'ok'
+ filelike.write('ok\n')
+ else:
+ # respond with the returned message
+ filelike.write('ok '+msg+'\n')
return True
-
-
-
def processLine(self, line):
- command, argstring = (line.strip()+' ').split(' ', 1)
+ # split line into command and optional args
+ command, argstring = (line+' ').split(' ', 1)
args = argstring.strip().split()
- self.log.info(command % args)
- if not hasattr(self.videomix, command):
- return 'unknown command {}'.format(command)
+ # log function-call as parsed
+ self.log.info("Read Function-Call from Socket: %s( %s )", command, args)
+
+ # check that the function-call is a known Command
+ if not hasattr(self.commands, command):
+ return False, 'unknown command %s' % command
- f = getattr(self.videomix, command)
- if not hasattr(f, 'is_control_server_entrypoint'):
- return 'method {} not callable from controlserver'.format(command)
try:
- return f(*args)
+ # fetch the function-pointer
+ f = getattr(self.commands, command)
+
+ # call the function
+ ret = f(*args)
+
+ # if it returned an iterable, probably (Success, Message), pass that on
+ if hasattr(ret, '__iter__'):
+ return ret
+ else:
+ # otherwise construct a tuple
+ return (ret, None)
+
except Exception as e:
- return str(e)
+ # In case of an Exception, return that
+ return False, str(e)
diff --git a/voctocore/lib/pipeline.py b/voctocore/lib/pipeline.py
index fb9b846..a4ada04 100644
--- a/voctocore/lib/pipeline.py
+++ b/voctocore/lib/pipeline.py
@@ -2,9 +2,6 @@
import logging
from gi.repository import Gst
-# import controlserver annotation
-from lib.controlserver import controlServerEntrypoint
-
# import library components
from lib.config import Config
from lib.video.src import VideoSrc
@@ -22,8 +19,11 @@ class Pipeline(object):
vmixerout = None
def __init__(self):
+ self.log.debug('creating Video-Pipeline')
self.initVideo()
+ self.log.debug('creating Control-Server')
+
def initVideo(self):
caps = Config.get('mix', 'videocaps')
self.log.info('Video-Caps configured to: %s', caps)
@@ -47,7 +47,7 @@ class Pipeline(object):
self.vmirrors.append(mirror)
self.log.debug('Creating Video-Mixer')
- self.videomixer = VideoMix()
+ self.vmixer = VideoMix()
port = 11000
self.log.debug('Creating Video-Mixer-Output at tcp-port %u', port)
diff --git a/voctocore/lib/video/mix.py b/voctocore/lib/video/mix.py
index 8bb7036..a9e1513 100644
--- a/voctocore/lib/video/mix.py
+++ b/voctocore/lib/video/mix.py
@@ -1,19 +1,30 @@
#!/usr/bin/python3
import logging
from gi.repository import Gst
+from enum import Enum
from lib.config import Config
+class ComposteModes(Enum):
+ fullscreen = 0
+
class VideoMix(object):
log = logging.getLogger('VideoMix')
mixingPipeline = None
+ caps = None
+ names = []
+
+ compositeMode = ComposteModes.fullscreen
+ sourceA = 0
+ sourceB = 1
+
def __init__(self):
- caps = Config.get('mix', 'videocaps')
+ self.caps = Config.get('mix', 'videocaps')
- names = Config.getlist('sources', 'video')
- self.log.info('Configuring Mixer for %u Sources', len(names))
+ self.names = Config.getlist('sources', 'video')
+ self.log.info('Configuring Mixer for %u Sources', len(self.names))
pipeline = """
videomixer name=mix !
@@ -21,10 +32,10 @@ class VideoMix(object):
textoverlay text=mixer halignment=left valignment=top ypad=175 !
intervideosink channel=video_mix
""".format(
- caps=caps
+ caps=self.caps
)
- for idx, name in enumerate(names):
+ for idx, name in enumerate(self.names):
pipeline += """
intervideosrc channel=video_{name}_mixer !
{caps} !
@@ -33,22 +44,42 @@ class VideoMix(object):
mix.
""".format(
name=name,
- caps=caps,
+ caps=self.caps,
idx=idx
)
- self.log.debug('Launching Mixing-Pipeline:\n%s', pipeline)
+ self.log.debug('Creating Mixing-Pipeline:\n%s', pipeline)
self.mixingPipeline = Gst.parse_launch(pipeline)
+
+ self.log.debug('Initializing Mixer-State')
+ self.updateMixerState()
+
+ self.log.debug('Launching Mixing-Pipeline:\n%s', pipeline)
self.mixingPipeline.set_state(Gst.State.PLAYING)
- mixerpad = self.mixingPipeline.get_by_name('mix').get_static_pad('sink_0')
- mixerpad.set_property('alpha', 0.5)
- mixerpad.set_property('xpos', 64)
- mixerpad.set_property('ypos', 64)
+ def updateMixerState(self):
+ if self.compositeMode == ComposteModes.fullscreen:
+ self.updateMixerStateFullscreen()
+
+ def updateMixerStateFullscreen(self):
+ self.log.info('Updating Mixer-State for Fullscreen-Composition')
+ for idx, name in enumerate(self.names):
+ alpha = int(idx == self.sourceA)
+
+ self.log.debug('Setting Mixerpad %u to x/y=0 and alpha=%u', idx, alpha)
+ mixerpad = self.mixingPipeline.get_by_name('mix').get_static_pad('sink_%u' % idx)
+ mixerpad.set_property('alpha', alpha )
+ mixerpad.set_property('xpos', 0)
+ mixerpad.set_property('ypos', 0)
+
+ self.log.debug('Resetting Scaler %u to non-scaling', idx)
+ capsfilter = self.mixingPipeline.get_by_name('caps_%u' % idx)
+ capsfilter.set_property('caps', Gst.Caps.from_string(self.caps))
- mixerpad = self.mixingPipeline.get_by_name('mix').get_static_pad('sink_1')
- mixerpad.set_property('alpha', 0.2)
+ def setVideoA(self, source):
+ self.sourceA = source
+ self.updateMixerState()
- capsilter = self.mixingPipeline.get_by_name('caps_1')
- capsilter.set_property('caps', Gst.Caps.from_string(
- 'video/x-raw,width=320,height=180'))
+ def setVideoB(self, source):
+ self.sourceN = source
+ self.updateMixerState()
diff --git a/voctocore/scripts/set-composite-none.sh b/voctocore/scripts/set-composite-none.sh
new file mode 100755
index 0000000..4ac2a66
--- /dev/null
+++ b/voctocore/scripts/set-composite-none.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo set_composite_mode none | nc localhost 9999
diff --git a/voctocore/scripts/set-composite-pip.sh b/voctocore/scripts/set-composite-pip.sh
new file mode 100755
index 0000000..04e41df
--- /dev/null
+++ b/voctocore/scripts/set-composite-pip.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo set_composite_mode pip | nc localhost 9999
diff --git a/voctocore/scripts/set-composite-side-by-side-equal.sh b/voctocore/scripts/set-composite-side-by-side-equal.sh
new file mode 100755
index 0000000..d3d81df
--- /dev/null
+++ b/voctocore/scripts/set-composite-side-by-side-equal.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo set_composite_mode side-by-side-equal | nc localhost 9999
diff --git a/voctocore/scripts/set-video-cam1.sh b/voctocore/scripts/set-video-cam1.sh
new file mode 100755
index 0000000..bc59e80
--- /dev/null
+++ b/voctocore/scripts/set-video-cam1.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo set_video_a cam1 | nc localhost 9999
diff --git a/voctocore/scripts/set-video-cam2.sh b/voctocore/scripts/set-video-cam2.sh
new file mode 100755
index 0000000..9bc0988
--- /dev/null
+++ b/voctocore/scripts/set-video-cam2.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo set_video_a cam2 | nc localhost 9999